diff --git a/.changeset/afraid-numbers-invite.md b/.changeset/afraid-numbers-invite.md new file mode 100644 index 0000000000..ee81341d3a --- /dev/null +++ b/.changeset/afraid-numbers-invite.md @@ -0,0 +1,15 @@ +--- +'@backstage/repo-tools': minor +--- + +Updates the OpenAPI client template to support the new format for identifying plugin ID. You should now use `info.title` like so, + +```diff +info: ++ title: yourPluginId +- title: @internal/plugin-*-backend + +servers: + - / +- - yourPluginId +``` diff --git a/.changeset/angry-eagles-end.md b/.changeset/angry-eagles-end.md deleted file mode 100644 index eec1e7b710..0000000000 --- a/.changeset/angry-eagles-end.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-test-utils': patch ---- - -Added `createExtensionTester` for rendering extensions in tests. diff --git a/.changeset/angry-gorillas-unite.md b/.changeset/angry-gorillas-unite.md deleted file mode 100644 index 73d5a6c88b..0000000000 --- a/.changeset/angry-gorillas-unite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-atlassian-provider': minor ---- - -New module for `@backstage/plugin-auth-backend` that adds an atlassian auth provider diff --git a/.changeset/angry-phones-arrive.md b/.changeset/angry-phones-arrive.md new file mode 100644 index 0000000000..2afcd240a9 --- /dev/null +++ b/.changeset/angry-phones-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': minor +--- + +Add support for `oneOf` in client generated by `schema openapi generate-client`. diff --git a/.changeset/angry-walls-despacito.md b/.changeset/angry-walls-despacito.md deleted file mode 100644 index f380b6df77..0000000000 --- a/.changeset/angry-walls-despacito.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-compat-api': patch ---- - -Added `convertLegacyRouteRef` utility to convert existing route refs to be used with the new experimental packages. diff --git a/.changeset/angry-walls-juggle.md b/.changeset/angry-walls-juggle.md deleted file mode 100644 index 3df4a80b28..0000000000 --- a/.changeset/angry-walls-juggle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-plugin-api': patch ---- - -Removed the alpha `convertLegacyRouteRef` utility, which as been moved to `@backstage/core-compat-api` diff --git a/.changeset/brave-socks-raise.md b/.changeset/brave-socks-raise.md deleted file mode 100644 index e8df72f4f5..0000000000 --- a/.changeset/brave-socks-raise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-addons-test-utils': patch ---- - -Move `@testing-library/react` to be a `peerDependency` diff --git a/.changeset/breezy-pans-glow.md b/.changeset/breezy-pans-glow.md deleted file mode 100644 index 026465aef7..0000000000 --- a/.changeset/breezy-pans-glow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Added pagination support to `EntityListProvider`. diff --git a/.changeset/bright-eyes-film.md b/.changeset/bright-eyes-film.md deleted file mode 100644 index 503079ad00..0000000000 --- a/.changeset/bright-eyes-film.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-test-utils': minor ---- - -New testing utility library for `@backstage/frontend-app-api` and `@backstage/frontend-plugin-api`. diff --git a/.changeset/metal-eggs-smile.md b/.changeset/brown-items-hammer.md similarity index 51% rename from .changeset/metal-eggs-smile.md rename to .changeset/brown-items-hammer.md index 1a7e86d6f0..fa023a026b 100644 --- a/.changeset/metal-eggs-smile.md +++ b/.changeset/brown-items-hammer.md @@ -2,4 +2,4 @@ '@backstage/plugin-auth-backend-module-okta-provider': patch --- -Adds okta-provider backend module for the auth plugin +Added missing `additionalScopes` option to configuration schema. diff --git a/.changeset/chatty-actors-live.md b/.changeset/chatty-actors-live.md deleted file mode 100644 index dcf308edec..0000000000 --- a/.changeset/chatty-actors-live.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Add dependency on `graphql-config` to compensate for `graphql-language-service` needing it but not shipping the dep properly diff --git a/.changeset/chilled-chefs-notice.md b/.changeset/chilled-chefs-notice.md new file mode 100644 index 0000000000..a12babb2a4 --- /dev/null +++ b/.changeset/chilled-chefs-notice.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-rails': patch +--- + +Make `containerRunner` argument optional diff --git a/.changeset/chilled-terms-breathe.md b/.changeset/chilled-terms-breathe.md deleted file mode 100644 index 654185492a..0000000000 --- a/.changeset/chilled-terms-breathe.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Allow a default cache TTL to be set through the app config diff --git a/.changeset/chilly-seahorses-bake.md b/.changeset/chilly-seahorses-bake.md new file mode 100644 index 0000000000..8ab80cf93e --- /dev/null +++ b/.changeset/chilly-seahorses-bake.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': patch +--- + +The backwards compatibility provider will now use the new `ComponentsApi` and `IconsApi` when implementing the old `AppContext`. diff --git a/.changeset/cold-cooks-care.md b/.changeset/cold-cooks-care.md new file mode 100644 index 0000000000..56c41a55e9 --- /dev/null +++ b/.changeset/cold-cooks-care.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-entity-validation': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-catalog-graph': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-catalog': patch +--- + +Change default icon for unknown entities to nothing instead of the help icon. diff --git a/.changeset/cold-pans-crash.md b/.changeset/cold-pans-crash.md deleted file mode 100644 index 85858fd270..0000000000 --- a/.changeset/cold-pans-crash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Migrate the atlassian auth provider to be implemented using the new `@backstage/plugin-auth-backend-module-atlassian-provider` module diff --git a/.changeset/create-app-1700579510.md b/.changeset/create-app-1700579510.md deleted file mode 100644 index b50d431d4b..0000000000 --- a/.changeset/create-app-1700579510.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Bumped create-app version. diff --git a/.changeset/create-app-1700607979.md b/.changeset/create-app-1700607979.md deleted file mode 100644 index b50d431d4b..0000000000 --- a/.changeset/create-app-1700607979.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Bumped create-app version. diff --git a/.changeset/curly-walls-relate.md b/.changeset/curly-walls-relate.md deleted file mode 100644 index 40cacca907..0000000000 --- a/.changeset/curly-walls-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Add missing export for IconLinkVertical diff --git a/.changeset/curvy-ladybugs-impress.md b/.changeset/curvy-ladybugs-impress.md new file mode 100644 index 0000000000..ab799e4888 --- /dev/null +++ b/.changeset/curvy-ladybugs-impress.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-adr': patch +--- + +Updated README diff --git a/.changeset/cyan-bats-lick.md b/.changeset/cyan-bats-lick.md new file mode 100644 index 0000000000..0683d806b0 --- /dev/null +++ b/.changeset/cyan-bats-lick.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-azure-devops-backend': patch +--- + +Added a note about Service Principles diff --git a/.changeset/cyan-icons-rest.md b/.changeset/cyan-icons-rest.md new file mode 100644 index 0000000000..3092c1bc90 --- /dev/null +++ b/.changeset/cyan-icons-rest.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-explore-backend': patch +--- + +Add support for the new backend system. + +A new backend plugin for the explore backend +was added and exported as `default`. + +You can use it with the new backend system like + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-explore-backend')); +``` diff --git a/.changeset/dry-lizards-taste.md b/.changeset/dry-lizards-taste.md new file mode 100644 index 0000000000..90e4c9f659 --- /dev/null +++ b/.changeset/dry-lizards-taste.md @@ -0,0 +1,6 @@ +--- +'@backstage/app-defaults': patch +'@backstage/plugin-catalog': patch +--- + +Change default icon for `kind:resource` to the storage icon. diff --git a/.changeset/dry-lobsters-flash.md b/.changeset/dry-lobsters-flash.md new file mode 100644 index 0000000000..9815bf62f4 --- /dev/null +++ b/.changeset/dry-lobsters-flash.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Added `IconsApi` implementation and the ability to configure icons through the `icons` option for `createApp` and `createSpecializedApp`. This is not a long-term solution as icons should be installable via extensions instead. This is just a stop-gap to make sure there is feature parity with the existing frontend system. diff --git a/.changeset/dry-readers-raise.md b/.changeset/dry-readers-raise.md deleted file mode 100644 index c13e830062..0000000000 --- a/.changeset/dry-readers-raise.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Added `headerOptions` to `TemplateListPage` to optionally override default values. -Changed `themeId` of TemplateListPage from `website` to `home`. diff --git a/.changeset/dull-dolphins-explain.md b/.changeset/dull-dolphins-explain.md new file mode 100644 index 0000000000..d8864e72e8 --- /dev/null +++ b/.changeset/dull-dolphins-explain.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-backend-module-aws': patch +--- + +The default EKS cluster entity transformer now sets the new +`kubernetes.io/x-k8s-aws-id` annotation. diff --git a/.changeset/dull-fireants-repeat.md b/.changeset/dull-fireants-repeat.md new file mode 100644 index 0000000000..4e849bfbaf --- /dev/null +++ b/.changeset/dull-fireants-repeat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Use the externalized `auth-backend-module-microsoft-provider` again. diff --git a/.changeset/eleven-ants-pretend.md b/.changeset/eleven-ants-pretend.md deleted file mode 100644 index 6b188117ba..0000000000 --- a/.changeset/eleven-ants-pretend.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Minor improvements to `Table` component. diff --git a/.changeset/empty-dingos-grow.md b/.changeset/empty-dingos-grow.md deleted file mode 100644 index 8a43cd210f..0000000000 --- a/.changeset/empty-dingos-grow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Fix copy entity url function in http contexts. diff --git a/.changeset/empty-ligers-hang.md b/.changeset/empty-ligers-hang.md new file mode 100644 index 0000000000..c17609712a --- /dev/null +++ b/.changeset/empty-ligers-hang.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Overflowing labels in OwnerPicker (Catalog) are now truncated. Hovering over them shows the full label diff --git a/.changeset/famous-houses-thank.md b/.changeset/famous-houses-thank.md new file mode 100644 index 0000000000..8fa0eb2d75 --- /dev/null +++ b/.changeset/famous-houses-thank.md @@ -0,0 +1,7 @@ +--- +'@backstage/frontend-test-utils': patch +'@backstage/frontend-app-api': patch +'@backstage/core-compat-api': patch +--- + +Updates to use the new `RouteResolutionsApi`. diff --git a/.changeset/fast-jeans-walk.md b/.changeset/fast-jeans-walk.md new file mode 100644 index 0000000000..f733c9f464 --- /dev/null +++ b/.changeset/fast-jeans-walk.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-react': minor +'@backstage/plugin-scaffolder': minor +--- + +Remove the old legacy exports from `/alpha` diff --git a/.changeset/fifty-adults-watch.md b/.changeset/fifty-adults-watch.md new file mode 100644 index 0000000000..8bff1c28cd --- /dev/null +++ b/.changeset/fifty-adults-watch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-devtools': patch +--- + +Refactored code to improve accessibility by moving elements outside the `ul` tag and placing them appropriately. Also adjusted theme to offer better contrast. diff --git a/.changeset/fifty-files-argue.md b/.changeset/fifty-files-argue.md new file mode 100644 index 0000000000..b80b2989af --- /dev/null +++ b/.changeset/fifty-files-argue.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-test-utils': minor +'@backstage/backend-common': minor +--- + +drop databases after unit tests if the database instance is not running in docker diff --git a/.changeset/flat-wasps-fold.md b/.changeset/flat-wasps-fold.md new file mode 100644 index 0000000000..653dc0d78f --- /dev/null +++ b/.changeset/flat-wasps-fold.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added initial `IconsApi` definition. diff --git a/.changeset/forty-cars-scream.md b/.changeset/forty-cars-scream.md new file mode 100644 index 0000000000..284acc3c44 --- /dev/null +++ b/.changeset/forty-cars-scream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': patch +--- + +Allow defining default output text to be shown diff --git a/.changeset/friendly-cheetahs-rescue.md b/.changeset/friendly-cheetahs-rescue.md new file mode 100644 index 0000000000..4f54a022af --- /dev/null +++ b/.changeset/friendly-cheetahs-rescue.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +Fixed an issue where a misleading error message would be logged when an +unsupported service locator method was specified. diff --git a/.changeset/funny-timers-visit.md b/.changeset/funny-timers-visit.md new file mode 100644 index 0000000000..3d802328b5 --- /dev/null +++ b/.changeset/funny-timers-visit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-react': patch +--- + +Fix broken XtermJS CSS import diff --git a/.changeset/gentle-lies-greet.md b/.changeset/gentle-lies-greet.md deleted file mode 100644 index 293f0ef064..0000000000 --- a/.changeset/gentle-lies-greet.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-app-api': patch ---- - -Removed `@backstage/plugin-graphiql` dependency. diff --git a/.changeset/giant-suits-switch.md b/.changeset/giant-suits-switch.md new file mode 100644 index 0000000000..77d0e62c73 --- /dev/null +++ b/.changeset/giant-suits-switch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-azure-devops-backend': patch +--- + +Fixed bug with `extractPartsFromAsset` that resulted in a leading `.` being removed from the path in an otherwise valid path (ex. `.assets/image.png`). The leading `.` will now only be moved for paths beginning with `./`. diff --git a/.changeset/gold-shirts-yell.md b/.changeset/gold-shirts-yell.md deleted file mode 100644 index ac03ce3362..0000000000 --- a/.changeset/gold-shirts-yell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -Support member list scrollable when parent has specified height diff --git a/.changeset/good-lemons-lick.md b/.changeset/good-lemons-lick.md new file mode 100644 index 0000000000..3fde0c7d84 --- /dev/null +++ b/.changeset/good-lemons-lick.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-scaffolder-backend-module-confluence-to-markdown': patch +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket': patch +'@backstage/plugin-scaffolder-backend-module-gerrit': patch +'@backstage/plugin-scaffolder-backend-module-github': patch +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +'@backstage/plugin-scaffolder-backend-module-sentry': patch +'@backstage/plugin-scaffolder-backend-module-yeoman': patch +'@backstage/plugin-scaffolder-backend-module-azure': patch +'@backstage/plugin-scaffolder-backend-module-rails': patch +--- + +Exporting a default module for the new Backend System diff --git a/.changeset/gorgeous-bobcats-press.md b/.changeset/gorgeous-bobcats-press.md new file mode 100644 index 0000000000..6193918cb7 --- /dev/null +++ b/.changeset/gorgeous-bobcats-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Include the extension point ID and the module ID in the backend init error message. diff --git a/.changeset/great-rings-type.md b/.changeset/great-rings-type.md deleted file mode 100644 index 0b10c51651..0000000000 --- a/.changeset/great-rings-type.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Use `Readable.from` to fix some of the stream issues diff --git a/.changeset/grumpy-poets-study.md b/.changeset/grumpy-poets-study.md new file mode 100644 index 0000000000..9925f2f87a --- /dev/null +++ b/.changeset/grumpy-poets-study.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Added basic support for the new frontend system, exported from the `/alpha` subpath. diff --git a/.changeset/mighty-beans-wink.md b/.changeset/hot-pillows-poke.md similarity index 59% rename from .changeset/mighty-beans-wink.md rename to .changeset/hot-pillows-poke.md index e0623643e1..8060e7121f 100644 --- a/.changeset/mighty-beans-wink.md +++ b/.changeset/hot-pillows-poke.md @@ -1,6 +1,6 @@ --- -'@backstage/plugin-catalog-import': patch '@backstage/plugin-user-settings': patch +'@backstage/plugin-scaffolder': patch '@backstage/plugin-tech-radar': patch '@backstage/plugin-graphiql': patch '@backstage/plugin-techdocs': patch @@ -8,4 +8,4 @@ '@backstage/plugin-search': patch --- -The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. +Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. diff --git a/.changeset/hot-tips-doubt.md b/.changeset/hot-tips-doubt.md new file mode 100644 index 0000000000..464ddec302 --- /dev/null +++ b/.changeset/hot-tips-doubt.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-openapi-utils': patch +--- + +Internal updates due to `json-schema-to-ts` diff --git a/.changeset/hot-wolves-flash.md b/.changeset/hot-wolves-flash.md deleted file mode 100644 index 84c4d34f37..0000000000 --- a/.changeset/hot-wolves-flash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-react': patch ---- - -Step titles in the Stepper are now clickable and redirect the user to the corresponding step, as an alternative to using the back buttons. diff --git a/.changeset/kind-badgers-rush.md b/.changeset/kind-badgers-rush.md deleted file mode 100644 index 4ec19bd17b..0000000000 --- a/.changeset/kind-badgers-rush.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/backend-openapi-utils': patch -'@backstage/plugin-scaffolder-react': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-scaffolder': patch ---- - -Minor updates for TypeScript 5.2.2+ compatibility diff --git a/.changeset/kind-clouds-fly.md b/.changeset/kind-clouds-fly.md new file mode 100644 index 0000000000..22aa7cda33 --- /dev/null +++ b/.changeset/kind-clouds-fly.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-node': patch +--- + +Enabled a way to include custom auth metadata info on the clusters endpoint. If you want to implement a Kubernetes auth strategy which requires surfacing custom auth metadata to the frontend, use the new presentAuthMetadata method on the AuthenticationStrategy interface. diff --git a/.changeset/large-frogs-grab.md b/.changeset/large-frogs-grab.md new file mode 100644 index 0000000000..2d36de3407 --- /dev/null +++ b/.changeset/large-frogs-grab.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Adding `supportedSubmitMethods` prop to `api-docs` to pass to the Swagger UI. This allows users to specify which HTTP methods they wish to allow end-users to make requests through the `Try It Out` button on the Swagger UI. diff --git a/.changeset/large-tables-wonder.md b/.changeset/large-tables-wonder.md new file mode 100644 index 0000000000..f54402dbd4 --- /dev/null +++ b/.changeset/large-tables-wonder.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Add experimental support for optional `auth` app entry point. diff --git a/.changeset/large-weeks-accept.md b/.changeset/large-weeks-accept.md deleted file mode 100644 index b640c44d42..0000000000 --- a/.changeset/large-weeks-accept.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-react': patch ---- - -Add ID property to the table displaying kubernetes pods to avoid closing the info sidebar when the data reloads and needs to rerender. diff --git a/.changeset/lemon-cameras-remember.md b/.changeset/lemon-cameras-remember.md new file mode 100644 index 0000000000..29cd88602f --- /dev/null +++ b/.changeset/lemon-cameras-remember.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Migrated oidc auth provider to new `@backstage/plugin-auth-backend-module-oidc-provider` module package. diff --git a/.changeset/long-suns-bow.md b/.changeset/long-suns-bow.md new file mode 100644 index 0000000000..81fafa8dae --- /dev/null +++ b/.changeset/long-suns-bow.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-backend': minor +'@backstage/plugin-search-backend': minor +'@backstage/plugin-todo-backend': patch +--- + +Updates the OpenAPI spec to use plugin as `info.title` instead of package name. diff --git a/.changeset/long-wolves-return.md b/.changeset/long-wolves-return.md deleted file mode 100644 index 8d4d78792f..0000000000 --- a/.changeset/long-wolves-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-app-api': patch ---- - -Bringing over apis from core-plugin-api diff --git a/.changeset/loud-walls-compare.md b/.changeset/loud-walls-compare.md new file mode 100644 index 0000000000..495ad933d9 --- /dev/null +++ b/.changeset/loud-walls-compare.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Fixed inconsistencies in icons used for System and Template diff --git a/.changeset/lovely-starfishes-remain.md b/.changeset/lovely-starfishes-remain.md new file mode 100644 index 0000000000..c57bfa0aaa --- /dev/null +++ b/.changeset/lovely-starfishes-remain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Removed alpha symbol from Task List header diff --git a/.changeset/lovely-terms-search.md b/.changeset/lovely-terms-search.md deleted file mode 100644 index 7590a0d5a6..0000000000 --- a/.changeset/lovely-terms-search.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-tasks': patch ---- - -Allow tasks to run more often that the default work check interval, which is 5 seconds. diff --git a/.changeset/lucky-kids-cough.md b/.changeset/lucky-kids-cough.md deleted file mode 100644 index 5ab1856e35..0000000000 --- a/.changeset/lucky-kids-cough.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -- Fixes bug where after unregistering an entity you are redirected to `/`. -- Adds an optional external route `unregisterRedirect` to override this behaviour to another route. diff --git a/.changeset/many-pugs-change.md b/.changeset/many-pugs-change.md new file mode 100644 index 0000000000..318c571891 --- /dev/null +++ b/.changeset/many-pugs-change.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +The purpose of this patch is to add a new login method which is `googleServiceAccount` configuring the kubernetes properties in the app-config.yaml file with authProvider key diff --git a/.changeset/metal-elephants-sit.md b/.changeset/metal-elephants-sit.md new file mode 100644 index 0000000000..093936da3b --- /dev/null +++ b/.changeset/metal-elephants-sit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Introduced a new `MultiEntityPicker` field that supports selecting multiple Entities diff --git a/.changeset/metal-students-drive.md b/.changeset/metal-students-drive.md new file mode 100644 index 0000000000..04f338c64e --- /dev/null +++ b/.changeset/metal-students-drive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-entity-validation': patch +--- + +Improves UX of the EntityValidationPage: Moves the validate button below the EntityTextArea which is actually validated, the location TextField can now be hidden to prevent confusion about what is validated and additional content can be added to the top of the validation page. diff --git a/.changeset/nasty-tips-exercise.md b/.changeset/nasty-tips-exercise.md deleted file mode 100644 index 5dd38368d4..0000000000 --- a/.changeset/nasty-tips-exercise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fix issue with Circular JSON dependencies in templating diff --git a/.changeset/neat-hotels-wink.md b/.changeset/neat-hotels-wink.md new file mode 100644 index 0000000000..fca82e38fa --- /dev/null +++ b/.changeset/neat-hotels-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Added the option to provide custom `groupTransformer`, `userTransformer` and `groupNameTransformer` to allow custom transformations of groups and users diff --git a/.changeset/nine-bulldogs-camp.md b/.changeset/nine-bulldogs-camp.md new file mode 100644 index 0000000000..b6612b9948 --- /dev/null +++ b/.changeset/nine-bulldogs-camp.md @@ -0,0 +1,8 @@ +--- +'@backstage/catalog-client': minor +'@backstage/plugin-catalog-backend': minor +'@backstage/plugin-search-backend': minor +'@backstage/plugin-todo-backend': patch +--- + +Updates the OpenAPI specification title to plugin ID instead of package name. diff --git a/.changeset/nine-olives-swim.md b/.changeset/nine-olives-swim.md new file mode 100644 index 0000000000..40852c08c2 --- /dev/null +++ b/.changeset/nine-olives-swim.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-node': minor +--- + +Added functions to clone a repo, create a branch, add files and push and commit to the branch. This allows for files to be added to the a PR for use in the bitbucket pull request action for issue #21762 diff --git a/.changeset/old-students-smoke.md b/.changeset/old-students-smoke.md new file mode 100644 index 0000000000..8e072c5203 --- /dev/null +++ b/.changeset/old-students-smoke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-oidc-provider': minor +--- + +Created new `@backstage/plugin-auth-backend-module-oidc-provider` module package to house oidc auth provider migration. diff --git a/.changeset/olive-singers-accept.md b/.changeset/olive-singers-accept.md new file mode 100644 index 0000000000..7e8cf7ae8d --- /dev/null +++ b/.changeset/olive-singers-accept.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': patch +--- + +Added `convertLegacyRouteRefs` for bulk conversion of plugin routes. diff --git a/.changeset/perfect-apes-sing.md b/.changeset/perfect-apes-sing.md deleted file mode 100644 index fede777057..0000000000 --- a/.changeset/perfect-apes-sing.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/core-components': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-catalog': patch ---- - -Register component overrides in the global `OverrideComponentNameToClassKeys` provided by `@backstage/theme`. This will in turn will provide component style override types for `createUnifiedTheme`. diff --git a/.changeset/pink-dryers-repair.md b/.changeset/pink-dryers-repair.md deleted file mode 100644 index 42720c635a..0000000000 --- a/.changeset/pink-dryers-repair.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -The Okta provider implementation is moved to the new module diff --git a/.changeset/plenty-numbers-enjoy.md b/.changeset/plenty-numbers-enjoy.md deleted file mode 100644 index 5e5acbabf1..0000000000 --- a/.changeset/plenty-numbers-enjoy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-react': patch ---- - -Removed `@backstage/frontend-app-api` dependency. diff --git a/.changeset/polite-knives-build.md b/.changeset/polite-knives-build.md deleted file mode 100644 index 66ecbf0645..0000000000 --- a/.changeset/polite-knives-build.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': minor ---- - -You can now select `single` kubernetes cluster that the entity is part-of from all your defined kubernetes clusters, by passing `backstage.io/kubernetes-cluster` annotation with the defined cluster name. - -If you do not specify the annotation by `default it fetches all` defined kubernetes cluster. - -To apply - -catalog-info.yaml - -```diff -annotations: - 'backstage.io/kubernetes-id': dice-roller - 'backstage.io/kubernetes-namespace': dice-space -+ 'backstage.io/kubernetes-cluster': dice-cluster - 'backstage.io/kubernetes-label-selector': 'app=my-app,component=front-end' -``` diff --git a/.changeset/polite-meals-hug.md b/.changeset/polite-meals-hug.md new file mode 100644 index 0000000000..664f450c6d --- /dev/null +++ b/.changeset/polite-meals-hug.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-stack-overflow': patch +--- + +Fixes a bug that made the API return questions not related to the tags provided diff --git a/.changeset/poor-pandas-dream.md b/.changeset/poor-pandas-dream.md deleted file mode 100644 index 2804fa6926..0000000000 --- a/.changeset/poor-pandas-dream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Tweaked Node.js version check for when to use the new module register API with the new backend `package start` command. diff --git a/.changeset/pre.json b/.changeset/pre.json index 5a151bfc2f..7b419c4250 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -2,314 +2,350 @@ "mode": "pre", "tag": "next", "initialVersions": { - "example-app": "0.2.89", - "@backstage/app-defaults": "1.4.5", - "example-app-next": "0.0.3", - "app-next-example-plugin": "0.0.3", - "example-backend": "0.2.89", - "@backstage/backend-app-api": "0.5.8", - "@backstage/backend-common": "0.19.9", - "@backstage/backend-defaults": "0.2.7", - "@backstage/backend-dev-utils": "0.1.2", - "example-backend-next": "0.0.17", - "@backstage/backend-openapi-utils": "0.1.0", - "@backstage/backend-plugin-api": "0.6.7", - "@backstage/backend-plugin-manager": "0.0.3", - "@backstage/backend-tasks": "0.5.12", - "@backstage/backend-test-utils": "0.2.8", - "@backstage/catalog-client": "1.4.6", + "example-app": "0.2.91", + "@backstage/app-defaults": "1.4.7", + "example-app-next": "0.0.5", + "app-next-example-plugin": "0.0.5", + "example-backend": "0.2.91", + "@backstage/backend-app-api": "0.5.10", + "@backstage/backend-common": "0.20.1", + "@backstage/backend-defaults": "0.2.9", + "@backstage/backend-dev-utils": "0.1.3", + "@backstage/backend-dynamic-feature-service": "0.1.0", + "example-backend-next": "0.0.19", + "@backstage/backend-openapi-utils": "0.1.2", + "@backstage/backend-plugin-api": "0.6.9", + "@backstage/backend-tasks": "0.5.14", + "@backstage/backend-test-utils": "0.2.10", + "@backstage/catalog-client": "1.5.2", "@backstage/catalog-model": "1.4.3", - "@backstage/cli": "0.24.0", + "@backstage/cli": "0.25.1", "@backstage/cli-common": "0.1.13", - "@backstage/cli-node": "0.2.0", + "@backstage/cli-node": "0.2.2", "@backstage/codemods": "0.1.46", "@backstage/config": "1.1.1", - "@backstage/config-loader": "1.5.3", - "@backstage/core-app-api": "1.11.1", - "@backstage/core-compat-api": "0.0.1", - "@backstage/core-components": "0.13.8", - "@backstage/core-plugin-api": "1.8.0", - "@backstage/create-app": "0.5.7", - "@backstage/dev-utils": "1.0.23", - "e2e-test": "0.2.9", + "@backstage/config-loader": "1.6.1", + "@backstage/core-app-api": "1.11.3", + "@backstage/core-compat-api": "0.1.1", + "@backstage/core-components": "0.13.10", + "@backstage/core-plugin-api": "1.8.2", + "@backstage/create-app": "0.5.9", + "@backstage/dev-utils": "1.0.26", + "e2e-test": "0.2.11", "@backstage/e2e-test-utils": "0.1.0", "@backstage/errors": "1.2.3", - "@backstage/eslint-plugin": "0.1.3", - "@backstage/frontend-app-api": "0.3.0", - "@backstage/frontend-plugin-api": "0.3.0", - "@backstage/integration": "1.7.2", + "@backstage/eslint-plugin": "0.1.4", + "@backstage/frontend-app-api": "0.5.0", + "@backstage/frontend-plugin-api": "0.5.0", + "@backstage/frontend-test-utils": "0.1.1", + "@backstage/integration": "1.8.0", "@backstage/integration-aws-node": "0.1.8", - "@backstage/integration-react": "1.1.21", + "@backstage/integration-react": "1.1.23", "@backstage/release-manifests": "0.0.11", - "@backstage/repo-tools": "0.4.0", - "@techdocs/cli": "1.7.0", - "techdocs-cli-embedded-app": "0.2.88", - "@backstage/test-utils": "1.4.5", - "@backstage/theme": "0.4.4", + "@backstage/repo-tools": "0.5.2", + "@techdocs/cli": "1.8.1", + "techdocs-cli-embedded-app": "0.2.90", + "@backstage/test-utils": "1.4.7", + "@backstage/theme": "0.5.0", "@backstage/types": "1.1.1", "@backstage/version-bridge": "1.0.7", - "@backstage/plugin-adr": "0.6.9", - "@backstage/plugin-adr-backend": "0.4.4", - "@backstage/plugin-adr-common": "0.2.17", - "@backstage/plugin-airbrake": "0.3.26", - "@backstage/plugin-airbrake-backend": "0.3.4", - "@backstage/plugin-allure": "0.1.42", - "@backstage/plugin-analytics-module-ga": "0.1.35", - "@backstage/plugin-analytics-module-ga4": "0.1.6", - "@backstage/plugin-analytics-module-newrelic-browser": "0.0.4", - "@backstage/plugin-apache-airflow": "0.2.17", - "@backstage/plugin-api-docs": "0.10.0", + "@backstage/plugin-adr": "0.6.12", + "@backstage/plugin-adr-backend": "0.4.6", + "@backstage/plugin-adr-common": "0.2.19", + "@backstage/plugin-airbrake": "0.3.29", + "@backstage/plugin-airbrake-backend": "0.3.6", + "@backstage/plugin-allure": "0.1.45", + "@backstage/plugin-analytics-module-ga": "0.1.37", + "@backstage/plugin-analytics-module-ga4": "0.1.8", + "@backstage/plugin-analytics-module-newrelic-browser": "0.0.6", + "@backstage/plugin-apache-airflow": "0.2.19", + "@backstage/plugin-api-docs": "0.10.3", "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.5", - "@backstage/plugin-apollo-explorer": "0.1.17", - "@backstage/plugin-app-backend": "0.3.55", - "@backstage/plugin-app-node": "0.1.7", - "@backstage/plugin-auth-backend": "0.20.0", - "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.2.1", - "@backstage/plugin-auth-backend-module-github-provider": "0.1.4", - "@backstage/plugin-auth-backend-module-gitlab-provider": "0.1.4", - "@backstage/plugin-auth-backend-module-google-provider": "0.1.4", - "@backstage/plugin-auth-backend-module-microsoft-provider": "0.1.2", - "@backstage/plugin-auth-backend-module-oauth2-provider": "0.1.4", - "@backstage/plugin-auth-backend-module-pinniped-provider": "0.1.1", - "@backstage/plugin-auth-node": "0.4.1", - "@backstage/plugin-azure-devops": "0.3.8", - "@backstage/plugin-azure-devops-backend": "0.4.4", - "@backstage/plugin-azure-devops-common": "0.3.1", - "@backstage/plugin-azure-sites": "0.1.15", - "@backstage/plugin-azure-sites-backend": "0.1.17", + "@backstage/plugin-apollo-explorer": "0.1.19", + "@backstage/plugin-app-backend": "0.3.57", + "@backstage/plugin-app-node": "0.1.9", + "@backstage/plugin-app-visualizer": "0.1.0", + "@backstage/plugin-auth-backend": "0.20.3", + "@backstage/plugin-auth-backend-module-atlassian-provider": "0.1.1", + "@backstage/plugin-auth-backend-module-gcp-iap-provider": "0.2.3", + "@backstage/plugin-auth-backend-module-github-provider": "0.1.6", + "@backstage/plugin-auth-backend-module-gitlab-provider": "0.1.6", + "@backstage/plugin-auth-backend-module-google-provider": "0.1.6", + "@backstage/plugin-auth-backend-module-microsoft-provider": "0.1.4", + "@backstage/plugin-auth-backend-module-oauth2-provider": "0.1.6", + "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "0.1.1", + "@backstage/plugin-auth-backend-module-okta-provider": "0.0.2", + "@backstage/plugin-auth-backend-module-pinniped-provider": "0.1.3", + "@backstage/plugin-auth-backend-module-vmware-cloud-provider": "0.1.1", + "@backstage/plugin-auth-node": "0.4.3", + "@backstage/plugin-azure-devops": "0.3.11", + "@backstage/plugin-azure-devops-backend": "0.5.1", + "@backstage/plugin-azure-devops-common": "0.3.2", + "@backstage/plugin-azure-sites": "0.1.18", + "@backstage/plugin-azure-sites-backend": "0.1.19", "@backstage/plugin-azure-sites-common": "0.1.1", - "@backstage/plugin-badges": "0.2.50", - "@backstage/plugin-badges-backend": "0.3.4", - "@backstage/plugin-bazaar": "0.2.18", - "@backstage/plugin-bazaar-backend": "0.3.5", - "@backstage/plugin-bitbucket-cloud-common": "0.2.14", - "@backstage/plugin-bitrise": "0.1.53", - "@backstage/plugin-catalog": "1.15.0", - "@backstage/plugin-catalog-backend": "1.15.0", - "@backstage/plugin-catalog-backend-module-aws": "0.3.1", - "@backstage/plugin-catalog-backend-module-azure": "0.1.26", - "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.1.0", - "@backstage/plugin-catalog-backend-module-bitbucket": "0.2.22", - "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.1.22", - "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.20", - "@backstage/plugin-catalog-backend-module-gcp": "0.1.7", - "@backstage/plugin-catalog-backend-module-gerrit": "0.1.23", - "@backstage/plugin-catalog-backend-module-github": "0.4.5", - "@backstage/plugin-catalog-backend-module-github-org": "0.1.1", - "@backstage/plugin-catalog-backend-module-gitlab": "0.3.4", - "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.4.11", - "@backstage/plugin-catalog-backend-module-ldap": "0.5.22", - "@backstage/plugin-catalog-backend-module-msgraph": "0.5.14", - "@backstage/plugin-catalog-backend-module-openapi": "0.1.24", - "@backstage/plugin-catalog-backend-module-puppetdb": "0.1.12", - "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.1.4", - "@backstage/plugin-catalog-backend-module-unprocessed": "0.3.4", - "@backstage/plugin-catalog-common": "1.0.18", - "@backstage/plugin-catalog-graph": "0.3.0", - "@backstage/plugin-catalog-import": "0.10.2", - "@backstage/plugin-catalog-node": "1.5.0", - "@backstage/plugin-catalog-react": "1.9.0", - "@backstage/plugin-catalog-unprocessed-entities": "0.1.5", - "@backstage/plugin-cicd-statistics": "0.1.28", - "@backstage/plugin-cicd-statistics-module-gitlab": "0.1.22", - "@backstage/plugin-circleci": "0.3.26", - "@backstage/plugin-cloudbuild": "0.3.26", - "@backstage/plugin-code-climate": "0.1.26", - "@backstage/plugin-code-coverage": "0.2.19", - "@backstage/plugin-code-coverage-backend": "0.2.21", - "@backstage/plugin-codescene": "0.1.19", - "@backstage/plugin-config-schema": "0.1.47", - "@backstage/plugin-cost-insights": "0.12.15", + "@backstage/plugin-badges": "0.2.53", + "@backstage/plugin-badges-backend": "0.3.6", + "@backstage/plugin-bazaar": "0.2.21", + "@backstage/plugin-bazaar-backend": "0.3.7", + "@backstage/plugin-bitbucket-cloud-common": "0.2.15", + "@backstage/plugin-bitrise": "0.1.56", + "@backstage/plugin-catalog": "1.16.1", + "@backstage/plugin-catalog-backend": "1.16.1", + "@backstage/plugin-catalog-backend-module-aws": "0.3.3", + "@backstage/plugin-catalog-backend-module-azure": "0.1.28", + "@backstage/plugin-catalog-backend-module-backstage-openapi": "0.1.2", + "@backstage/plugin-catalog-backend-module-bitbucket": "0.2.24", + "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.1.24", + "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.22", + "@backstage/plugin-catalog-backend-module-gcp": "0.1.9", + "@backstage/plugin-catalog-backend-module-gerrit": "0.1.25", + "@backstage/plugin-catalog-backend-module-github": "0.4.7", + "@backstage/plugin-catalog-backend-module-github-org": "0.1.3", + "@backstage/plugin-catalog-backend-module-gitlab": "0.3.6", + "@backstage/plugin-catalog-backend-module-incremental-ingestion": "0.4.13", + "@backstage/plugin-catalog-backend-module-ldap": "0.5.24", + "@backstage/plugin-catalog-backend-module-msgraph": "0.5.16", + "@backstage/plugin-catalog-backend-module-openapi": "0.1.26", + "@backstage/plugin-catalog-backend-module-puppetdb": "0.1.14", + "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "0.1.6", + "@backstage/plugin-catalog-backend-module-unprocessed": "0.3.6", + "@backstage/plugin-catalog-common": "1.0.20", + "@backstage/plugin-catalog-graph": "0.3.3", + "@backstage/plugin-catalog-import": "0.10.5", + "@backstage/plugin-catalog-node": "1.6.1", + "@backstage/plugin-catalog-react": "1.9.3", + "@backstage/plugin-catalog-unprocessed-entities": "0.1.7", + "@backstage/plugin-cicd-statistics": "0.1.31", + "@backstage/plugin-cicd-statistics-module-gitlab": "0.1.25", + "@backstage/plugin-circleci": "0.3.29", + "@backstage/plugin-cloudbuild": "0.3.29", + "@backstage/plugin-code-climate": "0.1.29", + "@backstage/plugin-code-coverage": "0.2.22", + "@backstage/plugin-code-coverage-backend": "0.2.23", + "@backstage/plugin-codescene": "0.1.21", + "@backstage/plugin-config-schema": "0.1.49", + "@backstage/plugin-cost-insights": "0.12.18", "@backstage/plugin-cost-insights-common": "0.1.2", - "@backstage/plugin-devtools": "0.1.6", - "@backstage/plugin-devtools-backend": "0.2.4", - "@backstage/plugin-devtools-common": "0.1.6", - "@backstage/plugin-dynatrace": "8.0.0", - "@backstage/plugin-entity-feedback": "0.2.9", - "@backstage/plugin-entity-feedback-backend": "0.2.4", + "@backstage/plugin-devtools": "0.1.8", + "@backstage/plugin-devtools-backend": "0.2.6", + "@backstage/plugin-devtools-common": "0.1.8", + "@backstage/plugin-dynatrace": "8.0.3", + "@backstage/plugin-entity-feedback": "0.2.12", + "@backstage/plugin-entity-feedback-backend": "0.2.6", "@backstage/plugin-entity-feedback-common": "0.1.3", - "@backstage/plugin-entity-validation": "0.1.11", - "@backstage/plugin-events-backend": "0.2.16", - "@backstage/plugin-events-backend-module-aws-sqs": "0.2.10", - "@backstage/plugin-events-backend-module-azure": "0.1.17", - "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.1.17", - "@backstage/plugin-events-backend-module-gerrit": "0.1.17", - "@backstage/plugin-events-backend-module-github": "0.1.17", - "@backstage/plugin-events-backend-module-gitlab": "0.1.17", - "@backstage/plugin-events-backend-test-utils": "0.1.17", - "@backstage/plugin-events-node": "0.2.16", - "@internal/plugin-todo-list": "1.0.19", - "@internal/plugin-todo-list-backend": "1.0.19", - "@internal/plugin-todo-list-common": "1.0.15", - "@backstage/plugin-explore": "0.4.12", - "@backstage/plugin-explore-backend": "0.0.17", + "@backstage/plugin-entity-validation": "0.1.14", + "@backstage/plugin-events-backend": "0.2.18", + "@backstage/plugin-events-backend-module-aws-sqs": "0.2.12", + "@backstage/plugin-events-backend-module-azure": "0.1.19", + "@backstage/plugin-events-backend-module-bitbucket-cloud": "0.1.19", + "@backstage/plugin-events-backend-module-gerrit": "0.1.19", + "@backstage/plugin-events-backend-module-github": "0.1.19", + "@backstage/plugin-events-backend-module-gitlab": "0.1.19", + "@backstage/plugin-events-backend-test-utils": "0.1.19", + "@backstage/plugin-events-node": "0.2.18", + "@internal/plugin-todo-list": "1.0.21", + "@internal/plugin-todo-list-backend": "1.0.21", + "@internal/plugin-todo-list-common": "1.0.17", + "@backstage/plugin-explore": "0.4.15", + "@backstage/plugin-explore-backend": "0.0.19", "@backstage/plugin-explore-common": "0.0.2", - "@backstage/plugin-explore-react": "0.0.33", - "@backstage/plugin-firehydrant": "0.2.10", - "@backstage/plugin-fossa": "0.2.58", - "@backstage/plugin-gcalendar": "0.3.20", - "@backstage/plugin-gcp-projects": "0.3.43", - "@backstage/plugin-git-release-manager": "0.3.39", - "@backstage/plugin-github-actions": "0.6.7", - "@backstage/plugin-github-deployments": "0.1.57", - "@backstage/plugin-github-issues": "0.2.15", - "@backstage/plugin-github-pull-requests-board": "0.1.20", - "@backstage/plugin-gitops-profiles": "0.3.42", - "@backstage/plugin-gocd": "0.1.32", - "@backstage/plugin-graphiql": "0.3.0", - "@backstage/plugin-graphql-voyager": "0.1.9", - "@backstage/plugin-home": "0.5.10", - "@backstage/plugin-home-react": "0.1.5", - "@backstage/plugin-ilert": "0.2.15", - "@backstage/plugin-jenkins": "0.9.1", - "@backstage/plugin-jenkins-backend": "0.3.1", - "@backstage/plugin-jenkins-common": "0.1.21", - "@backstage/plugin-kafka": "0.3.26", - "@backstage/plugin-kafka-backend": "0.3.5", - "@backstage/plugin-kubernetes": "0.11.1", - "@backstage/plugin-kubernetes-backend": "0.13.1", - "@backstage/plugin-kubernetes-cluster": "0.0.2", - "@backstage/plugin-kubernetes-common": "0.7.1", - "@backstage/plugin-kubernetes-node": "0.1.1", - "@backstage/plugin-kubernetes-react": "0.1.1", - "@backstage/plugin-lighthouse": "0.4.11", - "@backstage/plugin-lighthouse-backend": "0.3.4", + "@backstage/plugin-explore-react": "0.0.35", + "@backstage/plugin-firehydrant": "0.2.13", + "@backstage/plugin-fossa": "0.2.61", + "@backstage/plugin-gcalendar": "0.3.22", + "@backstage/plugin-gcp-projects": "0.3.45", + "@backstage/plugin-git-release-manager": "0.3.41", + "@backstage/plugin-github-actions": "0.6.10", + "@backstage/plugin-github-deployments": "0.1.60", + "@backstage/plugin-github-issues": "0.2.18", + "@backstage/plugin-github-pull-requests-board": "0.1.23", + "@backstage/plugin-gitops-profiles": "0.3.44", + "@backstage/plugin-gocd": "0.1.35", + "@backstage/plugin-graphiql": "0.3.2", + "@backstage/plugin-graphql-voyager": "0.1.11", + "@backstage/plugin-home": "0.6.1", + "@backstage/plugin-home-react": "0.1.7", + "@backstage/plugin-ilert": "0.2.18", + "@backstage/plugin-jenkins": "0.9.4", + "@backstage/plugin-jenkins-backend": "0.3.3", + "@backstage/plugin-jenkins-common": "0.1.23", + "@backstage/plugin-kafka": "0.3.29", + "@backstage/plugin-kafka-backend": "0.3.7", + "@backstage/plugin-kubernetes": "0.11.4", + "@backstage/plugin-kubernetes-backend": "0.14.1", + "@backstage/plugin-kubernetes-cluster": "0.0.5", + "@backstage/plugin-kubernetes-common": "0.7.3", + "@backstage/plugin-kubernetes-node": "0.1.3", + "@backstage/plugin-kubernetes-react": "0.2.1", + "@backstage/plugin-lighthouse": "0.4.14", + "@backstage/plugin-lighthouse-backend": "0.4.1", "@backstage/plugin-lighthouse-common": "0.1.4", - "@backstage/plugin-linguist": "0.1.11", - "@backstage/plugin-linguist-backend": "0.5.4", + "@backstage/plugin-linguist": "0.1.14", + "@backstage/plugin-linguist-backend": "0.5.6", "@backstage/plugin-linguist-common": "0.1.2", - "@backstage/plugin-microsoft-calendar": "0.1.9", - "@backstage/plugin-newrelic": "0.3.42", - "@backstage/plugin-newrelic-dashboard": "0.3.1", - "@backstage/plugin-nomad": "0.1.7", - "@backstage/plugin-nomad-backend": "0.1.9", - "@backstage/plugin-octopus-deploy": "0.2.8", - "@backstage/plugin-opencost": "0.2.2", - "@backstage/plugin-org": "0.6.16", - "@backstage/plugin-org-react": "0.1.15", - "@backstage/plugin-pagerduty": "0.6.7", - "@backstage/plugin-periskop": "0.1.24", - "@backstage/plugin-periskop-backend": "0.2.5", - "@backstage/plugin-permission-backend": "0.5.30", - "@backstage/plugin-permission-backend-module-allow-all-policy": "0.1.4", - "@backstage/plugin-permission-common": "0.7.10", - "@backstage/plugin-permission-node": "0.7.18", - "@backstage/plugin-permission-react": "0.4.17", - "@backstage/plugin-playlist": "0.2.0", - "@backstage/plugin-playlist-backend": "0.3.11", - "@backstage/plugin-playlist-common": "0.1.12", - "@backstage/plugin-proxy-backend": "0.4.5", - "@backstage/plugin-puppetdb": "0.1.9", - "@backstage/plugin-rollbar": "0.4.26", - "@backstage/plugin-rollbar-backend": "0.1.52", - "@backstage/plugin-scaffolder": "1.16.0", - "@backstage/plugin-scaffolder-backend": "1.19.0", - "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.2.8", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.31", - "@backstage/plugin-scaffolder-backend-module-gitlab": "0.2.10", - "@backstage/plugin-scaffolder-backend-module-rails": "0.4.24", - "@backstage/plugin-scaffolder-backend-module-sentry": "0.1.15", - "@backstage/plugin-scaffolder-backend-module-yeoman": "0.2.28", - "@backstage/plugin-scaffolder-common": "1.4.3", - "@backstage/plugin-scaffolder-node": "0.2.8", - "@backstage/plugin-scaffolder-react": "1.6.0", - "@backstage/plugin-search": "1.4.2", - "@backstage/plugin-search-backend": "1.4.7", - "@backstage/plugin-search-backend-module-catalog": "0.1.11", - "@backstage/plugin-search-backend-module-elasticsearch": "1.3.10", - "@backstage/plugin-search-backend-module-explore": "0.1.11", - "@backstage/plugin-search-backend-module-pg": "0.5.16", - "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.1.0", - "@backstage/plugin-search-backend-module-techdocs": "0.1.11", - "@backstage/plugin-search-backend-node": "1.2.11", - "@backstage/plugin-search-common": "1.2.8", - "@backstage/plugin-search-react": "1.7.2", - "@backstage/plugin-sentry": "0.5.11", - "@backstage/plugin-shortcuts": "0.3.16", - "@backstage/plugin-sonarqube": "0.7.8", - "@backstage/plugin-sonarqube-backend": "0.2.9", - "@backstage/plugin-sonarqube-react": "0.1.10", - "@backstage/plugin-splunk-on-call": "0.4.15", - "@backstage/plugin-stack-overflow": "0.1.22", - "@backstage/plugin-stack-overflow-backend": "0.2.11", - "@backstage/plugin-stackstorm": "0.1.8", - "@backstage/plugin-tech-insights": "0.3.18", - "@backstage/plugin-tech-insights-backend": "0.5.21", - "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.39", + "@backstage/plugin-microsoft-calendar": "0.1.11", + "@backstage/plugin-newrelic": "0.3.44", + "@backstage/plugin-newrelic-dashboard": "0.3.4", + "@backstage/plugin-nomad": "0.1.10", + "@backstage/plugin-nomad-backend": "0.1.11", + "@backstage/plugin-octopus-deploy": "0.2.11", + "@backstage/plugin-opencost": "0.2.4", + "@backstage/plugin-org": "0.6.19", + "@backstage/plugin-org-react": "0.1.18", + "@backstage/plugin-pagerduty": "0.7.1", + "@backstage/plugin-periskop": "0.1.27", + "@backstage/plugin-periskop-backend": "0.2.7", + "@backstage/plugin-permission-backend": "0.5.32", + "@backstage/plugin-permission-backend-module-allow-all-policy": "0.1.6", + "@backstage/plugin-permission-common": "0.7.12", + "@backstage/plugin-permission-node": "0.7.20", + "@backstage/plugin-permission-react": "0.4.19", + "@backstage/plugin-playlist": "0.2.3", + "@backstage/plugin-playlist-backend": "0.3.13", + "@backstage/plugin-playlist-common": "0.1.14", + "@backstage/plugin-proxy-backend": "0.4.7", + "@backstage/plugin-puppetdb": "0.1.12", + "@backstage/plugin-rollbar": "0.4.29", + "@backstage/plugin-rollbar-backend": "0.1.54", + "@backstage/plugin-scaffolder": "1.17.1", + "@backstage/plugin-scaffolder-backend": "1.20.0", + "@backstage/plugin-scaffolder-backend-module-azure": "0.1.1", + "@backstage/plugin-scaffolder-backend-module-bitbucket": "0.1.1", + "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "0.2.10", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.33", + "@backstage/plugin-scaffolder-backend-module-gerrit": "0.1.1", + "@backstage/plugin-scaffolder-backend-module-github": "0.1.1", + "@backstage/plugin-scaffolder-backend-module-gitlab": "0.2.12", + "@backstage/plugin-scaffolder-backend-module-rails": "0.4.26", + "@backstage/plugin-scaffolder-backend-module-sentry": "0.1.17", + "@backstage/plugin-scaffolder-backend-module-yeoman": "0.2.30", + "@backstage/plugin-scaffolder-common": "1.4.5", + "@backstage/plugin-scaffolder-node": "0.2.10", + "@backstage/plugin-scaffolder-react": "1.7.1", + "@backstage/plugin-search": "1.4.5", + "@backstage/plugin-search-backend": "1.4.9", + "@backstage/plugin-search-backend-module-catalog": "0.1.13", + "@backstage/plugin-search-backend-module-elasticsearch": "1.3.12", + "@backstage/plugin-search-backend-module-explore": "0.1.13", + "@backstage/plugin-search-backend-module-pg": "0.5.18", + "@backstage/plugin-search-backend-module-stack-overflow-collator": "0.1.2", + "@backstage/plugin-search-backend-module-techdocs": "0.1.13", + "@backstage/plugin-search-backend-node": "1.2.13", + "@backstage/plugin-search-common": "1.2.10", + "@backstage/plugin-search-react": "1.7.5", + "@backstage/plugin-sentry": "0.5.14", + "@backstage/plugin-shortcuts": "0.3.18", + "@backstage/plugin-sonarqube": "0.7.11", + "@backstage/plugin-sonarqube-backend": "0.2.11", + "@backstage/plugin-sonarqube-react": "0.1.12", + "@backstage/plugin-splunk-on-call": "0.4.18", + "@backstage/plugin-stack-overflow": "0.1.24", + "@backstage/plugin-stack-overflow-backend": "0.2.13", + "@backstage/plugin-stackstorm": "0.1.10", + "@backstage/plugin-tech-insights": "0.3.21", + "@backstage/plugin-tech-insights-backend": "0.5.23", + "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.41", "@backstage/plugin-tech-insights-common": "0.2.12", - "@backstage/plugin-tech-insights-node": "0.4.13", - "@backstage/plugin-tech-radar": "0.6.10", - "@backstage/plugin-techdocs": "1.9.0", - "@backstage/plugin-techdocs-addons-test-utils": "1.0.23", - "@backstage/plugin-techdocs-backend": "1.9.0", - "@backstage/plugin-techdocs-module-addons-contrib": "1.1.2", - "@backstage/plugin-techdocs-node": "1.10.0", - "@backstage/plugin-techdocs-react": "1.1.13", - "@backstage/plugin-todo": "0.2.30", - "@backstage/plugin-todo-backend": "0.3.5", - "@backstage/plugin-user-settings": "0.7.12", - "@backstage/plugin-user-settings-backend": "0.2.6", - "@backstage/plugin-vault": "0.1.21", - "@backstage/plugin-vault-backend": "0.4.0", - "@backstage/plugin-vault-node": "0.1.0", - "@backstage/plugin-xcmetrics": "0.2.45", - "@backstage/frontend-test-utils": "0.0.0", - "@backstage/plugin-auth-backend-module-atlassian-provider": "0.0.0", - "@backstage/plugin-auth-backend-module-okta-provider": "0.0.0" + "@backstage/plugin-tech-insights-node": "0.4.15", + "@backstage/plugin-tech-radar": "0.6.12", + "@backstage/plugin-techdocs": "1.9.3", + "@backstage/plugin-techdocs-addons-test-utils": "1.0.26", + "@backstage/plugin-techdocs-backend": "1.9.2", + "@backstage/plugin-techdocs-module-addons-contrib": "1.1.4", + "@backstage/plugin-techdocs-node": "1.11.1", + "@backstage/plugin-techdocs-react": "1.1.15", + "@backstage/plugin-todo": "0.2.33", + "@backstage/plugin-todo-backend": "0.3.7", + "@backstage/plugin-user-settings": "0.8.0", + "@backstage/plugin-user-settings-backend": "0.2.8", + "@backstage/plugin-vault": "0.1.24", + "@backstage/plugin-vault-backend": "0.4.2", + "@backstage/plugin-vault-node": "0.1.2", + "@backstage/plugin-xcmetrics": "0.2.47", + "@backstage/plugin-auth-backend-module-oidc-provider": "0.0.0", + "@backstage/plugin-signals": "0.0.0", + "@backstage/plugin-signals-backend": "0.0.0", + "@backstage/plugin-signals-node": "0.0.0", + "@backstage/plugin-signals-react": "0.0.0" }, "changesets": [ - "angry-eagles-end", - "angry-gorillas-unite", - "angry-walls-despacito", - "angry-walls-juggle", - "brave-socks-raise", - "breezy-pans-glow", - "bright-eyes-film", - "chatty-actors-live", - "chilled-terms-breathe", - "cold-pans-crash", - "create-app-1700579510", - "create-app-1700607979", - "curly-walls-relate", - "dry-readers-raise", - "eleven-ants-pretend", - "empty-dingos-grow", - "gentle-lies-greet", - "gold-shirts-yell", - "great-rings-type", - "hot-wolves-flash", - "kind-badgers-rush", - "large-weeks-accept", - "long-wolves-return", - "lovely-terms-search", - "lucky-kids-cough", - "metal-eggs-smile", - "mighty-beans-wink", - "nasty-tips-exercise", - "perfect-apes-sing", - "pink-dryers-repair", - "plenty-numbers-enjoy", - "polite-knives-build", - "poor-pandas-dream", - "proud-flies-travel", - "quick-horses-reply", - "renovate-07b1766", - "renovate-169bdc2", - "renovate-240982c", - "renovate-4891ee7", - "renovate-6c0fb81", - "renovate-9d2d52e", - "renovate-b193efa", - "renovate-c7270d5", - "renovate-ead3ba6", - "renovate-f5b18be", - "silly-numbers-wash", - "sixty-adults-kiss", - "sixty-houses-agree", - "spotty-olives-share", - "tiny-cobras-look", - "tricky-islands-wonder", - "twenty-beans-laugh", - "violet-zebras-thank" + "afraid-numbers-invite", + "angry-phones-arrive", + "brown-items-hammer", + "chilled-chefs-notice", + "chilly-seahorses-bake", + "cold-cooks-care", + "curvy-ladybugs-impress", + "cyan-bats-lick", + "cyan-icons-rest", + "dry-lizards-taste", + "dry-lobsters-flash", + "dull-dolphins-explain", + "dull-fireants-repeat", + "empty-ligers-hang", + "fast-jeans-walk", + "fifty-adults-watch", + "fifty-files-argue", + "flat-wasps-fold", + "forty-cars-scream", + "friendly-cheetahs-rescue", + "funny-timers-visit", + "good-lemons-lick", + "gorgeous-bobcats-press", + "grumpy-poets-study", + "hot-pillows-poke", + "hot-tips-doubt", + "kind-clouds-fly", + "large-frogs-grab", + "large-tables-wonder", + "lemon-cameras-remember", + "long-suns-bow", + "loud-walls-compare", + "lovely-starfishes-remain", + "many-pugs-change", + "metal-elephants-sit", + "metal-students-drive", + "neat-hotels-wink", + "nine-bulldogs-camp", + "nine-olives-swim", + "old-students-smoke", + "olive-singers-accept", + "polite-meals-hug", + "real-eggs-sip", + "renovate-47c1714", + "renovate-4b698fb", + "renovate-5f8d1ca", + "renovate-95b717a", + "renovate-f58dd5c", + "renovate-fb2e0b9", + "rotten-lemons-cry", + "serious-carpets-learn", + "seven-dots-serve", + "seven-plums-return", + "sharp-pandas-hunt", + "shy-carrots-decide", + "silent-hotels-knock", + "silent-poets-grab", + "six-melons-end", + "sixty-shoes-prove", + "soft-beans-tease", + "sour-rivers-fry", + "strange-parents-hammer", + "tame-numbers-smile", + "tame-rockets-sin", + "ten-numbers-happen", + "ten-planets-guess", + "tiny-kiwis-know", + "tough-drinks-scream", + "twenty-taxis-mate", + "two-coats-smile", + "two-singers-learn", + "unlucky-wasps-tan", + "warm-maps-scream", + "wet-emus-work", + "wicked-elephants-scream", + "wild-owls-doubt", + "young-rules-repeat" ] } diff --git a/.changeset/proud-flies-travel.md b/.changeset/proud-flies-travel.md deleted file mode 100644 index 41f40e2e90..0000000000 --- a/.changeset/proud-flies-travel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-compat-api': patch ---- - -Made package public so it can be published diff --git a/.changeset/quick-horses-reply.md b/.changeset/quick-horses-reply.md deleted file mode 100644 index 3469ef1d89..0000000000 --- a/.changeset/quick-horses-reply.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fix creating env secret in github:environment:create action diff --git a/.changeset/rare-seals-thank.md b/.changeset/rare-seals-thank.md new file mode 100644 index 0000000000..345c1c91b4 --- /dev/null +++ b/.changeset/rare-seals-thank.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Added `RouteResolutionsApi` as a replacement for the routing context. diff --git a/.changeset/real-eggs-sip.md b/.changeset/real-eggs-sip.md new file mode 100644 index 0000000000..ca23d792f8 --- /dev/null +++ b/.changeset/real-eggs-sip.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-signals-backend': patch +'@backstage/plugin-signals': patch +'@backstage/plugin-signals-react': patch +'@backstage/plugin-signals-node': patch +--- + +Add support to subscribe and publish messages through signals plugins diff --git a/.changeset/renovate-07b1766.md b/.changeset/renovate-07b1766.md deleted file mode 100644 index 245f827d08..0000000000 --- a/.changeset/renovate-07b1766.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-apollo-explorer': patch ---- - -Updated dependency `@apollo/explorer` to `^3.0.0`. diff --git a/.changeset/renovate-240982c.md b/.changeset/renovate-240982c.md deleted file mode 100644 index f68e583388..0000000000 --- a/.changeset/renovate-240982c.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/plugin-home-react': patch -'@backstage/plugin-home': patch -'@backstage/plugin-scaffolder-react': patch -'@backstage/plugin-scaffolder': patch ---- - -Updated dependency `@rjsf/utils` to `5.14.1`. -Updated dependency `@rjsf/core` to `5.14.1`. -Updated dependency `@rjsf/material-ui` to `5.14.1`. -Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. diff --git a/.changeset/renovate-47c1714.md b/.changeset/renovate-47c1714.md new file mode 100644 index 0000000000..4544d36713 --- /dev/null +++ b/.changeset/renovate-47c1714.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-home-react': patch +'@backstage/plugin-home': patch +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-scaffolder': patch +--- + +Updated dependency `@rjsf/utils` to `5.16.1`. +Updated dependency `@rjsf/core` to `5.16.1`. +Updated dependency `@rjsf/material-ui` to `5.16.1`. +Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. diff --git a/.changeset/renovate-4891ee7.md b/.changeset/renovate-4891ee7.md deleted file mode 100644 index 031e42d111..0000000000 --- a/.changeset/renovate-4891ee7.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/plugin-kubernetes-backend': patch -'@backstage/plugin-kubernetes-common': patch -'@backstage/plugin-kubernetes-react': patch -'@backstage/plugin-kubernetes': patch ---- - -Updated dependency `@kubernetes/client-node` to `0.20.0`. diff --git a/.changeset/renovate-4b698fb.md b/.changeset/renovate-4b698fb.md new file mode 100644 index 0000000000..220aa5c848 --- /dev/null +++ b/.changeset/renovate-4b698fb.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated dependency `vite-plugin-node-polyfills` to `^0.19.0`. diff --git a/.changeset/renovate-5f8d1ca.md b/.changeset/renovate-5f8d1ca.md new file mode 100644 index 0000000000..03f8d4b961 --- /dev/null +++ b/.changeset/renovate-5f8d1ca.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-oidc-provider': patch +--- + +Updated dependency `passport` to `^0.7.0`. diff --git a/.changeset/renovate-6c0fb81.md b/.changeset/renovate-95b717a.md similarity index 57% rename from .changeset/renovate-6c0fb81.md rename to .changeset/renovate-95b717a.md index a4677ed174..59beab8d04 100644 --- a/.changeset/renovate-6c0fb81.md +++ b/.changeset/renovate-95b717a.md @@ -2,4 +2,4 @@ '@backstage/plugin-bitbucket-cloud-common': patch --- -Updated dependency `ts-morph` to `^20.0.0`. +Updated dependency `ts-morph` to `^21.0.0`. diff --git a/.changeset/renovate-9d2d52e.md b/.changeset/renovate-9d2d52e.md deleted file mode 100644 index e7a02f8f41..0000000000 --- a/.changeset/renovate-9d2d52e.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Updated dependency `@typescript-eslint/eslint-plugin` to `6.11.0`. diff --git a/.changeset/renovate-b193efa.md b/.changeset/renovate-b193efa.md deleted file mode 100644 index dfb2430162..0000000000 --- a/.changeset/renovate-b193efa.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Updated dependency `@asyncapi/react-component` to `1.1.0`. diff --git a/.changeset/renovate-c7270d5.md b/.changeset/renovate-c7270d5.md deleted file mode 100644 index e38e9ba816..0000000000 --- a/.changeset/renovate-c7270d5.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-gcp': patch -'@backstage/plugin-kubernetes-backend': patch ---- - -Updated dependency `@google-cloud/container` to `^5.0.0`. diff --git a/.changeset/renovate-ead3ba6.md b/.changeset/renovate-ead3ba6.md deleted file mode 100644 index f62d2cf0ed..0000000000 --- a/.changeset/renovate-ead3ba6.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/plugin-home-react': patch -'@backstage/plugin-home': patch -'@backstage/plugin-scaffolder-react': patch -'@backstage/plugin-scaffolder': patch ---- - -Updated dependency `@rjsf/utils` to `5.14.2`. -Updated dependency `@rjsf/core` to `5.14.2`. -Updated dependency `@rjsf/material-ui` to `5.14.2`. -Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. diff --git a/.changeset/renovate-169bdc2.md b/.changeset/renovate-f58dd5c.md similarity index 51% rename from .changeset/renovate-169bdc2.md rename to .changeset/renovate-f58dd5c.md index 6a54230a28..8a226034ea 100644 --- a/.changeset/renovate-169bdc2.md +++ b/.changeset/renovate-f58dd5c.md @@ -2,4 +2,4 @@ '@backstage/plugin-api-docs': patch --- -Updated dependency `graphiql` to `3.0.10`. +Updated dependency `graphiql` to `3.1.0`. diff --git a/.changeset/renovate-f5b18be.md b/.changeset/renovate-f5b18be.md deleted file mode 100644 index 82bcbd724f..0000000000 --- a/.changeset/renovate-f5b18be.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-azure-sites-backend': patch ---- - -Updated dependency `@azure/arm-appservice` to `^14.0.0`. diff --git a/.changeset/renovate-fb2e0b9.md b/.changeset/renovate-fb2e0b9.md new file mode 100644 index 0000000000..39277aaa2a --- /dev/null +++ b/.changeset/renovate-fb2e0b9.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Updated dependency `@asyncapi/react-component` to `1.2.13`. diff --git a/.changeset/rotten-lemons-cry.md b/.changeset/rotten-lemons-cry.md new file mode 100644 index 0000000000..2bcc4c2ea7 --- /dev/null +++ b/.changeset/rotten-lemons-cry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-microsoft-provider': patch +--- + +Added the possibility to use custom scopes for performing login with Microsoft EntraID. diff --git a/.changeset/serious-carpets-learn.md b/.changeset/serious-carpets-learn.md new file mode 100644 index 0000000000..1f13420976 --- /dev/null +++ b/.changeset/serious-carpets-learn.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +'@backstage/plugin-scaffolder-common': minor +'@backstage/plugin-scaffolder-react': minor +'@backstage/plugin-scaffolder-node': minor +'@backstage/plugin-scaffolder': patch +--- + +Introduced the first version of recoverable tasks. diff --git a/.changeset/seven-dots-serve.md b/.changeset/seven-dots-serve.md new file mode 100644 index 0000000000..80919b5d28 --- /dev/null +++ b/.changeset/seven-dots-serve.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Exported `CatalogTable.defaultColumnsFunc` for defining the columns in `` of some Kinds while using the default columns for the others. diff --git a/.changeset/seven-plums-return.md b/.changeset/seven-plums-return.md new file mode 100644 index 0000000000..71016577b1 --- /dev/null +++ b/.changeset/seven-plums-return.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-common': patch +--- + +Clusters configured with the `aws` authentication strategy can now customize the +`x-k8s-aws-id` header value used to generate tokens. This value can be specified +specified via the `kubernetes.io/x-k8s-aws-id` parameter (in +`metadata.annotations` for clusters in the catalog, or the `authMetadata` block +on clusters in the app-config). This is particularly helpful when a Backstage +instance contains multiple AWS clusters with the same name in different regions +-- using this new parameter, the clusters can be given different logical names +to distinguish them but still use the same ID for the purposes of generating +tokens. diff --git a/.changeset/sharp-pandas-hunt.md b/.changeset/sharp-pandas-hunt.md new file mode 100644 index 0000000000..1c69c02a0b --- /dev/null +++ b/.changeset/sharp-pandas-hunt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-bitrise': patch +--- + +Update README diff --git a/.changeset/shiny-poets-tease.md b/.changeset/shiny-poets-tease.md new file mode 100644 index 0000000000..a991601a88 --- /dev/null +++ b/.changeset/shiny-poets-tease.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-node': patch +--- + +Update to a newer version of `@trendyol-js/openstack-swift-sdk` diff --git a/.changeset/shy-carrots-decide.md b/.changeset/shy-carrots-decide.md new file mode 100644 index 0000000000..5f64f3d4bd --- /dev/null +++ b/.changeset/shy-carrots-decide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-app-backend': patch +--- + +Do not force caching of the Javascript asset that contains the injected config. diff --git a/.changeset/silent-hotels-knock.md b/.changeset/silent-hotels-knock.md new file mode 100644 index 0000000000..7ff5d978d2 --- /dev/null +++ b/.changeset/silent-hotels-knock.md @@ -0,0 +1,25 @@ +--- +'@backstage/frontend-app-api': minor +--- + +**BREAKING**: The `app.routes.bindings` app-config mapping has been simplified. You now only need to specify the plugin ID and route ID on both sides of the mapping. + +Old form: + +```yaml +app: + routes: + bindings: + plugin.catalog.externalRoutes.viewTechDoc: plugin.techdocs.routes.docRoot + plugin.catalog.externalRoutes.createComponent: plugin.catalog-import.routes.importPage +``` + +New form: + +```yaml +app: + routes: + bindings: + catalog.viewTechDoc: techdocs.docRoot + catalog.createComponent: catalog-import.importPage +``` diff --git a/.changeset/silent-poets-grab.md b/.changeset/silent-poets-grab.md new file mode 100644 index 0000000000..0d76e263d2 --- /dev/null +++ b/.changeset/silent-poets-grab.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-app-api': patch +--- + +Improved the error message when data input/output shapes do not match diff --git a/.changeset/silly-numbers-wash.md b/.changeset/silly-numbers-wash.md deleted file mode 100644 index 25df8b12c1..0000000000 --- a/.changeset/silly-numbers-wash.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Added pagination support to `CatalogIndexPage` - -`CatalogIndexPage` now offers an optional pagination feature, designed to accommodate adopters managing extensive catalogs. This new capability allows for better handling of large amounts of data. - -To activate the pagination mode, simply update your `App.tsx` as follows: - -```diff - const routes = ( - - ... -- } /> -+ } /> - ... -``` - -In case you have a custom catalog page and you want to enable pagination, you need to pass the `pagination` prop to `EntityListProvider` instead. diff --git a/.changeset/six-melons-end.md b/.changeset/six-melons-end.md new file mode 100644 index 0000000000..f139e49887 --- /dev/null +++ b/.changeset/six-melons-end.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-react': patch +--- + +Fixed a bug where the logs dialog and any other functionality depending on the proxy endpoint would fail for clusters configured with the OIDC auth provider. diff --git a/.changeset/sixty-adults-kiss.md b/.changeset/sixty-adults-kiss.md deleted file mode 100644 index eb73387308..0000000000 --- a/.changeset/sixty-adults-kiss.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/frontend-app-api': patch ---- - -Added `createSpecializedApp`, which is a synchronous version of `createApp` where config and features already need to be loaded. diff --git a/.changeset/sixty-houses-agree.md b/.changeset/sixty-houses-agree.md deleted file mode 100644 index 1cdd5e7908..0000000000 --- a/.changeset/sixty-houses-agree.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/backend-common': minor -'@backstage/integration': minor ---- - -Implemented `readTree` for Gitea provider to support TechDocs functionality diff --git a/.changeset/sixty-shoes-prove.md b/.changeset/sixty-shoes-prove.md new file mode 100644 index 0000000000..fe9438eeb2 --- /dev/null +++ b/.changeset/sixty-shoes-prove.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-react': minor +--- + +**BREAKING** The pod exec terminal is now disabled by default since there are several scenarios where it is known not to work. It can be re-enabled at your own risk by setting the config parameter `kubernetes.podExecTerminal.enabled` to `true`. diff --git a/.changeset/soft-beans-tease.md b/.changeset/soft-beans-tease.md new file mode 100644 index 0000000000..881900e773 --- /dev/null +++ b/.changeset/soft-beans-tease.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cloudbuild': patch +--- + +Add telemetry HTTP header Google Cloud Platform diff --git a/.changeset/sour-rivers-fry.md b/.changeset/sour-rivers-fry.md new file mode 100644 index 0000000000..1b74850614 --- /dev/null +++ b/.changeset/sour-rivers-fry.md @@ -0,0 +1,9 @@ +--- +'@backstage/create-app': patch +--- + +Updated `packages/app` as well as the root `package.json` type resolutions to use React v18. + +The `@testing-library/*` dependencies have also been updated to the ones compatible with React v18, and the test at `packages/app/src/App.test.tsx` had been updated to use more modern patterns that work better with these new versions. + +For information on how to migrate existing apps to React v18, see the [migration guide](https://backstage.io/docs/tutorials/react18-migration) diff --git a/.changeset/spotty-olives-share.md b/.changeset/spotty-olives-share.md deleted file mode 100644 index 7437b3af00..0000000000 --- a/.changeset/spotty-olives-share.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@techdocs/cli': minor -'@backstage/plugin-techdocs-node': minor -'@backstage/backend-common': patch ---- - -Add command `--runAsDefaultUser` for `@techdocs/cli generate` to bypass running the docker builds as host user for macOS and Linux. diff --git a/.changeset/strange-parents-hammer.md b/.changeset/strange-parents-hammer.md new file mode 100644 index 0000000000..57d5eeb83d --- /dev/null +++ b/.changeset/strange-parents-hammer.md @@ -0,0 +1,5 @@ +--- +'@backstage/test-utils': minor +--- + +Added `components` option to `TestAppOptions`, which will be forwarded as the `components` option to `createApp`. diff --git a/.changeset/tall-tools-compare.md b/.changeset/tall-tools-compare.md new file mode 100644 index 0000000000..f55c8ef5a5 --- /dev/null +++ b/.changeset/tall-tools-compare.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-node': patch +--- + +Introduced `PinnipedHelper` class to enable authentication to Kubernetes clusters through Pinniped diff --git a/.changeset/tame-numbers-smile.md b/.changeset/tame-numbers-smile.md new file mode 100644 index 0000000000..1d675f8c9c --- /dev/null +++ b/.changeset/tame-numbers-smile.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-node': patch +--- + +Enabling authentication to kubernetes clusters with mTLS x509 client certs diff --git a/.changeset/tame-rockets-sin.md b/.changeset/tame-rockets-sin.md new file mode 100644 index 0000000000..30c7289871 --- /dev/null +++ b/.changeset/tame-rockets-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-module-addons-contrib': patch +--- + +Fix position of the ReportIssue component when is displaying at the top of the container. diff --git a/.changeset/ten-numbers-happen.md b/.changeset/ten-numbers-happen.md new file mode 100644 index 0000000000..73397d4568 --- /dev/null +++ b/.changeset/ten-numbers-happen.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +Custom per-cluster auth metadata (mainly for use with custom `AuthenticationStrategy` implementations) can now be specified in the `authMetadata` property of clusters in the app-config. diff --git a/.changeset/ten-planets-guess.md b/.changeset/ten-planets-guess.md new file mode 100644 index 0000000000..c8510dc6b2 --- /dev/null +++ b/.changeset/ten-planets-guess.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': patch +--- + +Fix typo in VisitsStorageApi diff --git a/.changeset/tidy-cooks-mix.md b/.changeset/tidy-cooks-mix.md new file mode 100644 index 0000000000..873de64a45 --- /dev/null +++ b/.changeset/tidy-cooks-mix.md @@ -0,0 +1,32 @@ +--- +'@backstage/plugin-azure-sites-backend': patch +'@backstage/plugin-azure-sites-common': patch +'@backstage/plugin-azure-sites': patch +--- + +Azure Sites `start` and `stop` action is now protected with the Permissions framework. + +The below example describes an action that forbids anyone but the owner of the catalog entity to trigger actions towards a site tied to an entity. + +```typescript + // packages/backend/src/plugins/permission.ts + import { azureSitesActionPermission } from '@backstage/plugin-azure-sites-common'; + ... + class TestPermissionPolicy implements PermissionPolicy { + async handle(request: PolicyQuery, user?: BackstageIdentityResponse): Promise { + if (isPermission(request.permission, azureSitesActionPermission)) { + return createCatalogConditionalDecision( + request.permission, + catalogConditions.isEntityOwner({ + claims: user?.identity.ownershipEntityRefs ?? [], + }), + ); + } + ... + return { + result: AuthorizeResult.ALLOW, + }; + } + ... + } +``` diff --git a/.changeset/tidy-cooks-mixed.md b/.changeset/tidy-cooks-mixed.md new file mode 100644 index 0000000000..a72f1ee0df --- /dev/null +++ b/.changeset/tidy-cooks-mixed.md @@ -0,0 +1,31 @@ +--- +'@backstage/plugin-azure-sites-backend': minor +--- + +**BREAKING**: `catalogApi` and `permissionsApi` are now a requirement to be passed through to the `createRouter` function. + +You can fix the typescript issues by passing through the required dependencies like the below `diff` shows: + +```diff + import { + createRouter, + AzureSitesApi, + } from '@backstage/plugin-azure-sites-backend'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { ++ const catalogClient = new CatalogClient({ ++ discoveryApi: env.discovery, ++ }); + + return await createRouter({ + logger: env.logger, + azureSitesApi: AzureSitesApi.fromConfig(env.config), ++ catalogApi: catalogClient, ++ permissionsApi: env.permissions, + }); + } +``` diff --git a/.changeset/tiny-cobras-look.md b/.changeset/tiny-cobras-look.md deleted file mode 100644 index 1c5acc6c4a..0000000000 --- a/.changeset/tiny-cobras-look.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Expose an `onAuth` handler for `git` actions to provide custom credentials diff --git a/.changeset/tiny-kiwis-know.md b/.changeset/tiny-kiwis-know.md new file mode 100644 index 0000000000..add4736527 --- /dev/null +++ b/.changeset/tiny-kiwis-know.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated dependencies in frontend plugin templates diff --git a/.changeset/tough-drinks-scream.md b/.changeset/tough-drinks-scream.md new file mode 100644 index 0000000000..3d2f154016 --- /dev/null +++ b/.changeset/tough-drinks-scream.md @@ -0,0 +1,12 @@ +--- +'@backstage/plugin-cloudbuild': minor +--- + +Changed build list view to automatically filter builds based on repository name matching component-info's metadata.name. +Added optional `google.com/cloudbuild-repo-name` annotation which allows you to specify a different repository to filter on. +Added optional `google.com/cloudbuild-trigger-name` annotation which allows you to filter based on a trigger name instead of a repo name. +Updated the ReadMe with information about the filtering and some other minor verbiage updates. +Changed `substitutions.BRANCH_NAME` to `substitutions.REF_NAME` so that the Ref field is populated properly. +Added optional `google.com/cloudbuild-location` annotation which allows you to specify the Cloud Build location of your builds. Default is global scope. +Changed build list view to show builds in a specific location if the location annotation is used. +Updated ReadMe with information about the use of the location filtering. diff --git a/.changeset/tricky-islands-wonder.md b/.changeset/tricky-islands-wonder.md deleted file mode 100644 index 5b9c37281b..0000000000 --- a/.changeset/tricky-islands-wonder.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': minor ---- - -The `columns` prop can be an array or a function that returns an array in order to override the default columns of the `CatalogIndexPage`. diff --git a/.changeset/twenty-beans-laugh.md b/.changeset/twenty-beans-laugh.md deleted file mode 100644 index 5dc537d93b..0000000000 --- a/.changeset/twenty-beans-laugh.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Fixed a issue where `CatalogPage` wasn't using the chosen `initiallySelectedFilter` as intended. diff --git a/.changeset/twenty-taxis-mate.md b/.changeset/twenty-taxis-mate.md new file mode 100644 index 0000000000..65ed158e9b --- /dev/null +++ b/.changeset/twenty-taxis-mate.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': patch +--- + +Plugins converted by `convertLegacyApp` now have their `routes` and `externalRoutes` included as well, allowing them to be used to bind external routes in configuration. diff --git a/.changeset/two-coats-smile.md b/.changeset/two-coats-smile.md new file mode 100644 index 0000000000..65abb7a1eb --- /dev/null +++ b/.changeset/two-coats-smile.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated the backend module template to make the module instance the package default export. diff --git a/.changeset/two-singers-learn.md b/.changeset/two-singers-learn.md new file mode 100644 index 0000000000..f5c2a78ab0 --- /dev/null +++ b/.changeset/two-singers-learn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-devtools': patch +--- + +Added alpha support for the New Frontend System (Declarative Integration) diff --git a/.changeset/unlucky-wasps-tan.md b/.changeset/unlucky-wasps-tan.md new file mode 100644 index 0000000000..97813cb416 --- /dev/null +++ b/.changeset/unlucky-wasps-tan.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket': patch +--- + +Enhanced the pull request action to allow for adding new content to the PR as described in this issue #21762 diff --git a/.changeset/violet-zebras-thank.md b/.changeset/violet-zebras-thank.md deleted file mode 100644 index eda6137dc1..0000000000 --- a/.changeset/violet-zebras-thank.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/theme': minor ---- - -Added a global `OverrideComponentNameToClassKeys` for other plugins and packages to populate using module augmentation. This will in turn will provide component style override types for `createUnifiedTheme`. diff --git a/.changeset/warm-maps-scream.md b/.changeset/warm-maps-scream.md new file mode 100644 index 0000000000..3ea5d93b03 --- /dev/null +++ b/.changeset/warm-maps-scream.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-azure-devops': patch +'@backstage/plugin-devtools': patch +'@backstage/plugin-linguist': patch +--- + +Updated imports from named to default imports to help with the Material UI v4 to v5 migration diff --git a/.changeset/wet-emus-work.md b/.changeset/wet-emus-work.md new file mode 100644 index 0000000000..413670a325 --- /dev/null +++ b/.changeset/wet-emus-work.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-compat-api': patch +--- + +collectLegacyRoutes throws in case invalid element is found diff --git a/.changeset/wicked-elephants-scream.md b/.changeset/wicked-elephants-scream.md new file mode 100644 index 0000000000..f002a48ba8 --- /dev/null +++ b/.changeset/wicked-elephants-scream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-azure-devops': patch +--- + +Prefer `dev.azure.com/build-definition` annotation when it is provided, as it is more specific than `dev.azure.com/project-repo`. This can also be used as a filter for mono-repos. diff --git a/.changeset/wild-owls-doubt.md b/.changeset/wild-owls-doubt.md new file mode 100644 index 0000000000..84abffb159 --- /dev/null +++ b/.changeset/wild-owls-doubt.md @@ -0,0 +1,16 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +The built-in module list has been trimmed down when using the new Backend System. Provider specific modules should now be installed with `backend.add` to provide additional actions to the scaffolder. These modules are as follows: + +- `@backstage/plugin-scaffolder-backend-module-github` +- `@backstage/plugin-scaffolder-backend-module-gitlab` +- `@backstage/plugin-scaffolder-backend-module-bitbucket` +- `@backstage/plugin-scaffolder-backend-module-gitea` +- `@backstage/plugin-scaffolder-backend-module-gerrit` +- `@backstage/plugin-scaffolder-backend-module-confluence-to-markdown` +- `@backstage/plugin-scaffolder-backend-module-cookiecutter` +- `@backstage/plugin-scaffolder-backend-module-rails` +- `@backstage/plugin-scaffolder-backend-module-sentry` +- `@backstage/plugin-scaffolder-backend-module-yeoman` diff --git a/.changeset/young-rules-repeat.md b/.changeset/young-rules-repeat.md new file mode 100644 index 0000000000..6fb17f0608 --- /dev/null +++ b/.changeset/young-rules-repeat.md @@ -0,0 +1,5 @@ +--- +'@backstage/eslint-plugin': patch +--- + +Added new `@backstage/no-top-level-material-ui-4-imports` rule that forbids top level imports from Material UI v4 packages diff --git a/.github/labeler.yml b/.github/labeler.yml index add66d7b28..5066ebaf05 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -2,34 +2,37 @@ area:catalog: - plugins/catalog/**/* - plugins/catalog-*/**/* - packages/catalog-*/**/* -area:scaffolder: - - plugins/scaffolder/**/* - - plugins/scaffolder-*/**/* -search: - - plugins/search/**/* - - plugins/search-*/**/* - - packages/search-*/**/* -homepage: - - plugins/home/**/* area:discoverability: # search + home - plugins/search/**/* - plugins/search-*/**/* - packages/search-*/**/* - plugins/home/**/* +area:kubernetes: + - plugins/kubernetes/**/* + - plugins/kubernetes-*/**/* +area:permission: + - plugins/permission-*/**/* +area:scaffolder: + - plugins/scaffolder/**/* + - plugins/scaffolder-*/**/* area:techdocs: - plugins/techdocs/**/* - plugins/techdocs-*/**/* - packages/techdocs-*/**/* -documentation: - - docs/**/* -microsite: - - microsite/**/* -storybook: - - storybook/**/* auth: - - plugins/auth-backend/**/* + - plugins/auth-*/**/* - packages/core-app-api/src/apis/implementations/auth/**/* - packages/core-app-api/src/lib/Auth*/**/* - packages/core-plugin-api/src/apis/definitions/auth.ts -area:permission: - - plugins/permission-*/**/* +documentation: + - docs/**/* +homepage: + - plugins/home/**/* +microsite: + - microsite/**/* +search: + - plugins/search/**/* + - plugins/search-*/**/* + - packages/search-*/**/* +storybook: + - storybook/**/* diff --git a/.github/uffizzi/k8s/manifests/backstage-deploy.yaml b/.github/uffizzi/k8s/manifests/backstage-deploy.yaml index 9bf62e991a..2c1458cfea 100644 --- a/.github/uffizzi/k8s/manifests/backstage-deploy.yaml +++ b/.github/uffizzi/k8s/manifests/backstage-deploy.yaml @@ -35,3 +35,10 @@ spec: value: 'postgres.default.svc.cluster.local' - name: NODE_ENV value: production + resources: + limits: + cpu: 1000m + memory: 384Mi + requests: + cpu: 20m + memory: 160Mi diff --git a/.github/uffizzi/k8s/manifests/pg-deploy.yaml b/.github/uffizzi/k8s/manifests/pg-deploy.yaml index 354acd8e87..950e64283f 100644 --- a/.github/uffizzi/k8s/manifests/pg-deploy.yaml +++ b/.github/uffizzi/k8s/manifests/pg-deploy.yaml @@ -15,15 +15,25 @@ spec: spec: containers: - name: postgres - image: postgres:13.2-alpine + image: postgres:12-alpine imagePullPolicy: 'IfNotPresent' ports: - containerPort: 5432 + env: + - name: PGDATA + value: '/data/pgdata' envFrom: - secretRef: name: postgres-secrets + resources: + limits: + cpu: 1000m + memory: 256Mi + requests: + cpu: 10m + memory: 96Mi volumeMounts: - - mountPath: /var/lib/postgresql + - mountPath: /data name: postgresdb volumes: - name: postgresdb diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt similarity index 98% rename from .github/vale/Vocab/Backstage/accept.txt rename to .github/vale/config/vocabularies/Backstage/accept.txt index 078a0617c9..940e855828 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -312,6 +312,8 @@ rebase rebasing Recharts Redash +renderer +renderers replicasets repo Repo @@ -323,10 +325,12 @@ reusability Reusability roadmaps rollbar +Roboto Rollbar Rollup routable Routable +Rspack rst rsync ruleset @@ -462,4 +466,4 @@ zod Zolotusky zoomable zsh -scrollable \ No newline at end of file +scrollable diff --git a/.github/workflows/automate_stale.yml b/.github/workflows/automate_stale.yml index 6d6fd65c0d..48b329a7b7 100644 --- a/.github/workflows/automate_stale.yml +++ b/.github/workflows/automate_stale.yml @@ -19,7 +19,7 @@ jobs: with: egress-policy: audit - - uses: actions/stale@v8.0.0 + - uses: actions/stale@v9.0.0 id: stale with: stale-issue-message: > diff --git a/.github/workflows/ci-noop.yml b/.github/workflows/ci-noop.yml index 6918db4be5..f7c6c40832 100644 --- a/.github/workflows/ci-noop.yml +++ b/.github/workflows/ci-noop.yml @@ -6,6 +6,7 @@ on: pull_request: paths: - 'microsite/**' + - 'beps/**' permissions: contents: read diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33d89b9c96..fd6e2edbc3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: pull_request: paths-ignore: - 'microsite/**' + - 'beps/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -34,7 +35,7 @@ jobs: - uses: actions/checkout@v4.1.1 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -70,7 +71,7 @@ jobs: - uses: actions/checkout@v4.1.1 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -153,8 +154,8 @@ jobs: name: Test ${{ matrix.node-version }} services: - postgres13: - image: postgres:13 + postgres16: + image: postgres:16 env: POSTGRES_PASSWORD: postgres options: >- @@ -164,8 +165,8 @@ jobs: --health-retries 5 ports: - 5432/tcp - postgres9: - image: postgres:9 + postgres12: + image: postgres:12 env: POSTGRES_PASSWORD: postgres options: >- @@ -201,7 +202,7 @@ jobs: run: git fetch origin master - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -215,11 +216,11 @@ jobs: run: yarn backstage-cli repo lint --since origin/master - name: test changed packages - run: yarn backstage-cli repo test --maxWorkers=2 --workerIdleMemoryLimit=1300M --since origin/master + run: yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --since origin/master env: BACKSTAGE_TEST_DISABLE_DOCKER: 1 - BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres13.ports[5432] }} - BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres9.ports[5432] }} + BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres16.ports[5432] }} + BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres12.ports[5432] }} BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored # We run the test cases before verifying the specs to prevent any failing tests from causing errors. diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index 7fd15426e5..037d4a88d7 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -27,7 +27,7 @@ jobs: ref: v${{ github.event.client_payload.version }} - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -61,7 +61,7 @@ jobs: with: context: './example-app' file: ./example-app/packages/backend/Dockerfile - push: ${{ github.event_name == "repository_dispatch" && github.event.action == "release-published" }} + push: ${{ (github.event_name == "repository_dispatch") && (github.event.action == "release-published") }} platforms: linux/amd64,linux/arm64 tags: | ghcr.io/${{ github.repository_owner }}/backstage:latest diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index d8e5789a09..8343b92a66 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@v4.1.1 - name: use node.js 18.x - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: 18.x registry-url: https://registry.npmjs.org/ # Needed for auth @@ -66,7 +66,7 @@ jobs: run: ls microsite/build && ls microsite/build/storybook - name: Deploy both microsite and storybook to gh-pages - uses: JamesIves/github-pages-deploy-action@a1ea191d508feb8485aceba848389d49f80ca2dc # v4.4.3 + uses: JamesIves/github-pages-deploy-action@65b5dfd4f5bcd3a7403bbc2959c144256167464e # v4.5.0 with: branch: gh-pages folder: microsite/build diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml index 0fe84b4329..33dc17f867 100644 --- a/.github/workflows/deploy_nightly.yml +++ b/.github/workflows/deploy_nightly.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: use node.js 18.x - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version: 18.x registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index 72124aa363..6cd6caecf9 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -17,8 +17,8 @@ jobs: node-version: [18.x, 20.x] services: - postgres13: - image: postgres:13 + postgres16: + image: postgres:16 env: POSTGRES_PASSWORD: postgres options: >- @@ -28,8 +28,8 @@ jobs: --health-retries 5 ports: - 5432/tcp - postgres9: - image: postgres:9 + postgres12: + image: postgres:12 env: POSTGRES_PASSWORD: postgres options: >- @@ -63,7 +63,7 @@ jobs: - uses: actions/checkout@v4.1.1 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -98,7 +98,7 @@ jobs: - name: test (and upload coverage) run: | - yarn backstage-cli repo test --maxWorkers=2 --workerIdleMemoryLimit=1300M --coverage + yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --coverage bash <(curl -s https://codecov.io/bash) # Upload code coverage for some specific flags. Also see .codecov.yml bash <(curl -s https://codecov.io/bash) -f packages/core-app-api/coverage/* -F core-app-api @@ -106,8 +106,8 @@ jobs: bash <(curl -s https://codecov.io/bash) -f packages/core-plugin-api/coverage/* -F core-plugin-api env: BACKSTAGE_TEST_DISABLE_DOCKER: 1 - BACKSTAGE_TEST_DATABASE_POSTGRES13_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres13.ports[5432] }} - BACKSTAGE_TEST_DATABASE_POSTGRES9_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres9.ports[5432] }} + BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres16.ports[5432] }} + BACKSTAGE_TEST_DATABASE_POSTGRES12_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres12.ports[5432] }} BACKSTAGE_TEST_DATABASE_MYSQL8_CONNECTION_STRING: mysql://root:root@localhost:${{ job.services.mysql8.ports[3306] }}/ignored - name: Discord notification @@ -145,7 +145,7 @@ jobs: - uses: actions/checkout@v4.1.1 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index d84c26d6b2..73dd5f8c57 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -66,6 +66,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@66b90a5db151a8042fa97405c6cf843bbe433f7b # v2.22.7 + uses: github/codeql-action/upload-sarif@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index 5296dbc4c3..9b5a04198a 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -20,7 +20,7 @@ jobs: fetch-depth: 0 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index c6547e0f61..1cab7797e6 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -13,7 +13,7 @@ jobs: egress-policy: audit # Setup node & install deps before checkout, keeping install quick - - uses: actions/setup-node@v4.0.0 + - uses: actions/setup-node@v4.0.1 with: node-version: 18.x - name: Install dependencies diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 40253188f6..ec7c00111d 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4.1.1 - name: use node.js 18.x - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: 18.x registry-url: https://registry.npmjs.org/ # Needed for auth @@ -29,7 +29,7 @@ jobs: cache-prefix: ${{ runner.os }}-v18.x - name: Create Snyk report - uses: snyk/actions/node@3e2680e8df93a24b52d119b1305fb7cedc60ceae # master + uses: snyk/actions/node@1d672a455ab3339ef0a0021e1ec809165ee12fad # master continue-on-error: true # Snyk CLI exits with error when vulnerabilities are found with: args: > diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index e27ef32157..245ef401fa 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@v4.1.1 - name: Monitor and Synchronize Snyk Policies - uses: snyk/actions/node@3e2680e8df93a24b52d119b1305fb7cedc60ceae # master + uses: snyk/actions/node@1d672a455ab3339ef0a0021e1ec809165ee12fad # master with: command: monitor args: > @@ -46,7 +46,7 @@ jobs: # Above we run the `monitor` command, this runs the `test` command which is # the one that generates the SARIF report that we can upload to GitHub. - name: Create Snyk report - uses: snyk/actions/node@3e2680e8df93a24b52d119b1305fb7cedc60ceae # master + uses: snyk/actions/node@1d672a455ab3339ef0a0021e1ec809165ee12fad # master continue-on-error: true # To make sure that SARIF upload gets called with: args: > @@ -58,6 +58,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=7168 - name: Upload Snyk report - uses: github/codeql-action/upload-sarif@v2.22.7 + uses: github/codeql-action/upload-sarif@v3.23.1 with: sarif_file: snyk.sarif diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 6bc36c796a..6eac098448 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -7,6 +7,7 @@ on: - 'contrib/**' - 'docs/**' - 'microsite/**' + - 'beps/**' - 'scripts/**' - 'storybook/**' - '**/*.test.*' @@ -33,7 +34,7 @@ jobs: uses: actions/checkout@v4.1.1 - name: setup-node - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: 18.x registry-url: https://registry.npmjs.org/ @@ -56,11 +57,11 @@ jobs: - name: Generate UUID image name id: uuid - run: echo "UUID_TAG_APP=$(uuidgen)" >> $GITHUB_ENV + run: echo "UUID_TAG_APP=backstage-$(uuidgen --time)" >> $GITHUB_ENV - name: Docker metadata id: meta - uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + uses: docker/metadata-action@dbef88086f6cef02e264edb7dbf63250c17cef6c # v5.5.0 with: images: registry.uffizzi.com/${{ env.UUID_TAG_APP }} tags: type=raw,value=60d diff --git a/.github/workflows/uffizzi-preview.yaml b/.github/workflows/uffizzi-preview.yaml index a9cce41fce..47029df5e0 100644 --- a/.github/workflows/uffizzi-preview.yaml +++ b/.github/workflows/uffizzi-preview.yaml @@ -12,16 +12,23 @@ jobs: name: Cache Manifests File runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} + permissions: + # "If you specify the access for any of these scopes, all of those that are not specified are set to none." + # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions + actions: read # Access cache outputs: - manifests-cache-key: ${{ env.MANIFESTS_FILE_HASH }} - git-ref: ${{ env.GIT_REF }} - pr-number: ${{ env.PR_NUMBER }} - action: ${{ env.ACTION }} + manifests-cache-key: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }} + git-ref: ${{ steps.event.outputs.GIT_REF }} + pr-number: ${{ steps.event.outputs.PR_NUMBER }} + action: ${{ steps.event.outputs.ACTION }} steps: - name: Harden Runner uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895 # v2.6.1 with: - egress-policy: audit + disable-sudo: true + egress-policy: block + allowed-endpoints: > + api.github.com:443 - name: 'Download artifacts' # Fetch output (zip archive) from the workflow run that triggered this workflow. @@ -48,59 +55,50 @@ jobs: let fs = require('fs'); fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-spec.zip`, Buffer.from(download.data)); - - name: 'Unzip artifact' - run: unzip preview-spec.zip + - name: 'Accept event from first stage' + run: unzip preview-spec.zip event.json - name: Read Event into ENV + id: event run: | - echo 'EVENT_JSON<> $GITHUB_ENV - cat event.json >> $GITHUB_ENV - echo -e '\nEOF' >> $GITHUB_ENV + echo PR_NUMBER=$(jq '.number | tonumber' < event.json) >> $GITHUB_OUTPUT + echo ACTION=$(jq --raw-output '.action | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_OUTPUT + echo GIT_REF=$(jq --raw-output '.pull_request.head.sha | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_OUTPUT - name: Hash Rendered Manifests File id: hash # If the previous workflow was triggered by a PR close event, we will not have a manifests file artifact. - if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }} + if: ${{ steps.event.outputs.ACTION != 'closed' }} run: | + unzip preview-spec.zip manifests.rendered.yml ls - echo "MANIFESTS_FILE_HASH=$(md5sum manifests.rendered.yml | awk '{ print $1 }')" >> $GITHUB_ENV + echo "MANIFESTS_FILE_HASH=$(md5sum manifests.rendered.yml | awk '{ print $1 }')" >> $GITHUB_OUTPUT - name: Cache Manifests File - if: ${{ fromJSON(env.EVENT_JSON).action != 'closed' }} - uses: actions/cache@v3.3.2 + if: ${{ steps.event.outputs.ACTION != 'closed' }} + uses: actions/cache@v4.0.0 with: path: manifests.rendered.yml - key: ${{ env.MANIFESTS_FILE_HASH }} - - - name: Read PR Number From Event Object - id: pr - run: echo "PR_NUMBER=${{ fromJSON(env.EVENT_JSON).number }}" >> $GITHUB_ENV - - - name: Read Event Type from Event Object - id: action - run: echo "ACTION=${{ fromJSON(env.EVENT_JSON).action }}" >> $GITHUB_ENV - - - name: Read Git Ref From Event Object - id: ref - run: echo "GIT_REF=${{ fromJSON(env.EVENT_JSON).pull_request.head.sha }}" >> $GITHUB_ENV + key: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }} - name: DEBUG - Print Job Outputs if: ${{ runner.debug }} run: | - echo "PR number: ${{ env.PR_NUMBER }}" - echo "Git Ref: ${{ env.GIT_REF }}" - echo "Manifests file hash: ${{ env.MANIFESTS_FILE_HASH }}" + echo "PR number: ${{ steps.event.outputs.PR_NUMBER }}" + echo "Git Ref: ${{ steps.event.outputs.GIT_REF }}" + echo "Action: ${{ steps.event.outputs.ACTION }}" + echo "Manifests file hash: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }}" cat event.json deploy-uffizzi-preview: - permissions: - contents: read - pull-requests: write - id-token: write name: Deploy to Uffizzi Virtual Cluster needs: - cache-manifests-file if: ${{ github.event.workflow_run.conclusion == 'success' && needs.cache-manifests-file.outputs.action != 'closed' }} + permissions: + contents: read + pull-requests: write + id-token: write runs-on: ubuntu-latest steps: - name: Checkout @@ -126,7 +124,7 @@ jobs: body: | ## Uffizzi Ephemeral Environment - Virtual Cluster - :cloud: deploying ... + :cloud: deploying cluster `pr-${{ needs.cache-manifests-file.outputs.pr-number }}` :gear: Updating now by workflow run [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}). @@ -142,8 +140,7 @@ jobs: - name: Fetch cached Manifests File id: cache - # if: ${{ contains(fromJSON('["create", "update"]'), env.UFFIZZI_ACTION) }} - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: manifests.rendered.yml key: ${{ needs.cache-manifests-file.outputs.manifests-cache-key }} diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml index 3a5582792c..1310fc15f4 100644 --- a/.github/workflows/verify_accessibility.yml +++ b/.github/workflows/verify_accessibility.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v4.1.1 - name: Use Node.js 18.x - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: 18.x - name: yarn install diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 2171371ba4..6fbf516c08 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2.22.7 + uses: github/codeql-action/init@v3.23.1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2.22.7 + uses: github/codeql-action/autobuild@v3.23.1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2.22.7 + uses: github/codeql-action/analyze@v3.23.1 diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index f87c8c2d38..d4b83ccefc 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -30,5 +30,6 @@ jobs: with: # This also contains --config=.github/vale/config.ini ... :/ files: '${{ steps.generate.outputs.args }}' + version: latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/verify_e2e-kubernetes.yml b/.github/workflows/verify_e2e-kubernetes.yml index 7e4862001e..db3651cfe1 100644 --- a/.github/workflows/verify_e2e-kubernetes.yml +++ b/.github/workflows/verify_e2e-kubernetes.yml @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@v4.1.1 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/verify_e2e-linux-noop.yml b/.github/workflows/verify_e2e-linux-noop.yml index c1ac6ecf89..19b2508d27 100644 --- a/.github/workflows/verify_e2e-linux-noop.yml +++ b/.github/workflows/verify_e2e-linux-noop.yml @@ -9,6 +9,7 @@ on: - 'contrib/**' - 'docs/**' - 'microsite/**' + - 'beps/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index 89da89889f..3f403b2c91 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -7,6 +7,7 @@ on: - 'contrib/**' - 'docs/**' - 'microsite/**' + - 'beps/**' concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -18,7 +19,7 @@ jobs: services: postgres: - image: postgres:latest + image: postgres:12 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -52,7 +53,7 @@ jobs: git config --global user.name 'GitHub e2e user' - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index 46070cf248..6fd912d67d 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -35,7 +35,7 @@ jobs: egress-policy: audit - uses: actions/checkout@v4.1.1 - - uses: actions/setup-python@v4.7.1 + - uses: actions/setup-python@v5.0.0 with: python-version: '3.9' diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index c0553fce3f..d549ac2a40 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -50,13 +50,13 @@ jobs: git config --global user.name 'GitHub e2e user' - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: setup python - uses: actions/setup-python@v4.7.1 + uses: actions/setup-python@v5.0.0 with: python-version: '3.10' diff --git a/.github/workflows/verify_microsite-noop.yml b/.github/workflows/verify_microsite-noop.yml index ac0e397127..d730c7aba6 100644 --- a/.github/workflows/verify_microsite-noop.yml +++ b/.github/workflows/verify_microsite-noop.yml @@ -7,6 +7,7 @@ on: paths-ignore: - '.github/workflows/verify_microsite.yml' - 'microsite/**' + - 'beps/**' - 'mkdocs.yml' - 'docs/**' diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index aef2bc333a..5c76148b92 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -5,6 +5,7 @@ on: paths: - '.github/workflows/verify_microsite.yml' - 'microsite/**' + - 'beps/**' - 'mkdocs.yml' - 'docs/**' @@ -30,7 +31,7 @@ jobs: - uses: actions/checkout@v4.1.1 - name: use node.js 18.x - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: 18.x diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index a49b8e603e..1c3a5fd619 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -37,7 +37,7 @@ jobs: fetch-depth: 0 # Required to retrieve git history - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -51,7 +51,7 @@ jobs: - run: yarn build-storybook - - uses: chromaui/action@820fe59a0cbddabf98b28657eb99e845b6abbe99 # v1 + - uses: chromaui/action@5f2cdb26c04c0364f5c8ca3fc41627a83d6ffc8a # v10 with: token: ${{ secrets.GITHUB_TOKEN }} # projectToken intentionally shared to allow collaborators to run Chromatic on forks diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index 9834435504..143b22c8b6 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -36,7 +36,7 @@ jobs: - uses: actions/checkout@v4.1.1 - name: use node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4.0.0 + uses: actions/setup-node@v4.0.1 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth @@ -55,7 +55,7 @@ jobs: run: yarn lint:type-deps - name: test - run: yarn backstage-cli repo test --maxWorkers=2 --workerIdleMemoryLimit=1300M + run: yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M env: BACKSTAGE_TEST_DISABLE_DOCKER: 1 diff --git a/.prettierignore b/.prettierignore index ad2bd355ed..c54ee5d821 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,7 +5,7 @@ coverage *.hbs templates api-report.md -*-api-report.md +api-report-*.md cli-report.md plugins/scaffolder-backend/sample-templates .vscode diff --git a/.yarn/patches/mock-fs-npm-5.2.0-5103a7b507.patch b/.yarn/patches/mock-fs-npm-5.2.0-5103a7b507.patch deleted file mode 100644 index ed5555203b..0000000000 --- a/.yarn/patches/mock-fs-npm-5.2.0-5103a7b507.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/lib/binding.js b/lib/binding.js -index a4a3e6cdc197bd0f12fe2d060640e91ac58ab457..ab04ce3115fe93073a3e25008730272b6fa21bd7 100644 ---- a/lib/binding.js -+++ b/lib/binding.js -@@ -165,6 +165,8 @@ function Binding(system) { - this.trackDescriptor(stderr); - } - -+Binding.prototype.readFileSync = path => require('fs').readFileSync(path).toString('utf8'); -+ - /** - * Get the file system underlying this binding. - * @return {FileSystem} The underlying file system. diff --git a/ADOPTERS.md b/ADOPTERS.md index 681285dd78..8f3f93658b 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -262,3 +262,4 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Gynzy](https://gynzy.com) | [Stef Louwers](https://github.com/fhp) | We are building an internal developer portal to get an overview of all our software components. | | [Cielo](https://www.cielo.com.br) | [@Alex Silva](https://github.com/narokwq) | We are using as our Internal Developer Portal, it provides developers with the resources, information, and tools they need to build high-quality applications and adhere to organizational standards and security practices. | | [Giant Swarm](https://giantswarm.io) | [Lucas Weatherhog](https://github.com/weatherhog) | At Giant Swarm we are using Backstage as our internal developer portal. Heavily using the service catalog and the TechDocs plugin. Further looking into adopting functionalities of our Web UI into Backstage to have a single pane of glass for our core business, managing all our components, and helping developers to be more productive. | +| [WD Studios](https://wdstudios.tech) | [Mikael Aboagye](https://github.com/JailbreakPapa) | We are using Backstage as our main internal developer portal for our Programmers and Studio Partners. It's really proven its worth at our studio with on-boarding new programmers! diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 07c2a3d96f..be88ed2249 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -139,7 +139,7 @@ yarn build:api-reports # Build API Reports used for documentation yarn lint # lint packages that have changed since later commit on origin/master yarn lint:all # lint all packages -yarn lind:docs # lint all the Markdown files +yarn lint:docs # lint all the Markdown files yarn lint:type-deps # verify that @types/* dependencies are placed correctly in packages yarn test # test packages that have changed since later commit on origin/master @@ -386,7 +386,7 @@ Here are a few things that can help as you go through the review process: - PRs get automatically assigned so you don't need to ping people, they will be notified and have a process of their own for this - If you are waiting for a review or mid-review and your PR goes stale one of the easiest ways to clear the stale bot is by simply rebasing your PR - There are times where you might run into conflict with the `yarn.lock` during a rebase, to help with that make sure your `master` branch is up to date and then in your branch run `git checkout master yarn.lock` and then run `yarn install`, this will get you a conflict free `yarn.lock` file you can commit -- If Vale finds issues with your documentation but it's a code reference you can fix it by putting backticks (`) around it. Now if it is a special word or maybe a name there are two ways you can fix that by adding it to the list of accepted words in the [accept.txt file](https://github.com/backstage/backstage/blob/master/.github/vale/Vocab/Backstage/accept.txt) and them committing that change +- If Vale finds issues with your documentation but it's a code reference you can fix it by putting backticks (`) around it. Now if it is a special word or maybe a name there are two ways you can fix that by adding it to the list of accepted words in the [accept.txt file](https://github.com/backstage/backstage/blob/master/.github/vale/config/vocabularies/Backstage/accept.txt) and them committing that change ### Merging to Master diff --git a/NOTICE b/NOTICE index 38e5cdef85..fb23d28ebc 100644 --- a/NOTICE +++ b/NOTICE @@ -2,4 +2,6 @@ Backstage Copyright 2020 The Backstage Authors Portions of this software were developed by third-party software vendors: + - Tech Radar Plugin (https://opensource.zalando.com/tech-radar/), Copyright (c) 2017 Zalando SE +- [OpenAPI Generator Templates](./packages/repo-tools/templates), Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) Copyright 2018 SmartBear Software diff --git a/OWNERS.md b/OWNERS.md index b3f9d7bef9..4d6fb21a80 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -78,7 +78,6 @@ Scope: The Permission Framework and plugins integrating with the permission fram | Name | Organization | Team | GitHub | Discord | | -------------------- | ------------ | --------------- | ----------------------------------------------- | ---------------- | | Ainhoa Larumbe | Spotify | Imaginary Goats | [ainhoaL](http://github.com/ainhoaL) | ainhoa#8085 | -| Aramis Sennyey | Spotify | Imaginary Goats | [sennyeya](https://github.com/sennyeya) | Aramis#7984 | | Claire Casey | Spotify | Imaginary Goats | [clairelcasey](http://github.com/clairelcasey) | clairecasey#2710 | | Eric Peterson | Spotify | Imaginary Goats | [iamEAP](http://github.com/iamEAP) | iamEAP#3058 | | Harry Hogg | Spotify | Imaginary Goats | [HHogg](http://github.com/HHogg) | simplex#3451 | @@ -118,7 +117,7 @@ Scope: Tooling for frontend and backend schema-first OpenAPI development. | Name | Organization | GitHub | Discord | | -------------- | ------------ | --------------------------------------- | ------------- | -| Aramis Sennyey | Spotify | [sennyeya](https://github.com/sennyeya) | `Aramis#7984` | +| Aramis Sennyey | | [sennyeya](https://github.com/sennyeya) | `Aramis#7984` | ### Scaffolder @@ -126,10 +125,9 @@ Team: @backstage/scaffolder-maintainers Scope: The Scaffolder frontend and backend plugins, and related tooling. -| Name | Organization | GitHub | Discord | -| ------------------- | -------------- | ------------------------------------- | ---------------- | -| Bogdan Nechyporenko | Bol.com | [acierto](https://github.com/acierto) | `bogdan_haarlem` | -| Paul Cowan | frontendrescue | [dagda1](https://github.com/dagda1) | `dagda1` | +| Name | Organization | GitHub | Discord | +| ------------------- | ------------ | ------------------------------------- | ---------------- | +| Bogdan Nechyporenko | Bol.com | [acierto](https://github.com/acierto) | `bogdan_haarlem` | ## Sponsors @@ -147,7 +145,7 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling. | Alex Crome | | [afscrome](https://github.com/afscrome) | `afscrome` | | Andre Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | | Andrew Thauer | Wealthsimple | [andrewthauer](https://github.com/andrewthauer) | `andrewthauer#3060` | -| Aramis Sennyey | Spotify | [sennyeya](https://github.com/sennyeya) | `Aramis#7984` | +| Aramis Sennyey | | [sennyeya](https://github.com/sennyeya) | `Aramis#7984` | | Brian Fletcher | Roadie.io | [punkle](https://github.com/punkle) | `Brian Fletcher#7051` | | Carlos Esteban Lopez Jaramillo | VMWare | [luchillo17](https://github.com/luchillo17) | `luchillo17#8777` | | David Tuite | Roadie.io | [dtuite](https://github.com/dtuite) | `David Tuite (roadie.io)#1010` | @@ -173,3 +171,9 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling. | Name | Organization | GitHub | Discord | | --------- | ------------ | ------------------------------------------- | -------------- | | Lee Mills | Spotify | [leemills83](https://github.com/leemills83) | `.binarypoint` | + +## Emeritus Project Area Maintainers + +| Maintainer | Organization | GitHub | Discord | +| ---------- | -------------- | ----------------------------------- | -------- | +| Paul Cowan | frontendrescue | [dagda1](https://github.com/dagda1) | `dagda1` | diff --git a/README-ko_kr.md b/README-ko_kr.md new file mode 100644 index 0000000000..0544304cea --- /dev/null +++ b/README-ko_kr.md @@ -0,0 +1,78 @@ +[![headline](docs/assets/headline.png)](https://backstage.io/) + +# [Backstage](https://backstage.io) + +[English](README.md) \| 한국어 + +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![CNCF Status](https://img.shields.io/badge/cncf%20status-incubation-blue.svg)](https://www.cncf.io/projects) +[![Discord](https://img.shields.io/discord/687207715902193673?logo=discord&label=Discord&color=5865F2&logoColor=white)](https://discord.gg/backstage-687207715902193673) +![Code style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg) +[![Codecov](https://img.shields.io/codecov/c/github/backstage/backstage)](https://codecov.io/gh/backstage/backstage) +[![](https://img.shields.io/github/v/release/backstage/backstage)](https://github.com/backstage/backstage/releases) +[![Uffizzi](https://img.shields.io/endpoint?url=https%3A%2F%2Fapp.uffizzi.com%2Fapi%2Fv1%2Fpublic%2Fshields%2Fgithub.com%2Fbackstage%2Fbackstage)](https://app.uffizzi.com/ephemeral-environments/backstage/backstage) +[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7678/badge)](https://bestpractices.coreinfrastructure.org/projects/7678) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/backstage/backstage/badge)](https://securityscorecards.dev/viewer/?uri=github.com/backstage/backstage) + +## 백스테이지(Backstage)란? + +[백스테이지(Backstage)](https://backstage.io/) 는 개발자 포털 구출을 위한 개방형 플랫폼입니다. 중앙 집중식 소프트웨어 카탈로그를 기반으로하는 Backstage는 마이크로 서비스와 인프라의 질서를 복원하고 제품팀이 자율성을 훼손하지 않고 고품질 코드를 신속하게 출시할 수 있도록 지원합니다. + +Backstage 는 모든 인프라 도구, 서비스 및 문서를 통합하여 처음부터 끝까지 간소화된 개발 환경을 만듭니다. + +![software-catalog](docs/assets/header.png) + +Backstage는 다음을 포함합니다: + +- [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/) 마이크로 서비스, 라이브러리, 데이터 파이프라인, 웹 사이트, ML 모델 등 모든 소프트웨어 관리 +- [Backstage Software Templates](https://backstage.io/docs/features/software-templates/) 새로운 프로젝트를 신속하게 시작하고 조직의 모범 사례에따라 도구를 표준화 +- [Backstage TechDocs](https://backstage.io/docs/features/techdocs/) "docs like code" 접근 방식을 사용하여 기술 문서를 쉽게 작성, 유지 관리, 검색 및 사용 +- [open source plugins](https://github.com/backstage/backstage/tree/master/plugins) Backstage의 사용자 정의 가능성과 기능을 확장 + +Backstage는 Spotify에서 제작되었지만 현재는 [Cloud Native Computing Foundation(CNCF)](https://www.cncf.io)에서 인큐베이션 수준 프로젝트로 호스팅되고 있습니다. 추가적인 정보는 [announcement](https://backstage.io/blog/2022/03/16/backstage-turns-two#out-of-the-sandbox-and-into-incubation)를 참조하세요. + +## 프로젝트 로드맵 + +제공된 마일스톤을 포함한 자세한 프로젝트 로드맵에 대한 자세한 내용은 [the Roadmap](https://backstage.io/docs/overview/roadmap)을 참조하세요. + +## 시작하기 + +Backstage를 시작하기위해 [Getting Started documentation](https://backstage.io/docs/getting-started)를 참조하세요. + +## 문서 + +Backstage의 문서는 다음을 포함합니다: + +- [Main documentation](https://backstage.io/docs) +- [Software Catalog](https://backstage.io/docs/features/software-catalog/) +- [Architecture](https://backstage.io/docs/overview/architecture-overview) ([Decisions](https://backstage.io/docs/architecture-decisions/)) +- [Designing for Backstage](https://backstage.io/docs/dls/design) +- [Storybook - UI components](https://backstage.io/storybook) + +## 커뮤니티 + +커뮤니티에 참여하려면 다음 리소스를 사용하세요: + +- [Discord chatroom](https://discord.gg/backstage-687207715902193673) - 지원 및 프로젝트 토론 +- [Contributing to Backstage](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) - 프로젝트 기여 +- [RFCs](https://github.com/backstage/backstage/labels/rfc) - 기술 방향을 정하는 데 도움을 주세요. +- [FAQ](https://backstage.io/docs/FAQ) - 자주 묻는 질문들 +- [Code of Conduct](CODE_OF_CONDUCT.md) - 커뮤니티 운영 방식 +- [Adopters](ADOPTERS.md) - Backstage를 사용하고 있는 기업 +- [Blog](https://backstage.io/blog/) - 공지사항 및 업데이트 +- [Newsletter](https://spoti.fi/backstagenewsletter) - 이메일 뉴스레터 구독 +- [Backstage Community Sessions](https://github.com/backstage/community) - 월간 모임 참여 및 커뮤니티 톺아보기 +- Backstage를 사용중이거나 흥미로운 프로젝트라고 생각하신다면 별표를 눌러주세요. 별표는 사랑입니다 ❤️ + +## License + +Copyright 2020-2023 © The Backstage Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage + +Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 + +## 보안 + +민감한 보안문제는 Github가 아닌 Spotify의 [bug-bounty program](https://hackerone.com/spotify) 통해 신고해주세요 +Please report sensitive security issues using Spotify's [bug-bounty program](https://hackerone.com/spotify) rather than GitHub. + +자세한 내용은 [security release process](SECURITY.md)를 참고하세요. diff --git a/README-zh_Hans.md b/README-zh_Hans.md new file mode 100644 index 0000000000..b1e5a16add --- /dev/null +++ b/README-zh_Hans.md @@ -0,0 +1,77 @@ +[![headline](docs/assets/headline.png)](https://backstage.io/) + +# [Backstage](https://backstage.io) + +简体中文\| [English](README.md) \| [한국어](README-ko_kr.md) + +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![CNCF Status](https://img.shields.io/badge/cncf%20status-incubation-blue.svg)](https://www.cncf.io/projects) +[![Discord](https://img.shields.io/discord/687207715902193673?logo=discord&label=Discord&color=5865F2&logoColor=white)](https://discord.gg/backstage-687207715902193673) +![Code style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg) +[![Codecov](https://img.shields.io/codecov/c/github/backstage/backstage)](https://codecov.io/gh/backstage/backstage) +[![](https://img.shields.io/github/v/release/backstage/backstage)](https://github.com/backstage/backstage/releases) +[![Uffizzi](https://img.shields.io/endpoint?url=https%3A%2F%2Fapp.uffizzi.com%2Fapi%2Fv1%2Fpublic%2Fshields%2Fgithub.com%2Fbackstage%2Fbackstage)](https://app.uffizzi.com/ephemeral-environments/backstage/backstage) +[![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7678/badge)](https://bestpractices.coreinfrastructure.org/projects/7678) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/backstage/backstage/badge)](https://securityscorecards.dev/viewer/?uri=github.com/backstage/backstage) + +## Backstage 是什么? + +[Backstage](https://backstage.io/) 是一个用于构建开发者门户的开放平台。在集中式软件目录的支持下,Backstage 可以恢复微服务和基础设施的秩序,并使您的产品团队能够快速交付高质量的代码,而不会影响自主权。 + +Backstage 统一了所有基础设施工具、服务和文档,以创建端到端的流线型开发环境。 + +![software-catalog](docs/assets/header.png) + +开箱即用的 Backstage 包括: + +- [Backstage 软件目录](https://backstage.io/docs/features/software-catalog/) 用于管理所有软件,例如微服务、类库、数据管道、网站和机器学习模型 +- [Backstage 软件模板](https://backstage.io/docs/features/software-templates/) 用于快速启动新项目,并根据组织的最佳实践实现工具标准化 +- [Backstage 技术文档](https://backstage.io/docs/features/techdocs/) 采用 "像编写代码一样编写文档 "的方法,轻松创建、维护、查找和使用技术文档 +- 此外,[开源插件](https://github.com/backstage/backstage/tree/master/plugins) 生态系统不断发展壮大,进一步扩展了 Backstage 的可定制性和功能性 + +Backstage 由 Spotify 创建,但现在由 [云原生计算基金会 (CNCF)](https://www.cncf.io) 作为孵化级项目托管。更多信息请参见[公告](https://backstage.io/blog/2022/03/16/backstage-turns-two#out-of-the-sandbox-and-into-incubation)。 + +## 项目路线图 + +有关项目路线图(包括已交付的里程碑)的详细信息,请参阅[路线图](https://backstage.io/docs/overview/roadmap)。 + +## 开始上手 + +要开始使用 Backstage,请参阅[入门文档](https://backstage.io/docs/getting-started)。 + +## 文档 + +Backstage 的文档包括: + +- [主要文档](https://backstage.io/docs) +- [软件目录](https://backstage.io/docs/features/software-catalog/) +- [架构](https://backstage.io/docs/overview/architecture-overview) ([决策](https://backstage.io/docs/architecture-decisions/)) +- [Backstage 设计](https://backstage.io/docs/dls/design) +- [Storybook - UI 组件](https://backstage.io/storybook) + +## 社区 + +要参与我们的社区,您可以使用以下资源: + +- [Discord 聊天室](https://discord.gg/backstage-687207715902193673) - 获得支持或讨论项目 +- [参与贡献 Backstage](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) - 如果您想做出贡献,请从这里开始 +- [RFCs](https://github.com/backstage/backstage/labels/rfc) - 帮助制定技术方向 +- [FAQ](https://backstage.io/docs/FAQ) - n.: 常问问题 +- [行为准则](CODE_OF_CONDUCT.md) - 这是我们的行事方式 +- [采纳者](ADOPTERS.md) - 已经在使用 Backstage 的公司 +- [博客](https://backstage.io/blog/) - 公告和更新 +- [通讯](https://spoti.fi/backstagenewsletter) - 订阅我们的电子邮件通讯 +- [Backstage 社区会议](https://github.com/backstage/community) - 参加每月聚会,探索 Backstage 社区 +- 给我们点个星星吧 ⭐️ - 如果您正在使用 Backstage 或认为它是一个有趣的项目,我们希望获得你的一颗星星 ❤️ + +## 许可 + +版权所有 2020-2023 © Backstage 作者。版权所有。Linux 基金会已注册商标并使用商标。有关 Linux 基金会的商标列表,请参阅我们的商标使用页面:https://www.linuxfoundation.org/trademark-usage + +采用 Apache v2.0 许可:http://www.apache.org/licenses/LICENSE-2.0 + +## 安全 + +请使用 Spotify 的 [Bug 赏金计划](https://hackerone.com/spotify) 而不是 GitHub 报告敏感安全问题。 + +更多详细信息,请参阅完整的[安全发布流程](SECURITY.md)。 diff --git a/README.md b/README.md index ef371691de..a1c53e4c8d 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ # [Backstage](https://backstage.io) +English \| [한국어](README-ko_kr.md) \| [中文版](README-zh_Hans.md) + [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![CNCF Status](https://img.shields.io/badge/cncf%20status-incubation-blue.svg)](https://www.cncf.io/projects) -[![Discord](https://img.shields.io/discord/687207715902193673)](https://discord.gg/backstage-687207715902193673) +[![Discord](https://img.shields.io/discord/687207715902193673?logo=discord&label=Discord&color=5865F2&logoColor=white)](https://discord.gg/backstage-687207715902193673) ![Code style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg) [![Codecov](https://img.shields.io/codecov/c/github/backstage/backstage)](https://codecov.io/gh/backstage/backstage) [![](https://img.shields.io/github/v/release/backstage/backstage)](https://github.com/backstage/backstage/releases) diff --git a/REVIEWING.md b/REVIEWING.md index 7ed3fcda57..c443c15d77 100644 --- a/REVIEWING.md +++ b/REVIEWING.md @@ -349,3 +349,13 @@ type LabelStyle = 'normal' | 'thin'; // Output, since it's an exported constant const LABEL_SIZE: number; ``` + +## Plugin Directory Submissions + +When reviewing Plugin Directory submissions please consider the following: + +- Check to make sure they have the rights for any icon being used. This is mostly for clearly copyrighted logos, for example the Microsoft Azure DevOps logo +- Make sure the package has been published on the NPM registry. +- Make sure the package on NPM has a link back to the code repo, this helps provide confidence that it's the right package. +- If they use an [NPM scope](https://docs.npmjs.com/about-scopes) make sure it that matches either the Organization name or user name, this provides trust in the plugin +- If the plugin has both a frontend and backend make sure that the documentation notes that. diff --git a/beps/0001-notifications-system/README.md b/beps/0001-notifications-system/README.md new file mode 100644 index 0000000000..c7b0ffff64 --- /dev/null +++ b/beps/0001-notifications-system/README.md @@ -0,0 +1,255 @@ +--- +title: Backstage Notifications System +status: provisional +authors: + - Rugvip +project-areas: + - core +creation-date: 2023-01-12 +--- + +# BEP: Backstage Notifications System + +[**Discussion Issue**](https://github.com/backstage/backstage/issues/22213) + +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) +- [Design Details](#design-details) +- [Release Plan](#release-plan) +- [Dependencies](#dependencies) +- [Alternatives](#alternatives) + +## Summary + +The Backstage Notifications System provides a way for Backstage plugins to send notifications to users. The notifications are displayed in the Backstage frontend UI, but future extensions can allow them to be sent via external channels such as email. The system includes a push mechanism to make sure that users receive notifications immediately, as well as persistence of notification history and read status. + +## Motivation + +Support for notifications is an old and common features request. See [#639](https://github.com/backstage/backstage/issues/639) and [#10652](https://github.com/backstage/backstage/issues/10652). There are also initiatives to implement notifications in [#20312](https://github.com/backstage/backstage/issues/20312) as well as the underlying infrastructure pieces, for example [#17997](https://github.com/backstage/backstage/issues/17997). + +This proposal assumes that further motivation for a notifications system as a whole is not needed. The focus is instead on the specific goals of an initial notifications system, and generally aiming to address the need to coordinate this work. + +### Goals + +We aim to build a system that would provide support for the following type of notifications, as listed in [#10652](https://github.com/backstage/backstage/issues/10652): + +- System-wide announcements or alerts +- Notifications for component owners (e.g. build failure, successful deployment, new vulnerabilities) +- Notifications for individuals (e.g. updates you have subscribed to, new required training courses) +- Notifications pertaining to a particular entity in the catalog (a given notification might apply to an entity and the owning team) + +Initially we only target a single way to send notifications: via a REST API call to the notifications backend, which in turn also provides an accompanying [backend service](https://backstage.io/docs/backend-system/architecture/services). + +The notifications system will need to support scaled deployments, where multiple backend instances may be deployed. + +In the future we aim to provide an extensible system that can be customized to send notifications to users via other channels. Notifications can be captured and processed in-band in order to trigger custom behavior such as sending an email. The goal is for this to be possible to build, but not necessarily part of the initial implementation. + +### Non-Goals + +The following features are out of scope for this BEP: + +- Building out specific support for channels such as email, Slack, and other external channels. +- Providing a fallback mechanisms when a notification cannot be delivered to a user. For example, if a user has not logged in for a long time, we will not attempt to send an email instead. + +## Proposal + +The notifications system is implemented through two net-new components, the `notifications` plugin, and the `signal` plugin. + +The `notifications` plugin in its simplest form provides the following: + +- A backend API for sending, reading, and interacting with notifications. +- Persistent storage of notification state. +- A frontend UI for viewing notifications. + +The `signal` plugin provides the following: + +- A backend API for pushing lightweight signals to online users. +- A connection between the frontend and backend that can be used to push messages to the client. +- A frontend API that lets plugins to subscribe to specific signals. + +### Signals Plugin + +In the backend the signal plugin implements a general purpose message bus for sending signals from backend plugins to connected users. It relies on the `EventBroker` from the [events plugin](https://github.com/backstage/backstage/blob/master/plugins/events-backend/README.md) for the actual message passing in the backend. In order to support scaled deployments, each signal backend instance has a separate subscription to the event broker so that each instance receives all events. It is then up to each backend instance to filter out events that are not relevant to it. For this reason, signals should be kept lightweight and not contain unnecessary data. + +In the frontend the signal plugin has a persistent connection to the signal backend. This is initially implemented as a WebSocket connection, but could in the future also receive fallback mechanisms such as Server Sent Events or long polling. It is important that this connection is authenticated as we will be routing signals to specific users. The exact implementation of the authentication is not part of this proposal, but it should use whatever the outcome of the discussion in issue [#19581](https://github.com/backstage/backstage/issues/19581) is. + +In order to route signals from the sender to the intended receiver the signal plugin uses the concept of signaling channels. They are just like the topics on the message bus, but we use separate terminology in order to avoid confusion. They also sit one layer beneath the even topic in the protocol stack, where the signal channel is communicated as part of the event payload. In the frontend, the signal plugin exposes an API to subscribe to a signal channel. Each time the user receives a signal on the specified channel, a listener is called with the signal payload. + +### Notifications Plugin + +The role of the notifications plugin is to manage the lifecycle of notifications. The backend plugin provides an API for other backends to send notifications, as well as an accompanying [backend service](https://backstage.io/docs/backend-system/architecture/services). It also provides a separate API for the frontend plugin to read notifications for an individual user and manage the read status of notifications. + +The notification backend stores notification using the [database service](https://backstage.io/docs/backend-system/core-services/index#database). In particular it needs to store the following information for each notification: + +- ID +- Payload +- Receivers +- Read status by user + +The receivers is **not** a list of users, but rather a filter that describes who should receive the notification. It must be possible to evaluate this filter in a database query, so that we can efficiently fetch all notifications for a given user. The same filter will also be used by the signal backend to determine which users should receive a signal. + +The notification backend does not provide any new permissions, since creating notifications can only be done by other backend plugins, while reading notifications can only be done by the authenticated user. It is possible that we want to add a permissions for reading notifications, in particular for admin and impersonation use cases, but that is not part of this proposal or the initial implementation. + +The notification frontend plugin provides a UI for viewing notifications, which in the initial implementation can be as simple as needed. The only requirement is that a user is able to view recent notifications and distinguish between read and unread notifications. A notification as marked as read once it has been interacted with. The frontend plugin also subscribes to the notifications signal channel and alerts the user when a new notification is received. + +### Architecture Overview + +The diagram below show an overview of the notifications system architecture and the interaction between the different components. + +![notifications system architecture diagram](./notifications-architecture.drawio.svg) + +The components that are presented with dashed lines are not directly part of this proposal, but included to be considered as future extensions. + +Let's walk through the process of sending of a notification to a user: + +1. A backend plugin that wants to send a notification to a user uses the `NotificationService`, which in turn makes a POST request to the notification backend. The request contains a filter to select the target users, as well as the notification payload. + + 1b. As the notification backend handles the request it could optionally invoke a series of notification processors. These processors can be used to transform the notification payload, send the notification through other channels, and decide whether the regular notification flow should continue or not. This step is not in scope for this proposal. + +2. The notification backend stores the received notification in its database. At this point the notification is available from the notification backend's read API. +3. The notification backend published an event to the event bus on the `'signal'` topic. This event payload contains the signaling channel, in this case `'notifications'`, as well as the target user ID and the notification ID, but not the notification payload. +4. Each instance of the signal backend plugin subscribes to the `'signal'` topic and receives the event. +5. Each signal backend instance has a set of push channels set up to online users. The incoming event is filtered based on the target user ID and the signaling is pushed through all connections with a matching user ID. +6. The signal frontend plugin receives the signal and forwards it to all subscribers of the target channel. In this case, the notifications frontend plugin, who is subscribed to the `'notifications'` channel. +7. The notifications frontend plugin receives the signal with the notification ID. It then makes a request to the notification backend to fetch the notification payload and then displays the notification to the user. + + 7b. Rather than rendering the notification directly, the notifications frontend plugin leaves that to an extension installed by the frontend part of the backend plugin that sent the notification. This allows for a more customized notification experience which can also contain custom actions and data display. This step is not in scope for this proposal. + +## Design Details + +### Backend Services + +The following backend service interfaces are added as part of this proposal. + +#### `NotificationService` + +```ts +// TODO - We may want to add an additional wrapping here with interfaces for Notification and NotificationParameters + +interface SendNotificationRequest { + receivers: SignalReceivers; + + /* TODO - please contribute :) */ +} + +interface SendNotificationResponse { + id: string; + receivers: SignalReceivers; + + /* TODO - please contribute :) */ +} + +interface NotificationService { + sendNotification( + request: SendNotificationRequest, + ): Promise; +} +``` + +#### `SignalService` + +```ts +type SignalReceivers = + | { + type: 'entity'; + entityRef: string; // typically a user or group entity + } + | { + type: 'broadcast'; // all users + }; + +interface Signal { + channel: string; + receivers: SignalReceivers; + payload: JsonObject; +} + +interface SignalService { + signal(signal: Signal): Promise; +} +``` + +### Frontend API + +The following frontend API is added as part of this proposal. + +#### `SignalApi` + +> TODO - we likely need a slightly different approach here, since APIs are lazy loaded. + +```ts +interface SignalSubscriber { + unsubscribe(): void; +} + +interface SignalApi { + subscribe( + channel: string, + listener: (payload: JsonObject) => void, + ): SignalSubscriber; +} +``` + +> TODO: Add payload formats and API details + + + +## Release Plan + +The notification and signal plugins are released as two new plugins in the Backstage ecosystem. They will both start out in an experimental state. + +For the notification plugin to reach a stable release we much reach the following: + +- A stable notifications payload format. +- A stable notifications receiver filter format. +- The event broker must have at least one implementation that supports scaled deployments. + +For the signal plugin to reach a stable release we much reach the following: + +- A stable signal receiver filter format. +- A stable signal channel API in the frontend. + +If any changes are required to the frontend framework to facilitate the implementation of notifications or signals, these will be released as experimental alpha features. They will stay in alpha until they are deemed stable enough, which must happen before a stable release of the notifications system. + +## Dependencies + +Since the signal plugin relies on the event broker for communication, it is a dependency for the notifications system as a whole. The event broker does not currently implement any transport for scaled deployments, which is a requirement for scaled deployments of the notification system. + +## Alternatives + +### Signal Plugin Separation + +One primary consideration for the notifications system was whether the notification plugin should set up its own frontend push connection rather than relying on the signal plugin. The signal plugin separation is done for three primary reasons: + +1. The use-case of sending signals from backend plugins to frontend plugins is not limited to only notifications, since not all signals should necessarily be displayed directly to the user. For example, the catalog backend may want to send a signal to the frontend when an entity is updated so that users that are currently viewing the entity in the frontend can trigger an update of the view. +2. We want to limit the number of WebSocket connections that the frontend needs to maintain. By using a separated signal abstraction it is easier to extend the implementation to support other messaging patterns in the future. For example, we may want to move the Scaffolder logs to be sent via the signal plugin instead. That is much more straightforward with a separate signal plugin compared to adding it to the notifications plugin. +3. The technical implementation of sending signals is sufficiently complex that it makes sense to separate out to its own solution in order to reduce the complexity of the notifications plugin. In particular when it comes to handling scaled deployments. + +### User-to-User Notifications + +The notification backend API for sending notifications is only available to other backend plugins, and is not accessible to end users in the frontend. If this were to be added it would likely be a separate backend module that you can install alongside the notification backend. That is unless the permission framework can be extended to support default policy handlers so that we can reject the permission to send user notifications by default. + +There are a few reasons we don't want to allow user-to-user notifications out of the box: + +- To limit the possibility of spam. Even if well-intended, a notification overload can quickly lead to a bad user experience and users disabling and ignoring notifications. +- From a security perspective it's beneficial to avoid user sent notifications since they could be used to trick users into clicking on malicious links, or other similar attacks. +- Our assumption is that this is a feature that most adopters will not want to use, and so by having it be disabled by default we can avoid additional configuration steps. diff --git a/beps/0001-notifications-system/notifications-architecture.drawio.svg b/beps/0001-notifications-system/notifications-architecture.drawio.svg new file mode 100644 index 0000000000..9f8194b499 --- /dev/null +++ b/beps/0001-notifications-system/notifications-architecture.drawio.svg @@ -0,0 +1,537 @@ + + + + + + + + + + +
+
+
+ Notification Backend +
+
+
+
+ + Notification Backend + +
+
+ + + + + + + + +
+
+
+ Notification Frontend +
+
+
+
+ + Notification Frontend + +
+
+ + + + +
+
+
+ Signal Backend +
+
+
+
+ + Signal Backend + +
+
+ + + + + + + + + + +
+
+
+ Signal Frontend +
+
+
+
+ + Signal Frontend + +
+
+ + + + + + +
+
+
+ Plugin X Backend +
+
+
+
+ + Plugin X Backend + +
+
+ + + + +
+
+
+ Plugin X Frontend +
+
+
+
+ + Plugin X Frontend + +
+
+ + + + + + + + + + + + +
+
+
+ Event Broker +
+
+
+
+ + Event Broker + +
+
+ + + + + + +
+
+
+ ... +
+
+
+
+ + ... + +
+
+ + + + +
+
+
+ ... +
+
+
+
+ + ... + +
+
+ + + + +
+
+
+ publish event +
+
+
+
+ + publish event + +
+
+ + + + +
+
+
+ broadcast +
+
+
+
+ + broadcast + +
+
+ + + + +
+
+
+ ??? +
+
+
+
+ + ??? + +
+
+ + + + +
+
+
+ server push +
+
+
+
+ + server push + +
+
+ + + + +
+
+
+ POST +
+
+
+
+ + POST + +
+
+ + + + + + +
+
+
+ External System +
+
+
+
+ + External System + +
+
+ + + + +
+
+
+ email/slack/teams, etc. +
+
+
+
+ + email/slack/teams, etc. + +
+
+ + + + +
+
+
+ Render +
+
+
+
+ + Render + +
+
+ + + + +
+
+
+ + Processor + +
+
+
+
+ + Processor + +
+
+ + + + +
+
+
+ GET +
+
+
+
+ + GET + +
+
+ + + + +
+
+
+ 1 +
+
+
+
+ + 1 + +
+
+ + + + +
+
+
+ 3 +
+
+
+
+ + 3 + +
+
+ + + + +
+
+
+ 1b +
+
+
+
+ + 1b + +
+
+ + + + +
+
+
+ 4 +
+
+
+
+ + 4 + +
+
+ + + + +
+
+
+ 5 +
+
+
+
+ + 5 + +
+
+ + + + +
+
+
+ 6 +
+
+
+
+ + 6 + +
+
+ + + + +
+
+
+ 7b +
+
+
+
+ + 7b + +
+
+ + + + +
+
+
+ 7 +
+
+
+
+ + 7 + +
+
+ + + + + + +
+
+
+ 2 +
+
+
+
+ + 2 + +
+
+ + + + +
+
+
+ Channel Callback +
+
+
+
+ + Channel Callback + +
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/beps/0002-dynamic-frontend-plugins/README.md b/beps/0002-dynamic-frontend-plugins/README.md new file mode 100644 index 0000000000..fc56e74c83 --- /dev/null +++ b/beps/0002-dynamic-frontend-plugins/README.md @@ -0,0 +1,498 @@ +--- +title: Dynamic Frontend Plugins +status: provisional +authors: + - '@Hyperkid123' +owners: +project-areas: + - core +creation-date: 2024-01-17 +--- + + + +# BEP: + + + +[**Discussion Issue**](https://github.com/backstage/backstage/issues/22423) + +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) +- [Design Details](#design-details) +- [Release Plan](#release-plan) +- [Dependencies](#dependencies) +- [Alternatives](#alternatives) + +## Summary + + + +The dynamic frontend plugins feature is a way of loading additional frontend plugins at runtime, without the requirement of rebuilding and restarting a running backstage instance. + +This system should significantly improve frontend plugin management for backstage instances. + +The dynamic plugins leverage the declarative UI system to define what a plugin is and how it should be represented in the browser. + +## Motivation + + + +### Goals + + + +- discover and choose tooling to enable dynamic frontend plugins +- easy way of converting existing frontend plugins to be dynamic +- turning on and off UI plugins at runtime without the requirement of rebuilding or restarting backstage +- manage dynamic plugins declaratively + +An additional goal might be exploring the scope of a "on demand" rending and loading. Currently all plugins, or at least some portion of them must be loaded at UI bootstrap. This approach is not fully dynamic and might be considered a waste of resources. Fully optimizing dynamic plugins loading would require additional changes to the new UI system. + +### Non-Goals + + + +## Proposal + + + +### Definition of UI dynamic plugin + +A dynamic UI plugin (from now just plugin) is a plugin that is not part of the output of a backstage instance build. The plugin and its assets are injected into backstage at runtime. In this case, its injected into the browser at some point during user session. + +From the user POV, there is no difference between classic and dynamic plugins. + +The difference is known only to maintainers and should be limited to + +- build requirements +- integration into backstage + +### Dynamic loading tool + +Traditional plugins are not dynamic out of the box. Additional changes are required during build time to make a plugin dynamic. + +The main application (shell) also requires some changes to be able to load, inject, and propagate mandatory context to dynamic plugins. + +Over the last few year, [Module Federation](https://github.com/module-federation) has become the standard when it comes to dynamically load JS modules browser (and nodejs) environment from remote locations and sharing context between the shell and the remote module. + +Although there are other options, like externalizing dependencies, the module federation has proven itself as a robust solution to this particular problem. + +### Module federation implementation + +There are multiple available implementations of module federation. + +Historically, module federation was implemented as a [Webpack feature](https://webpack.js.org/). Since then, additional implementations were created. Mainly for [`rspack`](https://www.rspack.dev/) and [Vite](https://github.com/originjs/vite-plugin-federation). + +Recent changes _claim_, that all of these module federation implementations should be compatible with each other and there should be no need for locking backstage into a single implementation. + +Compatibility between Webpack and `rspack` should available via the [@module-federation/enhanced](https://www.npmjs.com/package/@module-federation/enhanced) package. The compatibility is further [described here](https://www.rspack.dev/blog/module-federation-added-to-rspack.html#introducing-rspack-050). + +The Vite plugin claims webpack compatibility as well. The Vite plugin _is not part of the @module-federation_ organization. The extend of compatibility is at this time unknown. + +**The level of compatibility has to be tested.** Before then, no decision in regards to which implementation(s) will be used should be made. The chosen tool, or their mix, will shape the design and implementation. + +That said, packages from the `@module-federation/*` organization should have higher priority as they are based directly on the module federation concepts and are well supported. + +### Plugin integration into shell applications + +Plugins should be integrated via the new UI system. The system already provides an asynchronous way of loading plugins. The dynamic plugins can be loaded in a similar fashion. + +Plugins should be defined declaratively through configuration. Similar to what was described in this [RFC](https://github.com/backstage/backstage/issues/18372) and in this [issue](https://github.com/backstage/backstage/issues/19545). + +### Plugin registry + +Because plugins are not available at build time, some sort of registry needs to exist to store the information. + +This registry needs to be mutable at runtime (add/remove new plugin metadata) and changes have to be reflected on session refresh. + +This is currently an issue as the app config is embedded into JS assets during build time. + +Each plugin is required to provide manifest file (metadata) in predefined format. This manifest will be used to inject the plugin assets into the browser. + +### Plugin discovery + +Plugin discovery is a pre-requisite for Plugin registry. This should be responsible for scanning for available plugins and generating/modifying the plugin registry to always keep it up to date. + +## Design Details + +> NOTE The details are based on the Janus implementation of dynamic frontend plugins. The implementation leverages [Scalprum](https://github.com/scalprum/scaffolding) which is a Webpack based dynamic plugin manager for React applications. + +### Module federation implementation experiments + +Test should consist of trying to run permutations of webpack/Rspack/vite based shell apps/plugins and discover if we can freely choose any tool, or if we should restrict the tooling to just a subset of the available options. + +The outcome of initial testing is positive and it is possible to mix and match different build tools and consume different remote modules in a single shell application. + +The experimental code can be found in [this repository](https://github.com/scalprum/mf-mixing-experiments). + +**The testing so far was done only on very simple modules**. Although core React features are working (Context API and hooks), more testings needs to be done in order to declare this approach 100% reliable. + +So far a lot of custom code needs to be written to bridge Webpack, Rspack, @module-federation/enhanced with Vite. The first three are compatible out of the box, but Vite requires extra bridge to be able to consume/provide modules with/to other builds. + +### Plugin manifest + +Each plugin should have a manifest file with important metadata. This metadata is used to load the remote assets to browser. The plugin manifest should be part of a build output. + +A manifest should have: + +- name of plugin +- how can be the init container accessed +- what is the base URL (assets pathname) +- name of the entry script(s) + +#### Scalprum compatible manifest + +```TS +type PluginManifest = { + name: string; + version: string; + dependencies?: Record; + customProperties?: AnyObject; + baseURL: string; + extensions: Extension[]; + loadScripts: string[]; + registrationMethod: 'callback' | 'custom'; + buildHash?: string; +} +``` + +```js +{ + "name": "backstage.plugin-github-actions", + "version": "0.6.6", + "extensions": [], + "registrationMethod": "callback", // where container init is available in browser + "baseURL": "auto", + "loadScripts": [ + "backstage.plugin-github-actions.804b91040fcbca6585ce.js" + ], + "buildHash": "804b91040fcbca6585ce1bcd4b1f8aa2" +} +``` + +##### `registrationMethod` + +Refers to [webpack output.libraryTarget](https://webpack.js.org/configuration/output/#outputlibrarytarget). + +Callback refers to `jsonp` and a the `custom` is used if other available target configuration has been picked. + +It is recommended to use either `global` or `jsonp` as these are environment agnostic (browser VS node). `jsonp` requires additional configurations. The `global` is preferable due to its simplicity. + +##### `baseURL` + +The `baseURL` is derived from [webpack public path](https://webpack.js.org/configuration/output/#outputpublicpath) + +The public path can also be set to `auto` to remove the need to specify origin or pathname and resolve the pathname at runtime. + +In Scalprum, some manifest [post processing](https://github.com/janus-idp/backstage-showcase/blob/main/packages/app/src/components/DynamicRoot/DynamicRoot.tsx#L323) is required to load the initial scripts if the `auto` baseURL is chosen. + +### Plugin registry + +Plugin registry can be fairly simplistic. It can be as simple as JSON file containing list/map of available plugins and their manifests + +```TS +type RegistryEntry = { + name: string // plugin name + manifestLocation: string // path to the manifest resource +} + +// object for easy access +type PluginRegistry = { + [pluginName: string]: RegistryEntry +} + +// or as an array +type PluginRegistry = RegistryEntry[] +``` + +Example of such registry + +```JS +// as object +{ + "backstage.plugin-github-actions": { + "name": "backstage.plugin-github-actions", + "manifestLocation": "https://foo-bar.com/api/plugin-storage/plugin-manifest.json" + }, + // ..rest of plugins +} + +// as array +[ + { + "name": "backstage.plugin-github-actions", + "manifestLocation": "https://foo-bar.com/api/plugin-storage/plugin-manifest.json" + }, + // ...rest of plugins +] +``` + +Scalprum by default lazy loads plugins and manifests. That is because Scalprum initializes plugins only once they are supposed to be rendered in browser. + +Because backstage does not require that functionality in initial dynamic plugin implementation and loads all plugins at bootstrap, there is an alternative to embed manifest data into the registry itself. + +```TS +type RegistryEntry = { + name: string // plugin name + pluginManifest: PluginManifest +} + +// object for easy access +type PluginRegistry = { + [pluginName: string]: RegistryEntry +} + +// or as an array +type PluginRegistry = RegistryEntry[] + +``` + +Example of registry with embedded manifests + +```JS +// as object +{ + "backstage.plugin-github-actions": { + "name": "backstage.plugin-github-actions", + "pluginManifest": { + "name": "backstage.plugin-github-actions", + "version": "0.6.6", + "extensions": [], + "registrationMethod": "callback", + "baseURL": "auto", + "loadScripts": [ + "backstage.plugin-github-actions.804b91040fcbca6585ce.js" + ], + "buildHash": "804b91040fcbca6585ce1bcd4b1f8aa2" + } + }, + // ..rest of plugins +} + +// as array +[ + { + "name": "backstage.plugin-github-actions", + "pluginManifest": { + "name": "backstage.plugin-github-actions", + "version": "0.6.6", + "extensions": [], + "registrationMethod": "callback", + "baseURL": "auto", + "loadScripts": [ + "backstage.plugin-github-actions.804b91040fcbca6585ce.js" + ], + "buildHash": "804b91040fcbca6585ce1bcd4b1f8aa2" + } + }, + // ...rest of plugins +] + +``` + +### Webpack build configuration + +> NOTE This is a sample current configuration in the Janus project. It uses Scalprum webpack based build plugin to generate the output. It does not take the [all options](#Module-federation-implementation-experiments) in consideration. This section will likely change considerably. + +Part of a Scalprum tooling is also [dynamic-plugins-sdk](https://github.com/openshift/dynamic-plugin-sdk). Right now the package is a part of a different project, but that is about to change. More details in the [Scalprum roadmap](https://github.com/scalprum/scaffolding/blob/main/ROADMAP.md). + +#### Sample plugin configuration using Scalprum SDK + +```TS +import { DynamicRemotePlugin } from '@openshift/dynamic-plugin-sdk-webpack'; + +const sharedModules = { + /** + * Mandatory singleton packages for sharing + */ + react: { + singleton: true, + requiredVersion: '*', + }, + 'react-dom': { + singleton: true, + requiredVersion: '*', + }, + 'react-router-dom': { + singleton: true, + requiredVersion: '*', + }, + 'react-router': { + singleton: true, + requiredVersion: '*', + }, + ... + /** + * Full list of shared modules in Janus + * https://github.com/janus-idp/backstage-plugins/blob/87a6b045c7b0f301ebed8b8f99dc1741fa2b044b/packages/cli/src/lib/bundler/scalprumConfig.ts#L16 + */ + +} + +const dynamicPluginPlugin = new DynamicRemotePlugin({ + extensions: [], + sharedModules, + entryScriptFilename: `${options.pluginMetadata.name}.[contenthash].js`, + pluginMetadata: { + // version cna be used from the package.json version field + version: '1.0.0', + /** + * Name can be easily derived from the plugin name + * https://github.com/janus-idp/backstage-plugins/blob/87a6b045c7b0f301ebed8b8f99dc1741fa2b044b/packages/cli/src/commands/export-dynamic-plugin/frontend.ts#L40 + */ + name: 'backstage.plugin-github-actions', + /** + * Path to the plugin entry point. + * It can default to the same entry point as in regular build. + * Plugins can expose multiple modules. We have found that one is sufficient from backstage plugins. + */ + exposedModules: { + PluginRoot: './src/index.ts' + } + }, +}); +``` + +The `DynamicRemotePlugin` webpack plugin takes care of the rest, including the manifest generation. + +#### Sample plugin raw webpack configuration + +The Scalprum config translates to a following base webpack plugin configuration: + +```JS +import { container } from 'webpack'; + +const dynamicPlugin = new container.ModuleFederationPlugin({ + name: 'backstage.plugin-github-actions', + library: { + type: 'global', + /** + * Some library.type has name limitation + * for example, if "type": "var" is used, the library.name can contain the "-" character + */ + name: 'backstage.plugin-github-actions' + }, + filename: 'backstage.plugin-github-actions.[contenthash].js', + exposes: { + PluginRoot: './src/index.ts' + }, + // list of shared modules like "react", "react-dom", etc + shared: sharedModules, +}); +``` + +Additional plugin would have to be written from scratch to generate the plugin manifest file. + +### Sample shell application webpack configuration + +Shell application (backstage) has very simple module federation configuration. It does not require anything special to be able to inject plugins. Main requirement is to provide core shared packages like react and react-dom. + +```JS +import { container } from 'webpack'; + +const scalprumPlugin = new container.ModuleFederationPlugin({ + name: 'backstageHost', + filename: 'backstageHost.[contenthash].js', + // same share modules list as with plugin config + shared: [sharedModules], +}); +``` + +Nothing else is required. + +### Plugin storage + +Where will the dynamic plugin assets be hosted? Module federation does not strictly require the remote assets to be all hosted on the same origin. + +Theoretically plugins can be hosted on some "public CDN" which is detached from individual backstage instances. + +Assets can be also be hosted in the same way as they have always been. + +### Plugin discovery + +How to notify/send data to browser + +### Plugin initialization + +Currently all plugins have to be initialized at UI bootstrap (page refresh). The new UI async API can be used to initialize the remote assets. + +The `CreateAppFeatureLoader` can be leveraged to initialize the remote container. + +```TS +const allPluginManifests = {...} // get this from the config + +const asyncFeatureLoaders: CreateAppFeatureLoader[] = Object.values(allPluginManifests).map(({ manifest }) => { + return { + getLoaderName: () => manifest.name, + load: (options) => { + // initialize the remote container, depends on tooling + const plugin = initDynamicPlugin(manifest) + } + } +}) + +const app = createApp({ + features: [ + ...asyncFeatureLoaders, + // rest of classic features + ] +}) +``` + +### Plugin declarative configuration + +Janus dynamic plugins ref: https://github.com/janus-idp/backstage-showcase/blob/main/showcase-docs%2Fdynamic-plugins.md#frontend-layout-configuration + +TBD, depends heavily on the new UI system + + + +## Release Plan + + + +## Dependencies + + + +## Alternatives + + diff --git a/beps/NNNN-template/README.md b/beps/NNNN-template/README.md new file mode 100644 index 0000000000..40e2eb8f72 --- /dev/null +++ b/beps/NNNN-template/README.md @@ -0,0 +1,99 @@ +--- +title: BEP Title +status: provisional +authors: + - '@ghost' +owners: +project-areas: + - aaa + - bbb +creation-date: yyyy-mm-dd +--- + + + +# BEP: + + + +[**Discussion Issue**](https://github.com/backstage/backstage/issues/NNNNN) + +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) +- [Design Details](#design-details) +- [Release Plan](#release-plan) +- [Dependencies](#dependencies) +- [Alternatives](#alternatives) + +## Summary + + + +## Motivation + + + +### Goals + + + +### Non-Goals + + + +## Proposal + + + +## Design Details + + + +## Release Plan + + + +## Dependencies + + + +## Alternatives + + diff --git a/beps/README.md b/beps/README.md new file mode 100644 index 0000000000..531bd15f50 --- /dev/null +++ b/beps/README.md @@ -0,0 +1,86 @@ +# Backstage Enhancement Proposals (BEPs) + +A Backstage Enhancement Proposal (BEP) is a way to propose, communicate and coordinate on new efforts for the Backstage project. + +## Quick start for the BEP process + +1. Discuss the idea with the community and maintainers. Either here on GitHub, Discord, or during community sessions or SIG meetings. + Make sure that others think the work is worth taking up and will help review the BEP and any code changes required. +1. Make a copy of the [BEP template](./NNNN-template/) directory as `beps/NNNN-short-descriptive-title`, where `NNNN` is the next available number padded with leading zeroes. +1. Fill out as much of the YAML metadata as you can. +1. Fill out the template as best you can. +1. If you want the BEP to be owned by a particular project area, add an entry for the BEP folder to [CODEOWNERS](../.github/CODEOWNERS). +1. Create a PR for the BEP. Title it "BEP: <title>". Aim to get the high level goals clarified and avoid getting hung up on specific details. The PR can be merged early and iterated on. +1. Once the BEP is ready to be merged, create a [feature issue](https://github.com/backstage/backstage/issues/new?template=feature.yaml) titled "BEP: <title>". Use the summary of the BEP as description and link to the BEP PR in the context section. Once the PR is merged, the issue should be updated to link to the BEP folder instead. This issue will serve as a general discussion issue for the BEP. +1. Once the initial BEP is merged you should keep iterating on it until it is ready to leave that `provisional` state. Leaving the `provisional` state is a decision made by the project area maintainers. + +Just because a BEP is merged does not mean it is complete or approved for implementation. Any BEP marked as `provisional` is a working document and subject to change. + +The authors of the BEP are also responsible for driving the BEP forward all the way to implementation. The approval of a BEP is not a commitment to implement it. + +## FAQs + +### Do I have to use the BEP process? + +No, but it is recommended for larger changes and contributions that affect the Backstage framework or core features where you want to make design decisions upfront and iterate on them across several PRs. + +### What is the difference between a BEP and an RFC issue? + +An RFC issue is a good way to receive feedback on an idea. The BEP process may involve getting feedback on an idea, but it takes things further by making it easier to iterate on the design of a new feature, getting it approved by the owning project area maintainers, and tracking its implementation. + +While anyone can open an RFC issue, the features added by a BEP need to be approved by the owners before it can be merged. The BEP will also contain the latest agreed upon design, whereas an RFC issue is more of a discussion forum. + +Often a BEP can start out as an RFC issue, which may then be converted into a BEP once the idea has received an owner and been approved. + +### Should I use the BEP process for new or existing Backstage plugins? + +No, except for plugins that implement core features of Backstage. The BEP process is only intended for changes to the Backstage framework and core features. + +### Can I update an existing BEP? + +Yes! As long as the BEP is still in the `provisional` state you should keep iterating on it. Please keep each PR focused on a single topic and avoid long-running and overly broad PRs. + +### Can I update a BEP that was submitted by someone else? + +Yes! BEPs are living documents and anyone can suggest changes to them. We encourage to challenge the design decisions in a BEP and suggest alternatives. You can also help fill out details in a BEP that has not yet been fully fleshed out. + +### What's the difference between a BEP and an [ADR](https://backstage.io/docs/architecture-decisions/)? + +Architecture Decision Records (ADRs) are used to document decisions made for development within the Backstage project. They are not intended to be used for proposing new features or changes to Backstage. + +### My FAQ isn't answered here! + +The BEP process is still evolving! + +If something is missing or not answered here feel free to reach out on our community [Discord](https://discord.gg/backstage-687207715902193673). +If you want to propose a change to the BEP process you can also open a PR directly with your proposal. + +### BEP Metadata + +At the start of each BEP is a YAML document that contains metadata about the BEP. These are the fields that should currently be provided: + +| Field | Description | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **title** | The title of the BEP in plain language. The title will also be used in the BEP filename. See the template for instructions and details. | +| **status** | The current state of the BEP, must be one of the values listed in the [BEP Status](#bep-status) section. | +| **authors** | A list of GitHub IDs for the authors of the BEP. Typically only the original authors are included, but additional authors can be added if significant contributions are made. | +| **owners** | A list of GitHub IDs for the owners of the BEP. The owners are responsible for the implementation of the BEP, and are required for the BEP to move to the `implementable` status. | +| **project-areas** | The Project Areas closely associated with this BEP that need to approve it. For a list of existing project areas, see [OWNERS.md](../OWNERS.md). If the BEP is not related to any existing project area, use `core-maintainers` instead. | +| **creation-date** | The `yyyy-mm-dd` date that the BEP was first submitted in a PR. | + +## BEP Status + +The BEP Status is critical to clearly communicate the status of each BEP. Each BEP must have its status field set to one of the following values: + +- `provisional`: The BEP has been proposed and is actively being defined. The BEP as been approved by the owning project area maintainers as work to be done. +- `implementable`: The approvers have approved this BEP for implementation. +- `implemented`: The BEP has been implemented and is no longer actively changed. +- `deferred`: The BEP is proposed but not actively being worked on. +- `rejected`: The approvers and authors have decided that this BEP is not moving forward. + The BEP is kept around as a historical document. +- `withdrawn`: The authors have withdrawn the BEP. +- `replaced`: The BEP has been replaced by a new BEP. + +### Prior Art + +The BEP process is heavily inspired by the [Kubernetes Enhancement Proposal (KEP) process](https://github.com/kubernetes/enhancements/blob/master/keps/README.md). diff --git a/contrib/chart/backstage/Chart.lock b/contrib/chart/backstage/Chart.lock deleted file mode 100644 index b6068caacf..0000000000 --- a/contrib/chart/backstage/Chart.lock +++ /dev/null @@ -1,6 +0,0 @@ -dependencies: -- name: postgresql - repository: https://charts.bitnami.com/bitnami - version: 13.2.12 -digest: sha256:3add6e2f2c055e415403ebbba7740113ee052b87ad72e59b35b301df2098d41e -generated: "2023-11-20T12:09:32.786270903Z" diff --git a/contrib/chart/backstage/Chart.yaml b/contrib/chart/backstage/Chart.yaml deleted file mode 100644 index 80ff44462c..0000000000 --- a/contrib/chart/backstage/Chart.yaml +++ /dev/null @@ -1,30 +0,0 @@ -apiVersion: v2 -name: backstage -description: A Helm chart for Backstage -type: application - -# This is the chart version. This version number should be incremented each time you make changes -# to the chart and its templates, including the app version. -version: 0.1.3 - -# This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. -appVersion: v0.1.1-alpha.23 - -sources: - - https://github.com/backstage/backstage - - https://github.com/spotify/lighthouse-audit-service - -dependencies: - - name: postgresql - condition: postgresql.enabled - version: 13.2.12 - repository: https://charts.bitnami.com/bitnami - -maintainers: - - name: Martina Iglesias Fernández - email: martina@roadie.io - url: https://roadie.io - - name: David Tuite - email: david@roadie.io - url: https://roadie.io diff --git a/contrib/chart/backstage/README.md b/contrib/chart/backstage/README.md deleted file mode 100644 index 40bb2666bc..0000000000 --- a/contrib/chart/backstage/README.md +++ /dev/null @@ -1,282 +0,0 @@ -# Backstage demo helm charts - -This folder contains Helm charts that can easily create a Kubernetes deployment of a demo Backstage app. - -### Pre-requisites - -These charts depend on the `nginx-ingress` controller being present in the cluster. If it's not already installed you -can run: - -```shell -helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx -helm install nginx-ingress ingress-nginx/ingress-nginx -``` - -### Installing the charts - -After choosing a DNS name where backstage will be hosted create a yaml file for your custom configuration. - -```yaml -appConfig: - app: - baseUrl: https://backstage.mydomain.com - title: Backstage - backend: - baseUrl: https://backstage.mydomain.com - cors: - origin: https://backstage.mydomain.com - lighthouse: - baseUrl: https://backstage.mydomain.com/lighthouse-api - techdocs: - storageUrl: https://backstage.mydomain.com/api/techdocs/static/docs - requestUrl: https://backstage.mydomain.com/api/techdocs -``` - -Then use it to run: - -```shell -git clone https://github.com/backstage/backstage.git -cd contrib/chart/backstage -helm dependency update -helm install -f backstage-mydomain.yaml backstage . -``` - -This command will deploy the following pieces: - -- Backstage frontend -- Backstage backend with scaffolder and auth plugins -- (optional) a PostgreSQL instance -- lighthouse plugin -- ingress - -After a few minutes Backstage should be up and running in your cluster under the DNS specified earlier. - -Make sure to create the appropriate DNS entry in your infrastructure. To find the public IP address run: - -```shell -$ kubectl get ingress -NAME HOSTS ADDRESS PORTS AGE -backstage-ingress * 123.1.2.3 80 17m -``` - -> **NOTE**: this is not a production ready deployment. - -## Customization - -### Issue certificates - -These charts can install or reuse a `clusterIssuer` to generate certificates for the backstage `ingress`. To do that: - -1. [Install][install-cert-manager] or make sure [cert-manager][cert-manager] is installed in the cluster. -2. Enable the issuer in the charts. This will first check if there is a `letsencrypt` issuer already deployed in your - cluster and deploy one if it doesn't exist. - -To enable it you need to provide a valid email address in the chart's values: - -```yaml -issuer: - email: me@example.com - clusterIssuer: 'letsencrypt-prod' -``` - -By default, the charts use `letsencrypt-staging` so in the above example we instruct helm to use the production issuer -instead. - -[cert-manager]: https://cert-manager.io/docs/ -[install-cert-manager]: https://cert-manager.io/docs/installation/kubernetes/#installing-with-helm - -### Custom PostgreSQL instance - -Configuring a connection to an existing PostgreSQL instance is possible through the chart's values. - -First create a yaml file with the configuration you want to override, for example `backstage-prod.yaml`: - -```yaml -postgresql: - enabled: false - -appConfig: - app: - baseUrl: https://backstage-demo.mydomain.com - title: Backstage - backend: - baseUrl: https://backstage-demo.mydomain.com - cors: - origin: https://backstage-demo.mydomain.com - database: - client: pg - connection: - database: backstage_plugin_catalog - host: - user: - password: - lighthouse: - baseUrl: https://backstage-demo.mydomain.com/lighthouse-api - -lighthouse: - database: - client: pg - connection: - host: - user: - password: - database: lighthouse_audit_service -``` - -For the CA, create a `configMap` named `--postgres-ca` with a file called `ca.crt`: - -```shell -kubectl create configmap my-company-backstage-postgres-ca --from-file=ca.crt" -``` - -or disable CA mount - -```yaml -backend: - postgresCertMountEnabled: false - -lighthouse: - postgresCertMountEnabled: false -``` - -> Where the release name contains the chart name "backstage" then only the release name will be used. - -Now install the helm chart: - -```shell -cd contrib/chart/backstage -helm install -f backstage-prod.yaml my-backstage . -``` - -### Use your own docker images - -The docker images used for the deployment can be configured through the charts values: - -```yaml -frontend: - image: - repository: - tag: - -backend: - image: - repository: - tag: - -lighthouse: - image: - repository: - tag: -``` - -### Use a private docker repo - -Create a docker-registry secret - -```shell -kubectl create secret docker-registry # args -``` - -> For private images on docker hub --docker-server can be set to docker.io - -Reference the secret in your chart values - -```yaml -dockerRegistrySecretName: -``` - -### Different namespace - -To install the charts a specific namespace use `--namespace `: - -```shell -helm install -f my_values.yaml --namespace demos backstage . -``` - -### Disable loading of demo data - -To deploy backstage with the pre-loaded demo data disable `backend.demoData`: - -```shell -helm install -f my_values.yaml --set backend.demoData=false backstage . -``` - -### Other options - -For more customization options take a look at the [values.yaml](/contrib/chart/backstage/values.yaml) file. - -## Troubleshooting - -Some resources created by these charts are meant to survive after upgrades and even after uninstalls. When -troubleshooting these charts it can be useful to delete these resources between re-installs. - -Secrets: - -``` --postgresql-certs -- contains the certificates used by the deployed PostgreSQL -``` - -Persistent volumes: - -``` -data--postgresql-0 -- this is the data volume used by PostgreSQL to store data and configuration -``` - -> **NOTE**: this volume also stores the configuration for PostgreSQL which includes things like the password for the -> `postgres` user. This means that uninstalling and re-installing the charts with `postgres.enabled` set to `true` and -> auto generated passwords will fail. The solution is to delete this volume with -> `kubectl delete pvc data--postgresql-0` - -ConfigMaps: - -``` --postgres-ca -- contains the generated CA certificate for PostgreSQL when `postgres` is enabled -``` - -#### Unable to verify signature - -``` -Backend failed to start up Error: unable to verify the first certificate - at TLSSocket.onConnectSecure (_tls_wrap.js:1501:34) - at TLSSocket.emit (events.js:315:20) - at TLSSocket._finishInit (_tls_wrap.js:936:8) - at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:710:12) { - code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' -``` - -This error happens in the backend when it tries to connect to the configured PostgreSQL database and the specified CA is not correct. The solution is to make sure that the contents of the `configMap` that holds the certificate match the CA for the PostgreSQL instance. A workaround is to set `appConfig.backend.database.connection.ssl.rejectUnauthorized` to `false` in the chart's values. - -#### Multi-Platform Kubernetes Services - -If you are running a multi-platform Kubernetes service with Windows and Linux nodes then you will need to apply a `nodeSelector` to the Helm chart to ensure that pods are scheduled onto the correct platform nodes. - -Add the following to your Helm values file: - -```yaml -global: - nodeSelector: - kubernetes.io/os: linux - -# If using Postgres Chart also add -postgresql: - master: - nodeSelector: - kubernetes.io/os: linux - slave: - nodeSelector: - kubernetes.io/os: linux -``` - - - -## Uninstalling Backstage - -To uninstall Backstage simply run: - -```shell -RELEASE_NAME= # use `helm list` to find out the name -helm uninstall ${RELEASE_NAME} -kubectl delete pvc data-${RELEASE_NAME}-postgresql-0 -kubectl delete secret ${RELEASE_NAME}-postgresql-certs -kubectl delete configMap ${RELEASE_NAME}-postgres-ca -``` diff --git a/contrib/chart/backstage/files/app-config.development.yaml.tpl b/contrib/chart/backstage/files/app-config.development.yaml.tpl deleted file mode 100644 index 105ba3b259..0000000000 --- a/contrib/chart/backstage/files/app-config.development.yaml.tpl +++ /dev/null @@ -1,51 +0,0 @@ -backend: - lighthouseHostname: {{ include "lighthouse.serviceName" . | quote }} - listen: - port: {{ .Values.appConfig.backend.listen.port | default 7007 }} - database: - client: {{ .Values.appConfig.backend.database.client | quote }} - connection: - host: {{ include "backend.postgresql.host" . | quote }} - port: {{ include "backend.postgresql.port" . | quote }} - user: {{ include "backend.postgresql.user" . | quote }} - database: {{ .Values.appConfig.backend.database.connection.database | quote }} - ssl: - rejectUnauthorized: {{ .Values.appConfig.backend.database.connection.ssl.rejectUnauthorized | quote }} - ca: {{ include "backstage.backend.postgresCaFilename" . | quote }} - -catalog: -{{- if .Values.backend.demoData }} - locations: - # Backstage example components - - type: github - target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-components.yaml - # Example component for github-actions - - type: github - target: https://github.com/backstage/backstage/blob/master/plugins/github-actions/examples/sample.yaml - # Example component for techdocs - - type: github - target: https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component/documented-component.yaml - # Backstage example APIs - - type: github - target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml - # Backstage example templates - - type: github - target: https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/sample-templates/all-templates.yaml -{{- else }} - locations: [] -{{- end }} - -auth: - providers: - microsoft: null - -scaffolder: - azure: null - - -sentry: - organization: {{ .Values.appConfig.sentry.organization | quote }} - -techdocs: - generator: - runIn: 'local' \ No newline at end of file diff --git a/contrib/chart/backstage/files/create-backend-dbs.sql b/contrib/chart/backstage/files/create-backend-dbs.sql deleted file mode 100644 index 043ff7daf5..0000000000 --- a/contrib/chart/backstage/files/create-backend-dbs.sql +++ /dev/null @@ -1,13 +0,0 @@ -{{ $backendDb := .Values.appConfig.backend.database.connection.database }} -{{ $lighthouseDb := .Values.lighthouse.database.connection.database }} -{{ $user := .Values.global.postgresql.postgresqlUsername }} - -grant all privileges on database {{ $backendDb }} to {{ $user }}; - -create database backstage_plugin_auth; -grant all privileges on database backstage_plugin_auth to {{ $user }}; - -{{ if not (eq $backendDb $lighthouseDb) }} -create database {{ $lighthouseDb }}; -grant all privileges on database {{ $lighthouseDb }} to {{ $user }}; -{{ end }} diff --git a/contrib/chart/backstage/templates/_helpers.tpl b/contrib/chart/backstage/templates/_helpers.tpl deleted file mode 100644 index 123d4dba5b..0000000000 --- a/contrib/chart/backstage/templates/_helpers.tpl +++ /dev/null @@ -1,286 +0,0 @@ -{{/* -Expand the name of the chart. -*/}} -{{- define "backstage.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "backstage.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "backstage.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Common App labels -*/}} -{{- define "backstage.app.labels" -}} -app.kubernetes.io/name: {{ include "backstage.name" . }}-app -helm.sh/chart: {{ include "backstage.chart" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end -}} - -{{/* -Common Backend labels -*/}} -{{- define "backstage.backend.labels" -}} -app.kubernetes.io/name: {{ include "backstage.name" . }}-backend -helm.sh/chart: {{ include "backstage.chart" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end -}} - -{{/* -Name for postgresql dependency -See https://github.com/helm/helm/issues/3920#issuecomment-686913512 -*/}} -{{- define "backstage.postgresql.fullname" -}} -{{ printf "%s-%s" .Release.Name .Values.postgresql.nameOverride }} -{{- end -}} - - -{{/* -Create the name of the service account to use for the app -*/}} -{{- define "backstage.app.serviceAccountName" -}} -{{- if .Values.app.serviceAccount.create -}} - {{ default "default" .Values.app.serviceAccount.name }} -{{- else -}} - {{ default "default" .Values.app.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{/* -Create the name of the service account to use for the backend -*/}} -{{- define "backstage.backend.serviceAccountName" -}} -{{- if .Values.backend.serviceAccount.create -}} - {{ default default .Values.backend.serviceAccount.name }} -{{- else -}} - {{ default "default" .Values.backend.serviceAccount.name }} -{{- end -}} -{{- end -}} - -{{/* -Path to the CA certificate file in the backend -*/}} -{{- define "backstage.backend.postgresCaFilename" -}} -{{ include "backstage.backend.postgresCaDir" . }}/{{- required "The name for the CA certificate file for postgresql is required" .Values.global.postgresql.caFilename }} -{{- end -}} -{{/* - -{{/* -Directory path to the CA certificate file in the backend -*/}} -{{- define "backstage.backend.postgresCaDir" -}} -{{- if .Values.appConfig.backend.database.connection.ssl.ca -}} - {{ .Values.appConfig.backend.database.connection.ssl.ca }} -{{- else -}} -/etc/postgresql -{{- end -}} -{{- end -}} -{{/* - -Path to the CA certificate file in lighthouse -*/}} -{{- define "backstage.lighthouse.postgresCaFilename" -}} -{{ include "backstage.lighthouse.postgresCaDir" . }}/{{- required "The name for the CA certificate file for postgresql is required" .Values.global.postgresql.caFilename }} -{{- end -}} - -{{/* -Directory path to the CA certificate file in lighthouse -*/}} -{{- define "backstage.lighthouse.postgresCaDir" -}} -{{- if .Values.lighthouse.database.pathToDatabaseCa -}} - {{ .Values.lighthouse.database.pathToDatabaseCa }} -{{- else -}} -/etc/postgresql -{{- end -}} -{{- end -}} -{{/* - -{{/* -Generate ca for postgresql -*/}} -{{- define "backstage.postgresql.generateCA" -}} -{{- $ca := .ca | default (genCA (include "backstage.postgresql.fullname" .) 365) -}} -{{- $_ := set . "ca" $ca -}} -{{- $ca.Cert -}} -{{- end -}} - -{{/* -Generate certificates for postgresql -*/}} -{{- define "generateCerts" -}} -{{- $postgresName := (include "backstage.postgresql.fullname" .) }} -{{- $altNames := list $postgresName ( printf "%s.%s" $postgresName .Release.Namespace ) ( printf "%s.%s.svc" ( $postgresName ) .Release.Namespace ) -}} -{{- $ca := .ca | default (genCA (include "backstage.postgresql.fullname" .) 365) -}} -{{- $_ := set . "ca" $ca -}} -{{- $cert := genSignedCert ( $postgresName ) nil $altNames 365 $ca -}} -tls.crt: {{ $cert.Cert | b64enc }} -tls.key: {{ $cert.Key | b64enc }} -{{- end -}} - -{{/* -Generate a password for the postgres user used for the connections from the backend and lighthouse -*/}} -{{- define "postgresql.generateUserPassword" -}} -{{- $pgPassword := .pgPassword | default ( randAlphaNum 12 ) -}} -{{- $_ := set . "pgPassword" $pgPassword -}} -{{ $pgPassword}} -{{- end -}} - -{{/* -Name of the backend service -*/}} -{{- define "backend.serviceName" -}} -{{ include "backstage.fullname" . }}-backend -{{- end -}} - -{{/* -Name of the frontend service -*/}} -{{- define "frontend.serviceName" -}} -{{ include "backstage.fullname" . }}-frontend -{{- end -}} - -{{/* -Name of the lighthouse backend service -*/}} -{{- define "lighthouse.serviceName" -}} -{{ include "backstage.fullname" . }}-lighthouse -{{- end -}} - -{{/* -Name of the postgresql service -*/}} -{{- define "postgresql.serviceName" -}} -{{- include "backstage.postgresql.fullname" . }} -{{- end -}} - -{{/* -Postgres host for lighthouse -*/}} -{{- define "lighthouse.postgresql.host" -}} -{{- if .Values.postgresql.enabled }} -{{- include "postgresql.serviceName" . }} -{{- else -}} -{{- required "A valid .Values.lighthouse.database.connection.host is required when postgresql is not enabled" .Values.lighthouse.database.connection.host -}} -{{- end -}} -{{- end -}} - -{{/* -Postgres host for the backend -*/}} -{{- define "backend.postgresql.host" -}} -{{- if .Values.postgresql.enabled }} -{{- include "postgresql.serviceName" . }} -{{- else -}} -{{- required "A valid .Values.appConfig.backend.database.connection.host is required when postgresql is not enabled" .Values.appConfig.backend.database.connection.host -}} -{{- end -}} -{{- end -}} - -{{/* -Postgres port for the backend -*/}} -{{- define "backend.postgresql.port" -}} -{{- if .Values.postgresql.enabled }} -{{- .Values.postgresql.service.port }} -{{- else if .Values.appConfig.backend.database.connection.port -}} -{{- .Values.appConfig.backend.database.connection.port }} -{{- else -}} -5432 -{{- end -}} -{{- end -}} - -{{/* -Postgres port for lighthouse -*/}} -{{- define "lighthouse.postgresql.port" -}} -{{- if .Values.postgresql.enabled }} -{{- .Values.postgresql.service.port }} -{{- else if .Values.lighthouse.database.connection.port -}} -{{- .Values.lighthouse.database.connection.port }} -{{- else -}} -5432 -{{- end -}} -{{- end -}} - -{{/* -Postgres user for backend -*/}} -{{- define "backend.postgresql.user" -}} -{{- if .Values.postgresql.enabled }} -{{- .Values.global.postgresql.postgresqlUsername }} -{{- else -}} -{{- required "A valid .Values.appConfig.backend.database.connection.user is required when postgresql is not enabled" .Values.appConfig.backend.database.connection.user -}} -{{- end -}} -{{- end -}} - -{{/* -Postgres user for lighthouse -*/}} -{{- define "lighthouse.postgresql.user" -}} -{{- if .Values.postgresql.enabled }} -{{- .Values.global.postgresql.postgresqlUsername }} -{{- else -}} -{{- required "A valid .Values.lighthouse.database.connection.user is required when postgresql is not enabled" .Values.lighthouse.database.connection.user -}} -{{- end -}} -{{- end -}} - -{{/* -Postgres password secret for backend -*/}} -{{- define "backend.postgresql.passwordSecret" -}} -{{- if .Values.postgresql.enabled }} -{{- template "backstage.postgresql.fullname" . }} -{{- else -}} -{{ $secretName := (printf "%s-backend-postgres" (include "backstage.fullname" . )) }} -{{- required "A valid .Values.appConfig.backend.database.connection.password is required when postgresql is not enabled" $secretName -}} -{{- end -}} -{{- end -}} - -{{/* -Postgres password for lighthouse -*/}} -{{- define "lighthouse.postgresql.passwordSecret" -}} -{{- if .Values.postgresql.enabled }} -{{- template "backstage.postgresql.fullname" . }} -{{- else -}} -{{ $secretName := (printf "%s-lighthouse-postgres" (include "backstage.fullname" . )) }} -{{- required "A valid .Values.lighthouse.database.connection.password is required when postgresql is not enabled" $secretName -}} -{{- end -}} -{{- end -}} - -{{/* -app-config file name -*/}} -{{- define "backstage.appConfigFilename" -}} -{{- "app-config.development.yaml" -}} -{{- end -}} diff --git a/contrib/chart/backstage/templates/backend-deployment.yaml b/contrib/chart/backstage/templates/backend-deployment.yaml deleted file mode 100644 index 0fa6b7fbe2..0000000000 --- a/contrib/chart/backstage/templates/backend-deployment.yaml +++ /dev/null @@ -1,98 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "backstage.fullname" . }}-backend - -spec: - replicas: {{ .Values.backend.replicaCount }} - - selector: - matchLabels: - app: backstage - component: backend - - template: - metadata: - annotations: - ad.datadoghq.com/backstage.logs: '[{"source":"backstage","service":"backend"}]' - labels: - app: backstage - component: backend - - spec: - {{- if .Values.dockerRegistrySecretName }} - imagePullSecrets: - - name: {{ .Values.dockerRegistrySecretName }} - {{- end}} - containers: - - name: {{ .Chart.Name }}-backend - command: ["node"] - args: - - "packages/backend" - - "--config" - - "app-config.yaml" - - "--config" - - {{ printf "/usr/src/app/%s" (include "backstage.appConfigFilename" .) | quote }} - image: {{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }} - imagePullPolicy: {{ .Values.backend.image.pullPolicy }} - ports: - - containerPort: {{ .Values.backend.containerPort }} - resources: - {{- toYaml .Values.backend.resources | nindent 12 }} - - envFrom: - - secretRef: - name: {{ include "backstage.fullname" . }}-backend - - configMapRef: - name: {{ include "backstage.fullname" . }}-app-env - - configMapRef: - name: {{ include "backstage.fullname" . }}-auth - env: - - name: NODE_ENV - value: {{ .Values.backend.nodeEnv | default "development" }} - - name: APP_CONFIG_backend_database_connection_password - valueFrom: - secretKeyRef: - name: {{ include "backend.postgresql.passwordSecret" .}} - key: postgresql-password - volumeMounts: - {{- if .Values.backend.postgresCertMountEnabled }} - - name: postgres-ca - mountPath: {{ include "backstage.backend.postgresCaDir" . }} - {{- end }} - - name: app-config - mountPath: {{ printf "/usr/src/app/%s" (include "backstage.appConfigFilename" .) }} - subPath: {{ include "backstage.appConfigFilename" . }} - - volumes: - {{- if .Values.backend.postgresCertMountEnabled }} - - name: postgres-ca - configMap: - name: {{ include "backstage.fullname" . }}-postgres-ca - {{- end }} - - name: app-config - configMap: - name: {{ include "backstage.fullname" . }}-app-config - - {{- if .Values.global.nodeSelector }} - nodeSelector: {{- toYaml .Values.global.nodeSelector | nindent 8 }} - {{- end }} - -{{- if .Values.backend.enabled }} ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ include "backend.serviceName" . }} - -spec: - ports: - - port: 80 - targetPort: {{ .Values.backend.containerPort }} - - selector: - app: backstage - component: backend - - type: {{ .Values.backend.serviceType }} -{{- end }} diff --git a/contrib/chart/backstage/templates/backend-secret.yaml b/contrib/chart/backstage/templates/backend-secret.yaml deleted file mode 100644 index 299d893ec4..0000000000 --- a/contrib/chart/backstage/templates/backend-secret.yaml +++ /dev/null @@ -1,24 +0,0 @@ -{{- if .Values.backend.enabled }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "backstage.fullname" . }}-backend -type: Opaque -stringData: - AUTH_GOOGLE_CLIENT_SECRET: {{ .Values.auth.google.clientSecret }} - AUTH_GITHUB_CLIENT_SECRET: {{ .Values.auth.github.clientSecret }} - AUTH_GITLAB_CLIENT_SECRET: {{ .Values.auth.gitlab.clientSecret }} - AUTH_OKTA_CLIENT_SECRET: {{ .Values.auth.okta.clientSecret }} - AUTH_OAUTH2_CLIENT_SECRET: {{ .Values.auth.oauth2.clientSecret }} - AUTH_AUTH0_CLIENT_SECRET: {{ .Values.auth.auth0.clientSecret }} - AUTH_MICROSOFT_CLIENT_SECRET: {{ .Values.auth.microsoft.clientSecret }} - SENTRY_TOKEN: {{ .Values.auth.sentryToken }} - ROLLBAR_ACCOUNT_TOKEN: {{ .Values.auth.rollbarAccountToken }} - CIRCLECI_AUTH_TOKEN: {{ .Values.auth.circleciAuthToken }} - GITHUB_TOKEN: {{ .Values.auth.githubToken }} - GITLAB_TOKEN: {{ .Values.auth.gitlabToken }} - AZURE_TOKEN: {{ .Values.auth.azure.api.token }} - NEW_RELIC_REST_API_KEY: {{ .Values.auth.newRelicRestApiKey }} - TRAVISCI_AUTH_TOKEN: {{ .Values.auth.travisciAuthToken }} - PAGERDUTY_TOKEN: {{ .Values.auth.pagerdutyToken }} -{{- end }} diff --git a/contrib/chart/backstage/templates/backstage-app-config.yaml b/contrib/chart/backstage/templates/backstage-app-config.yaml deleted file mode 100644 index 061fc3285b..0000000000 --- a/contrib/chart/backstage/templates/backstage-app-config.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "backstage.fullname" . }}-app-config -data: -{{ include "backstage.appConfigFilename" . | indent 2 }}: | -{{ tpl (.Files.Get "files/app-config.development.yaml.tpl") . | indent 4 }} ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "backstage.fullname" . }}-app-env -data: - APP_CONFIG_app_baseUrl: {{ .Values.appConfig.app.baseUrl | quote | quote }} - APP_CONFIG_app_title: {{ .Values.appConfig.app.title | quote | quote }} - APP_CONFIG_backend_baseUrl: {{ .Values.appConfig.backend.baseUrl | quote | quote }} - APP_CONFIG_backend_cors_origin: {{ .Values.appConfig.backend.cors.origin | quote | quote }} - APP_CONFIG_techdocs_storageUrl: {{ .Values.appConfig.techdocs.storageUrl | quote | quote }} - APP_CONFIG_techdocs_requestUrl: {{ .Values.appConfig.techdocs.requestUrl | quote | quote }} - APP_CONFIG_lighthouse_baseUrl: {{ .Values.appConfig.lighthouse.baseUrl | quote | quote }} - APP_CONFIG_backend_database_connection_ssl_rejectUnauthorized: "false" - APP_CONFIG_auth_providers_github_development_appOrigin: {{ .Values.appConfig.auth.providers.github.development.appOrigin | quote | quote }} - APP_CONFIG_auth_providers_google_development_appOrigin: {{ .Values.appConfig.auth.providers.google.development.appOrigin | quote | quote }} - APP_CONFIG_auth_providers_gitlab_development_appOrigin: {{ .Values.appConfig.auth.providers.gitlab.development.appOrigin | quote | quote }} - APP_CONFIG_auth_providers_okta_development_appOrigin: {{ .Values.appConfig.auth.providers.okta.development.appOrigin | quote | quote }} - APP_CONFIG_auth_providers_oauth2_development_appOrigin: {{ .Values.appConfig.auth.providers.oauth2.development.appOrigin | quote | quote }} - diff --git a/contrib/chart/backstage/templates/backstage-auth-config.yaml b/contrib/chart/backstage/templates/backstage-auth-config.yaml deleted file mode 100644 index 7dcf8379aa..0000000000 --- a/contrib/chart/backstage/templates/backstage-auth-config.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "backstage.fullname" . }}-auth -data: - AUTH_GOOGLE_CLIENT_ID: {{ .Values.auth.google.clientId }} - AUTH_GITHUB_CLIENT_ID: {{ .Values.auth.github.clientId }} - AUTH_GITLAB_CLIENT_ID: {{ .Values.auth.gitlab.clientId }} - # This should not be prefixed with AUTH_. This could be a typo in the Backstage app config. - # Regardless, it is not decided by me. - GITLAB_BASE_URL: {{ .Values.auth.gitlab.baseUrl }} - AUTH_OKTA_CLIENT_ID: {{ .Values.auth.okta.clientId }} - AUTH_OKTA_AUDIENCE: {{ .Values.auth.okta.audience }} - AUTH_OAUTH2_CLIENT_ID: {{ .Values.auth.oauth2.clientId }} - AUTH_OAUTH2_AUTH_URL: {{ .Values.auth.oauth2.authUrl }} - AUTH_OAUTH2_TOKEN_URL: {{ .Values.auth.oauth2.tokenUrl }} - AUTH_AUTH0_CLIENT_ID: {{ .Values.auth.auth0.clientId }} - AUTH_AUTH0_DOMAIN: {{ .Values.auth.auth0.domain }} - AUTH_MICROSOFT_CLIENT_ID: {{ .Values.auth.microsoft.clientId }} - AUTH_MICROSOFT_TENANT_ID: {{ .Values.auth.microsoft.tenantId }} - diff --git a/contrib/chart/backstage/templates/frontend-deployment.yaml b/contrib/chart/backstage/templates/frontend-deployment.yaml deleted file mode 100644 index dba4e2c96a..0000000000 --- a/contrib/chart/backstage/templates/frontend-deployment.yaml +++ /dev/null @@ -1,66 +0,0 @@ -{{- if .Values.frontend.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "backstage.fullname" . }}-frontend - -spec: - replicas: {{ .Values.frontend.replicaCount }} - - selector: - matchLabels: - app: backstage - component: frontend - - template: - metadata: - annotations: - ad.datadoghq.com/backstage.logs: '[{"source":"backstage","service":"frontend"}]' - labels: - app: backstage - component: frontend - - spec: - {{- if .Values.dockerRegistrySecretName }} - imagePullSecrets: - - name: {{ .Values.dockerRegistrySecretName }} - {{- end}} - containers: - - name: {{ .Chart.Name }}-frontend - image: {{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }} - imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} - ports: - - containerPort: {{ .Values.frontend.containerPort }} - resources: - {{- toYaml .Values.frontend.resources | nindent 12 }} - envFrom: - - configMapRef: - name: {{ include "backstage.fullname" . }}-app-env - volumeMounts: - - name: app-config - mountPath: {{ printf "/usr/share/nginx/html/static/%s" (include "backstage.appConfigFilename" .) }} - subPath: {{ include "backstage.appConfigFilename" . }} - volumes: - - name: app-config - configMap: - name: {{ include "backstage.fullname" . }}-app-config - {{- if .Values.global.nodeSelector }} - nodeSelector: {{- toYaml .Values.global.nodeSelector | nindent 8 }} - {{- end }} ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ include "frontend.serviceName" . }} - -spec: - ports: - - port: 80 - targetPort: {{ .Values.frontend.containerPort }} - - selector: - app: backstage - component: frontend - - type: {{ .Values.frontend.serviceType }} -{{- end }} diff --git a/contrib/chart/backstage/templates/ingress.yaml b/contrib/chart/backstage/templates/ingress.yaml deleted file mode 100644 index 7e008c5d9d..0000000000 --- a/contrib/chart/backstage/templates/ingress.yaml +++ /dev/null @@ -1,150 +0,0 @@ -{{- $frontendUrl := urlParse .Values.appConfig.app.baseUrl}} -{{- $backendUrl := urlParse .Values.appConfig.backend.baseUrl}} -{{- $lighthouseUrl := urlParse .Values.appConfig.lighthouse.baseUrl}} - -{{/* Determine the api type for the ingress */}} -{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} -apiVersion: networking.k8s.io/v1 -{{- else -}} -apiVersion: networking.k8s.io/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ include "backstage.fullname" . }}-ingress - annotations: - {{- if .Values.issuer.email }} - cert-manager.io/cluster-issuer: {{ .Values.issuer.clusterIssuer }} - {{- end }} - nginx.ingress.kubernetes.io/ssl-redirect: "false" - nginx.ingress.kubernetes.io/configuration-snippet: | - if ($scheme = https) { - add_header Strict-Transport-Security "max-age=0;"; - } - {{- toYaml .Values.ingress.annotations | nindent 4 }} -spec: - tls: - - secretName: {{ include "backstage.fullname" . }}-tls - hosts: - - {{ $frontendUrl.host }} - - {{ $backendUrl.host }} - - {{ $lighthouseUrl.host }} - rules: - - host: {{ $frontendUrl.host }} - http: - paths: - - path: / - {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} - pathType: Prefix - {{- end }} - backend: - {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} - service: - name: {{ include "frontend.serviceName" . }} - port: - number: 80 - {{- else -}} - serviceName: {{ include "frontend.serviceName" . }} - servicePort: 80 - {{- end }} - {{/* Route the backend inside the same hostname as the frontend when they are the same */}} - {{- if eq $frontendUrl.host $backendUrl.host}} - - path: /api/ - {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} - pathType: Prefix - {{- end }} - backend: - {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} - service: - name: {{ include "backend.serviceName" . }} - port: - number: 80 - {{- else -}} - serviceName: {{ include "backend.serviceName" . }} - servicePort: 80 - {{- end }} - {{/* Route the backend through a different host */}} - {{- else -}} - - host: {{ $backendUrl.host }} - http: - paths: - - path: {{ $backendUrl.path | default "/" }} - {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} - pathType: Prefix - {{- end }} - backend: - {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} - service: - name: {{ include "backend.serviceName" . }} - port: - number: 80 - {{- else -}} - serviceName: {{ include "backend.serviceName" . }} - servicePort: 80 - {{- end }} - {{- end }} - -{{/* Route lighthouse through a different host */}} -{{- if not ( eq $frontendUrl.host $lighthouseUrl.host ) }} - - host: {{ $lighthouseUrl.host }} - http: - paths: - - path: {{ $lighthouseUrl.path | default "/" }} - {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} - pathType: Prefix - {{- end }} - backend: - {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} - service: - name: {{ include "lighthouse.serviceName" . }} - port: - number: 80 - {{- else -}} - serviceName: {{ include "lighthouse.serviceName" . }} - servicePort: 80 - {{- end }} -{{- else }} -{{/* Route lighthouse by path with re-write rules when it is hosted under the same hostname */}} ---- -{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} -apiVersion: networking.k8s.io/v1 -{{- else -}} -apiVersion: networking.k8s.io/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ include "backstage.fullname" . }}-ingress-lighthouse - annotations: - {{- if .Values.issuer.email }} - cert-manager.io/cluster-issuer: {{ .Values.issuer.clusterIssuer }} - {{- end }} - nginx.ingress.kubernetes.io/rewrite-target: /$2 - nginx.ingress.kubernetes.io/ssl-redirect: "false" - nginx.ingress.kubernetes.io/configuration-snippet: | - if ($scheme = https) { - add_header Strict-Transport-Security "max-age=0;"; - } - {{- toYaml .Values.ingress.annotations | nindent 4 }} -spec: - tls: - - secretName: {{ include "backstage.fullname" . }}-tls - hosts: - - {{ $lighthouseUrl.host }} - rules: - - host: {{ $frontendUrl.host }} - http: - paths: - - path: {{$lighthouseUrl.path}}(/|$)(.*) - {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} - pathType: Prefix - {{- end }} - backend: - {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }} - service: - name: {{ include "lighthouse.serviceName" . }} - port: - number: 80 - {{- else -}} - serviceName: {{ include "lighthouse.serviceName" . }} - servicePort: 80 - {{- end }} -{{- end }} diff --git a/contrib/chart/backstage/templates/issuer.yaml b/contrib/chart/backstage/templates/issuer.yaml deleted file mode 100644 index d129c8c701..0000000000 --- a/contrib/chart/backstage/templates/issuer.yaml +++ /dev/null @@ -1,19 +0,0 @@ -{{- if (and (.Capabilities.APIVersions.Has "cert-manager.io/v1alpha2") .Values.issuer.email ) -}} -{{/* Only install issuer if it doesn't already exist in the cluster */}} -{{- if not ( lookup "cert-manager.io/v1alpha2" "ClusterIssuer" "" .Values.issuer.clusterIssuer ) }} -apiVersion: cert-manager.io/v1alpha2 -kind: ClusterIssuer -metadata: - name: {{ .Values.issuer.clusterIssuer }} -spec: - acme: - server: https://acme-v02.api.letsencrypt.org/directory - email: {{ required "expected a valid .Values.issuer.email to enable ClusterIssuer" .Values.issuer.email }} - privateKeySecretRef: - name: {{ required "expected .Values.issuer.cluster-issuer to not be empty (letsencrypt-prod | letsencrypt-staging)" .Values.issuer.clusterIssuer }} - solvers: - - http01: - ingress: - class: nginx -{{- end -}} -{{- end -}} diff --git a/contrib/chart/backstage/templates/lighthouse-config.yaml b/contrib/chart/backstage/templates/lighthouse-config.yaml deleted file mode 100644 index 7b8cad42dc..0000000000 --- a/contrib/chart/backstage/templates/lighthouse-config.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.lighthouse.enabled }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "backstage.fullname" . -}}-lighthouse -data: - PGDATABASE: {{ .Values.lighthouse.database.connection.database | quote }} - PGUSER: {{ include "lighthouse.postgresql.user" . | quote }} - PGPORT: {{ include "lighthouse.postgresql.port" . | quote }} - PGHOST: {{ include "lighthouse.postgresql.host" . | quote }} - PGPATH_TO_CA: {{ include "backstage.lighthouse.postgresCaFilename" . | quote }} -{{- end }} \ No newline at end of file diff --git a/contrib/chart/backstage/templates/lighthouse-deployment.yaml b/contrib/chart/backstage/templates/lighthouse-deployment.yaml deleted file mode 100644 index 849c48d7a6..0000000000 --- a/contrib/chart/backstage/templates/lighthouse-deployment.yaml +++ /dev/null @@ -1,86 +0,0 @@ -{{- if .Values.lighthouse.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "backstage.fullname" . }}-lighthouse - -spec: - replicas: {{ .Values.lighthouse.replicaCount }} - - selector: - matchLabels: - app: backstage - component: lighthouse-audit-service - - template: - metadata: - annotations: - ad.datadoghq.com/backstage.logs: '[{"source":"backstage","service":"lighthouse"}]' - labels: - app: backstage - component: lighthouse-audit-service - - spec: - {{- if .Values.dockerRegistrySecretName }} - imagePullSecrets: - - name: {{ .Values.dockerRegistrySecretName }} - {{- end}} - containers: - - name: lighthouse-audit-service - image: {{ .Values.lighthouse.image.repository }}:{{ .Values.lighthouse.image.tag }} - imagePullPolicy: {{ .Values.lighthouse.image.pullPolicy }} - ports: - - containerPort: {{ .Values.lighthouse.containerPort }} - resources: - {{- toYaml .Values.lighthouse.resources | nindent 12 }} - - envFrom: - - configMapRef: - name: {{ include "backstage.fullname" . -}}-lighthouse - - configMapRef: - name: {{ include "backstage.fullname" . }}-app-env - - env: - - name: LAS_PORT - value: {{ .Values.lighthouse.containerPort | quote }} - - name: LAS_CORS - value: "true" - - name: PGPASSWORD - valueFrom: - secretKeyRef: - name: {{ include "lighthouse.postgresql.passwordSecret" . }} - key: postgresql-password - - {{- if .Values.lighthouse.postgresCertMountEnabled }} - volumeMounts: - - name: postgres-ca - mountPath: {{ include "backstage.lighthouse.postgresCaDir" . }} - {{- end }} - - {{- if .Values.lighthouse.postgresCertMountEnabled }} - volumes: - - name: postgres-ca - configMap: - name: {{ include "backstage.fullname" . }}-postgres-ca - {{- end }} - - {{- if .Values.global.nodeSelector }} - nodeSelector: {{- toYaml .Values.global.nodeSelector | nindent 8 }} - {{- end }} ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ include "lighthouse.serviceName" . }} - -spec: - ports: - - port: 80 - targetPort: {{ .Values.lighthouse.containerPort }} - - selector: - app: backstage - component: lighthouse-audit-service - - type: {{ .Values.lighthouse.serviceType }} -{{- end }} diff --git a/contrib/chart/backstage/templates/postgresql-ca-config.yaml b/contrib/chart/backstage/templates/postgresql-ca-config.yaml deleted file mode 100644 index 2631989f4e..0000000000 --- a/contrib/chart/backstage/templates/postgresql-ca-config.yaml +++ /dev/null @@ -1,21 +0,0 @@ -{{- if .Values.postgresql.enabled }} ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "backstage.fullname" . }}-postgres-ca - labels: - app: {{ include "backstage.postgresql.fullname" . }} - release: {{ .Release.Name }} - annotations: - "helm.sh/hook": "pre-install" - "helm.sh/hook-delete-policy": "before-hook-creation" -data: - {{ .Values.global.postgresql.caFilename }}: | -{{ include "backstage.postgresql.generateCA" . | indent 4}} -{{- else }} -{{- $caConfig := printf "%s-postgres-ca" (include "backstage.fullname" .) }} -{{- if not ( lookup "v1" "ConfigMap" .Release.Namespace $caConfig ) }} -{{- fail (printf "\n\nPlease create the '%s' configmap with the CA certificate for your existing postgresql: kubectl create configmap %s --from-file=ca.crt" $caConfig $caConfig) }} -{{- end }} -{{- end }} \ No newline at end of file diff --git a/contrib/chart/backstage/templates/postgresql-certs-secret.yaml b/contrib/chart/backstage/templates/postgresql-certs-secret.yaml deleted file mode 100644 index c6845caa7e..0000000000 --- a/contrib/chart/backstage/templates/postgresql-certs-secret.yaml +++ /dev/null @@ -1,16 +0,0 @@ -{{- if .Values.postgresql.enabled }} ---- -apiVersion: v1 -kind: Secret -type: kubernetes.io/tls -metadata: - name: {{ required ".Values.postgresql.tls.certificatesSecret is required" .Values.postgresql.tls.certificatesSecret }} - labels: - app: {{ include "backstage.postgresql.fullname" . }} - release: {{ .Release.Name }} - annotations: - "helm.sh/hook": "pre-install" - "helm.sh/hook-delete-policy": "before-hook-creation" -data: -{{ include "generateCerts" . | indent 2 }} -{{- end }} diff --git a/contrib/chart/backstage/templates/postgresql-initdb-secret.yaml b/contrib/chart/backstage/templates/postgresql-initdb-secret.yaml deleted file mode 100644 index d1c446f79f..0000000000 --- a/contrib/chart/backstage/templates/postgresql-initdb-secret.yaml +++ /dev/null @@ -1,14 +0,0 @@ -{{- if .Values.postgresql.enabled }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ required ".Values.postgresql.initdbScriptsSecret is required" .Values.postgresql.initdbScriptsSecret }} - annotations: - "helm.sh/hook": "pre-install" - "helm.sh/hook-delete-policy": "before-hook-creation" -type: Opaque -data: - create-backend-dbs.sql: | - {{ tpl (.Files.Get "files/create-backend-dbs.sql") . | b64enc }} -{{- end }} - diff --git a/contrib/chart/backstage/templates/postgresql-password-backend-secret.yaml b/contrib/chart/backstage/templates/postgresql-password-backend-secret.yaml deleted file mode 100644 index a71cef7b21..0000000000 --- a/contrib/chart/backstage/templates/postgresql-password-backend-secret.yaml +++ /dev/null @@ -1,15 +0,0 @@ -{{- if not .Values.postgresql.enabled }} ---- -apiVersion: v1 -kind: Secret -type: Opaque -metadata: - name: {{ include "backend.postgresql.passwordSecret" . }} - labels: - release: {{ .Release.Name }} - annotations: - "helm.sh/hook": "pre-install,pre-upgrade" - "helm.sh/hook-delete-policy": "before-hook-creation" -data: - postgresql-password: {{ .Values.appConfig.backend.database.connection.password | b64enc }} -{{- end }} diff --git a/contrib/chart/backstage/templates/postgresql-password-lighthouse-secret.yaml b/contrib/chart/backstage/templates/postgresql-password-lighthouse-secret.yaml deleted file mode 100644 index e7374e70db..0000000000 --- a/contrib/chart/backstage/templates/postgresql-password-lighthouse-secret.yaml +++ /dev/null @@ -1,17 +0,0 @@ -{{- if .Values.lighthouse.enabled }} -{{- if not .Values.postgresql.enabled }} ---- -apiVersion: v1 -kind: Secret -type: Opaque -metadata: - name: {{ include "lighthouse.postgresql.passwordSecret" . }} - labels: - release: {{ .Release.Name }} - annotations: - "helm.sh/hook": "pre-install,pre-upgrade" - "helm.sh/hook-delete-policy": "before-hook-creation" -data: - postgresql-password: {{ .Values.lighthouse.database.connection.password | b64enc }} -{{- end }} -{{- end }} diff --git a/contrib/chart/backstage/templates/tests/test-app-connection.yaml b/contrib/chart/backstage/templates/tests/test-app-connection.yaml deleted file mode 100644 index 93f51f3f09..0000000000 --- a/contrib/chart/backstage/templates/tests/test-app-connection.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- -apiVersion: v1 -kind: Pod -metadata: - name: {{ include "backstage.fullname" . -}}-test-app-connection - annotations: - "helm.sh/hook": test -spec: - containers: - - name: test-app - image: busybox - command: - - /bin/sh - - -ecx - - | - echo -e "===== Testing the connection with the frontend...\n" - wget -q -O - {{ printf "%s.%s" (include "frontend.serviceName" .) .Release.Namespace | quote }} - echo -e "\n\n===== Testing the connection with the backend...\n" - wget -q -O - {{ printf "http://%s.%s/catalog/entities" (include "backend.serviceName" .) .Release.Namespace | quote }} - echo -e "\n\n===== Testing the connection with the lighthouse plugin...\n" - wget -q -O - {{ printf "%s.%s/v1/audits" (include "lighthouse.serviceName" .) .Release.Namespace | quote }} - echo -e "\n" - restartPolicy: Never \ No newline at end of file diff --git a/contrib/chart/backstage/templates/tests/test-postgresql.yaml b/contrib/chart/backstage/templates/tests/test-postgresql.yaml deleted file mode 100644 index 8eec1db452..0000000000 --- a/contrib/chart/backstage/templates/tests/test-postgresql.yaml +++ /dev/null @@ -1,33 +0,0 @@ -{{- if .Values.postgresql.enabled}} ---- -apiVersion: v1 -kind: Pod -metadata: - name: {{ include "backstage.name" . -}}-test-postgres - annotations: - "helm.sh/hook": test -spec: - containers: - - name: postgresql-client - image: bitnami/postgresql - env: - - name: PG_HOST - value: {{ include "postgresql.serviceName" . | quote }} - - name: PG_PORT - value: {{ .Values.postgresql.service.port | quote }} - - name: PG_USER - value: {{ .Values.global.postgresql.postgresqlUsername | quote }} - - name: PGPASSWORD - valueFrom: - secretKeyRef: - name: {{ include "backend.postgresql.passwordSecret" .}} - key: postgresql-password - - name: PG_DBNAME - value: {{ .Values.appConfig.backend.database.connection.database }} - command: - - /bin/bash - - -ecx - - | - psql --host=$PG_HOST --port=$PG_PORT --username=$PG_USER --dbname=$PG_DBNAME --no-password - restartPolicy: Never -{{- end }} \ No newline at end of file diff --git a/contrib/chart/backstage/values.yaml b/contrib/chart/backstage/values.yaml deleted file mode 100644 index cd80fd954b..0000000000 --- a/contrib/chart/backstage/values.yaml +++ /dev/null @@ -1,216 +0,0 @@ -# Default values for backstage. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -frontend: - enabled: false - replicaCount: 1 - image: - repository: martinaif/backstage-k8s-demo-frontend - tag: test1 - pullPolicy: IfNotPresent - containerPort: 80 - serviceType: ClusterIP - resources: - requests: - memory: 128Mi - limits: - memory: 256Mi - -backend: - enabled: true - nodeEnv: development - demoData: true - replicaCount: 1 - image: - repository: martinaif/backstage-k8s-demo-backend - tag: 20210423T1550 - pullPolicy: IfNotPresent - containerPort: 7007 - serviceType: ClusterIP - postgresCertMountEnabled: true - resources: - requests: - memory: 512Mi - limits: - memory: 1024Mi - -lighthouse: - enabled: true - replicaCount: 1 - image: - repository: roadiehq/lighthouse-audit-service - tag: latest - pullPolicy: IfNotPresent - containerPort: 3003 - serviceType: ClusterIP - postgresCertMountEnabled: true - resources: - requests: - memory: 128Mi - limits: - memory: 256Mi - database: - connection: - port: - host: - user: - password: - database: lighthouse_audit_service - pathToDatabaseCa: - -nameOverride: '' -fullnameOverride: '' - -ingress: - annotations: - kubernetes.io/ingress.class: nginx - -issuer: - email: - clusterIssuer: 'letsencrypt-staging' - -global: - postgresql: - postgresqlUsername: backend-user - caFilename: ca.crt - nodeSelector: {} - -postgresql: - enabled: true - nameOverride: postgresql - tls: - enabled: true - certificatesSecret: backstage-postgresql-certs - certFilename: tls.crt - certKeyFilename: tls.key - volumePermissions: - enabled: true - initdbScriptsSecret: backstage-postgresql-initdb - -appConfig: - app: - baseUrl: https://demo.example.com - title: Backstage - backend: - baseUrl: https://demo.example.com - listen: - port: 7007 - cors: - origin: https://demo.example.com - database: - client: pg - connection: - database: backstage_plugin_catalog - host: - user: - port: - password: - ssl: - rejectUnauthorized: false - ca: - sentry: - organization: example-org-name - techdocs: - storageUrl: https://demo.example.com/api/techdocs/static/docs - requestUrl: https://demo.example.com/api/techdocs - lighthouse: - baseUrl: https://demo.example.com/lighthouse-api - rollbar: - organization: example-org-name - - # Auth config has recently moved into the app config file in upstream Backstage. However, - # most of this config simply mandates that items like the client id and client secret should - # be picked up from the environment variables named below. Those environment variables are - # set in this helm controlled environment by the 'auth' configuration below this section. - # Thus, the only key in this config which directly controls an app config is the - # auth.providers.github.development.appOrigin property. - auth: - providers: - google: - development: - appOrigin: 'http://localhost:3000/' - secure: false - clientId: ${AUTH_GOOGLE_CLIENT_ID} - clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET} - github: - development: - appOrigin: 'http://localhost:3000/' - secure: false - clientId: ${AUTH_GITHUB_CLIENT_ID} - clientSecret: ${AUTH_GITHUB_CLIENT_SECRET} - enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL} - gitlab: - development: - appOrigin: 'http://localhost:3000/' - secure: false - clientId: ${AUTH_GITLAB_CLIENT_ID} - clientSecret: ${AUTH_GITLAB_CLIENT_SECRET} - audience: ${GITLAB_BASE_URL} - okta: - development: - appOrigin: 'http://localhost:3000/' - secure: false - clientId: ${AUTH_OKTA_CLIENT_ID} - clientSecret: ${AUTH_OKTA_CLIENT_SECRET} - audience: ${AUTH_OKTA_AUDIENCE} - oauth2: - development: - appOrigin: 'http://localhost:3000/' - secure: false - clientId: ${AUTH_OAUTH2_CLIENT_ID} - clientSecret: ${AUTH_OAUTH2_CLIENT_SECRET} - authorizationURL: ${AUTH_OAUTH2_AUTH_URL} - tokenURL: ${AUTH_OAUTH2_TOKEN_URL} - auth0: - development: - clientId: ${AUTH_AUTH0_CLIENT_ID} - clientSecret: ${AUTH_AUTH0_CLIENT_SECRET} - domain: ${AUTH_AUTH0_DOMAIN} - microsoft: - development: - clientId: ${AUTH_MICROSOFT_CLIENT_ID} - clientSecret: ${AUTH_MICROSOFT_CLIENT_SECRET} - tenantId: ${AUTH_MICROSOFT_TENANT_ID} - -auth: - google: - clientId: a - clientSecret: a - github: - clientId: c - clientSecret: c - gitlab: - clientId: b - clientSecret: b - baseUrl: b - okta: - clientId: b - clientSecret: b - audience: b - oauth2: - clientId: b - clientSecret: b - authUrl: b - tokenUrl: b - auth0: - clientId: b - clientSecret: b - domain: b - microsoft: - clientId: f - clientSecret: f - tenantId: f - azure: - api: - token: h - sentryToken: e - rollbarAccountToken: f - # This is a 'Personal Access Token' - circleciAuthToken: r - # Used by the scaffolder to create GitHub repos. Must have 'repo' scope. - githubToken: g - gitlabToken: g - newRelicRestApiKey: r - travisciAuthToken: fake-travis-ci-auth-token - pagerdutyToken: h diff --git a/contrib/terraform/techdocs-s3-storage/readme.md b/contrib/terraform/techdocs-s3-storage/readme.md index 9615cc8d13..b4968f7c09 100644 --- a/contrib/terraform/techdocs-s3-storage/readme.md +++ b/contrib/terraform/techdocs-s3-storage/readme.md @@ -1 +1 @@ -This terraform file should create a S3 bucket and setup IAM with a user with an inline policy which gives the user access to the bucket. After you have created the bucket, user and policy you should go to the user in the AWS console and create an access key. This access key should be used as the env variables in step 3a [here](https://backstage.io/docs/features/techdocs/using-cloud-storage#configuring-aws-s3-bucket-with-techdocs). +This Terraform file should create an S3 bucket and set up IAM with a user and an inline policy that authorises access to the bucket. After you've set up the bucket, user, and policy, go to the user's AWS dashboard and create an access key. This access key should be used to configure the environment variables in step 3a [here](https://backstage.io/docs/features/techdocs/using-cloud-storage#configuring-aws-s3-bucket-with-techdocs). diff --git a/docs/architecture-decisions/adr011-plugin-package-structure.md b/docs/architecture-decisions/adr011-plugin-package-structure.md index 2def903dfc..8e4b5435f9 100644 --- a/docs/architecture-decisions/adr011-plugin-package-structure.md +++ b/docs/architecture-decisions/adr011-plugin-package-structure.md @@ -7,8 +7,7 @@ description: Architecture Decision Record (ADR) for Plugin Package Structure ## Context -A core feature of Backstage is the extensibility via plugins. The Backstage -repository is open for contributions of plugins. Even most of the core features +A core feature of Backstage is the extensibility via plugins. Even most of the core features are implemented as plugins. A plugin consists of one or multiple packages in the `plugins/` directory. Up till now, we have a simple conventions for naming plugin packages: Plugins are named `x`, with the option of having a related diff --git a/docs/assets/backend-system/scaled-deployments.drawio.svg b/docs/assets/backend-system/scaled-deployments.drawio.svg new file mode 100644 index 0000000000..85060af236 --- /dev/null +++ b/docs/assets/backend-system/scaled-deployments.drawio.svg @@ -0,0 +1,429 @@ + + + + + + + + + + + + + + + + + + + +
+
+
+ Backend Deployment A +
+
+
+
+ + Backend Deployme... + +
+
+ + + + +
+
+
+ Frontend App +
+
+
+
+ + Frontend App + +
+
+ + + + + + + +
+
+
+ All other traffic +
+
+
+
+ + All other traffic + +
+
+ + + + + +
+
+
+ /api/{techdocs,scaffolder}/* +
+
+
+
+ + /api/{techdocs,scaffolder}/* + +
+
+ + + + +
+
+
+ (Authenticating) Reverse Proxy +
+
+
+
+ + (Authenticating)... + +
+
+ + + + + + + + +
+
+
+ Backend Deployment C +
+
+
+
+ + Backend Deployme... + +
+
+ + + + + + + + +
+
+
+ Backend Deployment B +
+
+
+
+ + Backend Deployme... + +
+
+ + + + + + +
+
+
+ app +
+
+
+
+ + app + +
+
+ + + + + + +
+
+
+ auth +
+
+
+
+ + auth + +
+
+ + + + + + +
+
+
+ catalog +
+
+
+
+ + catalog + +
+
+ + + + + + +
+
+
+ search +
+
+
+
+ + search + +
+
+ + + + + + +
+
+
+ techdocs +
+
+
+
+ + techdocs + +
+
+ + + + + + +
+
+
+ scaffolder +
+
+
+
+ + scaffolder + +
+
+ + + + + + +
+
+
+ proxy +
+
+
+
+ + proxy + +
+
+ + + + +
+
+
+ Plugins +
+
+
+
+ + Plugi... + +
+
+ + + + + + +
+
+
+ /api/{catalog,search}/* +
+
+
+
+ + /api/{catalog,search}/* + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + Horizontally Scaled + +
+ + Deployments + +
+
+
+
+ + Horizontally Scale... + +
+
+ + + + +
+
+
+ Logical Databases +
+
+
+
+ + Logical Databases + +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ DBMS Instances +
+
+
+
+ + DBMS Instances + +
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/assets/frontend-system/architecture-utility-apis.drawio.svg b/docs/assets/frontend-system/architecture-utility-apis.drawio.svg new file mode 100644 index 0000000000..a7008b1126 --- /dev/null +++ b/docs/assets/frontend-system/architecture-utility-apis.drawio.svg @@ -0,0 +1,314 @@ + + + + + + + +
+
+
+ App +
+
+
+
+ + App + +
+
+ + + + + +
+
+
+ provides +
+
+
+
+ + provides + +
+
+ + + + +
+
+
+ Catalog Plugin +
+ + plugin:catalog + +
+
+
+
+ + Catalog Plugin... + +
+
+ + + + + +
+
+
+ + replaces +
+ implementation +
+
+
+
+
+
+ + replaces... + +
+
+ + + + +
+
+
+ Extension Overrides +
+
+
+
+ + Extension Overr... + +
+
+ + + + + +
+
+
+ provides +
+
+
+
+ + provides + +
+
+ + + + +
+
+
+ Todo Plugin +
+ + plugin:todo + +
+
+
+
+ + Todo Plugin... + +
+
+ + + + +
+
+
+ Backstage Frontend Framework +
+
+
+
+ + Backstage Frontend Framework + +
+
+ + + + + + +
+
+
+ + Catalog Client Utility API +
+ + api:plugin.catalog.service + +
+
+
+
+
+ + Catalog Client Utility API... + +
+
+ + + + +
+
+
+ + Todo Utility API +
+ + api:plugin.todo.api + +
+
+
+
+
+ + Todo Utility API... + +
+
+ + + + + + + + + +
+
+
+ + installs + +
+
+
+
+ + installs + +
+
+ + + + +
+
+
+ + Fetch Utility API +
+ + api:core.fetch + +
+
+
+
+
+ + Fetch Utility API... + +
+
+ + + + + +
+
+
+ provides +
+
+
+
+ + provides + +
+
+ + + + + +
+
+
+ depends on +
+
+
+
+ + depends on + +
+
+ + + + + +
+
+
+ depends on +
+
+
+
+ + depends on + +
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md index a65f3d7899..0a626ef175 100644 --- a/docs/auth/identity-resolver.md +++ b/docs/auth/identity-resolver.md @@ -236,6 +236,12 @@ export default async function createPlugin( // an entity you will need to replace this step as well. // // You might also replace it if you for example want to filter out certain groups. + // + // Note that `getDefaultOwnershipEntityRefs` only includes groups to which the + // user has a direct MEMBER_OF relationship. It's perfectly fine to include + // groups that the user is transitively part of in the claims array, but the + // catalog doesn't currently provide a direct way of accessing this list of + // groups. const ownershipRefs = getDefaultOwnershipEntityRefs(entity); // The last step is to issue the token, where we might provide more options in the future. diff --git a/docs/auth/microsoft/provider.md b/docs/auth/microsoft/provider.md index 999e1f40d2..bb8a7db6e4 100644 --- a/docs/auth/microsoft/provider.md +++ b/docs/auth/microsoft/provider.md @@ -28,6 +28,7 @@ On the **API permissions** tab, click on `Add Permission`, then add the followin - `openid` - `profile` - `User.Read` +- Optional custom scopes of the `Microsoft Graph` API defined in the app-config.yaml file. Your company may require you to grant [admin consent](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/user-admin-consent-overview) for these permissions. Even if your company doesn't require admin consent, you may wish to do so as it means users don't need to individually consent the first time they access backstage. @@ -54,6 +55,8 @@ auth: clientSecret: ${AZURE_CLIENT_SECRET} tenantId: ${AZURE_TENANT_ID} domainHint: ${AZURE_TENANT_ID} + additionalScopes: + - Mail.Send ``` The Microsoft provider is a structure with three mandatory configuration keys: @@ -65,6 +68,7 @@ The Microsoft provider is a structure with three mandatory configuration keys: Leave blank if your app registration is multi tenant. When specified, this reduces login friction for users with accounts in multiple tenants by automatically filtering away accounts from other tenants. For more details, see [Home Realm Discovery](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/home-realm-discovery-policy) +- `additionalScopes` (optional): List of scopes for the App Registration. The default and mandatory value is ['user.read']. ## Adding the provider to the Backstage frontend diff --git a/docs/auth/okta/provider.md b/docs/auth/okta/provider.md index 99ff664a67..2242e4a0f0 100644 --- a/docs/auth/okta/provider.md +++ b/docs/auth/okta/provider.md @@ -27,6 +27,8 @@ To add Okta authentication, you must create an Application from Okta: - `Controlled access`: (select as appropriate) - Click Save +The configuration examples provided above are suitable for local development. For a production deployment, substitute `http://localhost:7007` with the url that your Backstage instance is available at. + # Configuration The provider configuration can then be added to your `app-config.yaml` under the diff --git a/docs/backend-system/architecture/01-index.md b/docs/backend-system/architecture/01-index.md index 0e780039b6..9c37df7b66 100644 --- a/docs/backend-system/architecture/01-index.md +++ b/docs/backend-system/architecture/01-index.md @@ -6,8 +6,6 @@ sidebar_label: Overview description: The structure and architecture of the new Backend System and its component parts --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - ## Building Blocks This section introduces the high-level building blocks upon which this new @@ -59,7 +57,7 @@ Just like plugins, modules also have access to services and can depend on their A detailed explanation of the package architecture can be found in the [Backstage Architecture -Overview](../../overview/architecture-overview/#package-architecture). The +Overview](../../overview/architecture-overview.md#package-architecture). The most important packages to consider for this system are the following: - `plugin--backend` houses the implementation of the backend plugins diff --git a/docs/backend-system/architecture/02-backends.md b/docs/backend-system/architecture/02-backends.md index c64024d653..3386cff3f7 100644 --- a/docs/backend-system/architecture/02-backends.md +++ b/docs/backend-system/architecture/02-backends.md @@ -6,8 +6,6 @@ sidebar_label: Backend description: Backend instances --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - ## The Backend Instance This is the main entry point for creating a backend. It does not have any functionality in and of itself, but is simply responsible for wiring things together. diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md index df610f45ab..573f6612c2 100644 --- a/docs/backend-system/architecture/03-services.md +++ b/docs/backend-system/architecture/03-services.md @@ -6,8 +6,6 @@ sidebar_label: Services description: Services for backend plugins --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - Backend services provide shared functionality available to all backend plugins and modules. They are made available through service references that embed a type that represents the service interface, similar to how [Utility APIs](../../api/utility-apis.md) work in the Backstage frontend system. To use a service in your plugin or module you request an implementation of that service using the service reference. The system surrounding services exists to provide a level of indirection between the service interfaces and their implementation. It is an implementation of dependency injection, where each backend instance is the dependency injection container. The implementation for each service is provided by a service factory, which encapsulates the logic for how each service instance is created. diff --git a/docs/backend-system/architecture/04-plugins.md b/docs/backend-system/architecture/04-plugins.md index e93a6a39b2..195c32b049 100644 --- a/docs/backend-system/architecture/04-plugins.md +++ b/docs/backend-system/architecture/04-plugins.md @@ -6,8 +6,6 @@ sidebar_label: Plugins description: Backend plugins --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - Plugins provide the actual base features of a Backstage backend. Each plugin operates completely independently of all other plugins and they only communicate with each other through network calls. This means that there is a strong degree of isolation between plugins, and that each plugin can be considered a separate microservice. While a default Backstage project has all plugins installed within a single backend, it is also possible to split this setup into multiple backends, with each backend housing one or more plugins. ## Defining a Plugin diff --git a/docs/backend-system/architecture/05-extension-points.md b/docs/backend-system/architecture/05-extension-points.md index e6eb0a9e16..4b7f893f0b 100644 --- a/docs/backend-system/architecture/05-extension-points.md +++ b/docs/backend-system/architecture/05-extension-points.md @@ -6,8 +6,6 @@ sidebar_label: Extension Points description: Extension points of backend plugins --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - While plugins are able to use static configuration for lightweight forms of customization, you can quickly hit a limit where you need something more powerful to allow users to extend your plugin. For this purpose, the backend system provides a mechanism for plugins to provide extension points, which can be used to expose deeper customizations for your plugin. Extension points are used by modules, which are installed in the backend adjacent to plugins. Modules are covered more in-depth in the [next section](./06-modules.md). Extension points are quite similar to services, in that they both encapsulate an interface in a reference object. The key difference is that extension points are registered and provided by plugins themselves, and do not have any factory associated with them. Extension points for a given plugin are also only accessible to modules that extend that same plugin. diff --git a/docs/backend-system/architecture/06-modules.md b/docs/backend-system/architecture/06-modules.md index e916e4fdf6..eb00e737e3 100644 --- a/docs/backend-system/architecture/06-modules.md +++ b/docs/backend-system/architecture/06-modules.md @@ -6,8 +6,6 @@ sidebar_label: Modules description: Modules for backend plugins --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - Backend modules are used to extend [plugins](./04-plugins.md) or sometimes other modules with additional features or change existing behavior. They must always be installed in the same backend instance as the plugin that they extend, and may only extend a single plugin. Modules interact with their target plugin using the [extension points](./05-extension-points.md) registered by the plugin, while also being able to depend on the [services](./03-services.md) of that plugin. Both modules and plugins register an `init` method that is called during startup. In order to ensure that modules have registered all their extensions before the plugin starts up, all modules for each plugin are completely initialized before the plugin itself is initialized. In practice this means that all promises returned by each `init` method of the modules need to resolve before the plugin `init` method is called. This also means that it is not possible to further interact with the extension points once the `init` method has resolved. @@ -25,8 +23,8 @@ import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node' import { MyCustomProcessor } from './MyCustomProcessor'; export const catalogModuleExampleCustomProcessor = createBackendModule({ - moduleId: 'exampleCustomProcessor', pluginId: 'catalog', + moduleId: 'example-custom-processor', register(env) { env.registerInit({ deps: { diff --git a/docs/backend-system/architecture/07-naming-patterns.md b/docs/backend-system/architecture/07-naming-patterns.md index 3e57fa6863..f45355acf0 100644 --- a/docs/backend-system/architecture/07-naming-patterns.md +++ b/docs/backend-system/architecture/07-naming-patterns.md @@ -6,22 +6,22 @@ sidebar_label: Naming Patterns description: Naming patterns in the backend system --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - These are the naming patterns to adhere to within the backend system. They help us keep exports consistent across packages and make it easier to understand the usage and intent of exports. +As a rule, all names should be camel case, with the exceptions of plugin and module IDs, which should be kebab case. + ### Plugins -| Description | Pattern | Examples | -| ----------- | ------------ | ----------------------------------- | -| export | `Plugin` | `catalogPlugin`, `scaffolderPlugin` | -| ID | `''` | `'catalog'`, `'scaffolder'` | +| Description | Pattern | Examples | +| ----------- | ----------------- | ------------------------------------- | +| export | `Plugin` | `catalogPlugin`, `userSettingsPlugin` | +| ID | `''` | `'catalog'`, `'user-settings'` | Example: ```ts -export const catalogPlugin = createBackendPlugin({ - pluginId: 'catalog', +export const userSettingsPlugin = createBackendPlugin({ + pluginId: 'user-settings', ... }) ``` @@ -31,14 +31,14 @@ export const catalogPlugin = createBackendPlugin({ | Description | Pattern | Examples | | ----------- | ---------------------------- | ----------------------------------- | | export | `Module` | `catalogModuleGithubEntityProvider` | -| ID | `''` | `'githubEntityProvider'` | +| ID | `''` | `'github-entity-provider'` | Example: ```ts export const catalogModuleGithubEntityProvider = createBackendModule({ pluginId: 'catalog', - moduleId: 'githubEntityProvider', + moduleId: 'github-entity-provider', ... }) ``` diff --git a/docs/backend-system/building-backends/01-index.md b/docs/backend-system/building-backends/01-index.md index 3c6c51be51..4caa6fd524 100644 --- a/docs/backend-system/building-backends/01-index.md +++ b/docs/backend-system/building-backends/01-index.md @@ -6,8 +6,6 @@ sidebar_label: Overview description: Building backends using the new backend system --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - > NOTE: If you have an existing backend that is not yet using the new backend > system, see [migrating](./08-migrating.md). @@ -142,3 +140,13 @@ backend.start(); ``` We've now split the backend into two separate deployments, but we still need to make sure that they can communicate with each other. This is the hard and somewhat tedious part, as Backstage currently doesn't provide an out of the box solution that solves this. You'll need to manually configure the two backends with custom implementations of the `DiscoveryService` and have them return the correct URLs for each other. Likewise, you'll also need to provide a custom implementation of the `DiscoveryApi` in the frontend, unless you surface the two backends via a proxy that handles the routing instead. + +### Split backend deployments architecture example + +Below is an example of a more elaborate setup where we have three different backend deployments, each with their own set of plugins and modules. Between our frontend app and the backend instances we have a reverse proxy that routes the traffic to the appropriate instance. As an option for securing the Backstage deployment the proxy can be set up as an authenticating reverse proxy as well, denying unauthenticated users access to the backend instances. + +![diagram of a backstage deployment with three separate horizontally scaled backend systems](../../assets/backend-system/scaled-deployments.drawio.svg) + +In this example we have split out the Catalog and Search plugins into one backend deployment. The proxy routes all traffic for `/api/catalog/` and `/api/search/` to this instance. With this separation we're able to scale and deploy these two plugins independently, and they are also isolated from both a performance and security perspective. Likewise the TechDocs and Scaffolder plugins are split out as well, and then we route the rest of the traffic to our instance that contains the App, Auth, and Proxy plugins. + +We also see how each of the plugins have their own logical database, but are often set up to share the actual Database Management System (DBMS) instance. This is of course not a requirement, and you can choose to further divide or consolidate the databases as you see fit. diff --git a/docs/backend-system/building-backends/08-migrating.md b/docs/backend-system/building-backends/08-migrating.md index 98dde46d38..fc1a6f1859 100644 --- a/docs/backend-system/building-backends/08-migrating.md +++ b/docs/backend-system/building-backends/08-migrating.md @@ -6,8 +6,6 @@ sidebar_label: Migration Guide description: How to migrate existing backends to the new backend system --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - ## Overview This section describes how to migrate an existing Backstage backend service @@ -21,8 +19,7 @@ internal plugins and support classes themselves to the backend system first - the migration here will mostly deal with wiring and using compatibility wrappers where possible in the backend package itself. We hope that you will find that you end up with a much smaller, easier to understand, and easier to maintain -package as a result of these steps, and then being able to [migrate -plugins](../building-plugins-and-modules/08-migrating.md) as a separate +package as a result of these steps, and then being able to [migrate plugins](../building-plugins-and-modules/08-migrating.md) as a separate endeavour later. ## Overall Structure @@ -262,9 +259,259 @@ If you have other customizations made to `plugins/catalog.ts`, such as adding custom processors or entity providers, read on. Otherwise, you should be able to just delete that file at this point. +#### Amazon Web Services + +`AwsEksClusterProcessor` and `AwsOrganizationCloudAccountProcessor` have not yet been migrated to the new backend system. +See [Other Catalog Extensions](#other-catalog-extensions) for how to use these in the new backend system. + +For `AwsS3DiscoveryProcessor`, first migrate to `AwsS3EntityProvider`. + +To migrate `AwsS3EntityProvider` to the new backend system, add a reference to the `@backstage/plugin-catalog-backend-module-aws` module. + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-catalog-backend-module-aws/alpha')); +/* highlight-add-end */ +``` + +If you were providing a `schedule` in code, this now needs to be set via configuration. +All other AWS configuration in `app-config.yaml` remains the same. + +```yaml title="app-config.yaml" +catalog: + providers: + awsS3: + yourProviderId: + # ... + /* highlight-add-start */ + schedule: + frequency: PT1H + timeout: PT50M + /* highlight-add-end */ +``` + +#### Azure DevOps + +For `AzureDevOpsDiscoveryProcessor`, first migrate to `AzureDevOpsEntityProvider`. + +To migrate `AzureDevOpsEntityProvider` to the new backend system, add a reference to the `@backstage/plugin-catalog-backend-module-azure` module. + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-catalog-backend-module-azure/alpha')); +/* highlight-add-end */ +``` + +If you were providing a `schedule` in code, this now needs to be set via configuration. +All other Azure DevOps configuration in `app-config.yaml` remains the same. + +```yaml title="app-config.yaml" +catalog: + providers: + azureDevOps: + yourProviderId: + # ... + /* highlight-add-start */ + schedule: + frequency: PT1H + timeout: PT50M + /* highlight-add-end */ +``` + +#### Open API + +`InternalOpenApiDocumentationProvider` has not yet been migrated to the new backend system. +See [Other Catalog Extensions](#other-catalog-extensions) for how to use this in the new backend system. + +#### Bitbucket + +For `BitbucketDiscoveryProcessor`, migrate to `BitbucketCloudEntityProvider` or `BitbucketServerEntityProvider` + +To migrate `BitbucketCloudEntityProvider` to the new backend system, add a reference to the `@backstage/plugin-catalog-backend-module-bitbucket-cloud` module. + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add( + import('@backstage/plugin-catalog-backend-module-bitbucket-cloud/alpha'), +); +/* highlight-add-end */ +``` + +If you were providing a `schedule` in code, this now needs to be set via configuration. +All other Bitbucket Cloud configuration in `app-config.yaml` remains the same. + +```yaml title="app-config.yaml" +catalog: + providers: + bitbucketCloud: + yourProviderId: + # ... + /* highlight-add-start */ + schedule: + frequency: PT30M + timeout: PT3M + /* highlight-add-end */ +``` + +To migrate `BitbucketServerEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-bitbucket-server`. + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add( + import('@backstage/plugin-catalog-backend-module-bitbucket-server/alpha'), +); +/* highlight-add-end */ +``` + +If you were providing a `schedule` in code, this now needs to be set via configuration. +All other Bitbucket Server configuration in `app-config.yaml` remains the same. + +```yaml title="app-config.yaml" +catalog: + providers: + bitbucketServer: + yourProviderId: + # ... + /* highlight-add-start */ + schedule: + frequency: PT30M + timeout: PT3M + /* highlight-add-end */ +``` + +#### Google Cloud Platform + +To migrate `GkeEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-gcp`. + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-catalog-backend-module-gcp')); +/* highlight-add-end */ +``` + +Configuration in app-config.yaml remains the same. + +#### Gerrit + +To migrate `GerritEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-gerrit`. + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-catalog-backend-module-gerrit/alpha')); +/* highlight-add-end */ +``` + +If you were providing a `schedule` in code, this now needs to be set via configuration. +All other Gerrit configuration in `app-config.yaml` remains the same. + +```yaml title="app-config.yaml" +catalog: + providers: + gerrit: + yourProviderId: + # ... + /* highlight-add-start */ + schedule: + frequency: PT30M + timeout: PT3M + /* highlight-add-end */ +``` + +#### Github + +For `GithubDiscoveryProcessor`, `GithubMultiOrgReaderProcessor` and `GithubOrgReaderProcessor`, first migrate to the equivalent Entity Provider. + +To migrate `GithubEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-github`. + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-catalog-backend-module-github/alpha')); +/* highlight-add-end */ +``` + +If you were providing a `schedule` in code, this now needs to be set via configuration. +All other Github configuration in `app-config.yaml` remains the same. + +```yaml title="app-config.yaml" +catalog: + providers: + github: + yourProviderId: + # ... + /* highlight-add-start */ + schedule: + frequency: PT30M + timeout: PT3M + /* highlight-add-end */ +``` + +To migrate `GithubMultiOrgEntityProvider` and `GithubOrgEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-github-org`. + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-catalog-backend-module-github-org')); +/* highlight-add-end */ +``` + +If you were providing a `schedule` in code, this now needs to be set via configuration. +All other Github configuration in `app-config.yaml` remains the same. + +```yaml title="app-config.yaml" +catalog: + providers: + githubOrg: + yourProviderId: + # ... + /* highlight-add-start */ + schedule: + frequency: PT30M + timeout: PT3M + /* highlight-add-end */ +``` + +If you were providing transformers, these can be configured by extending `githubOrgEntityProviderTransformsExtensionPoint` + +```ts title="packages/backend/src/index.ts" +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { githubOrgEntityProviderTransformsExtensionPoint } from '@backstage/plugin-catalog-backend-module-github-org'; + +backend.add( + createBackendModule({ + pluginId: 'catalog', + moduleId: 'githubOrgTransformers', + register(env) { + env.registerInit({ + deps: { + /* highlight-add-start */ + githubOrgTransformers: + githubOrgEntityProviderTransformsExtensionPoint, + /* highlight-add-end */ + }, + async init({ githubOrgTransformers }) { + /* highlight-add-start */ + githubOrgTransformers.setUserTransformer(myUserTransformer); + githubOrgTransformers.setTeamTransformer(myTeamTransformer); + /* highlight-add-end */ + }, + }); + }, + }), +); +``` + #### Microsoft Graph -Import the Microsoft Graph catalog module +For `MicrosoftGraphOrgReaderProcessor`, first migrate to `MicrosoftGraphOrgEntityProvider` + +To migrate `MicrosoftGraphOrgEntityProvider` to the new backend system, add a reference to `@backstage/plugin-catalog-backend-module-msgraph`. ```ts title="packages/backend/src/index.ts" backend.add(import('@backstage/plugin-catalog-backend/alpha')); @@ -273,7 +520,8 @@ backend.add(import('@backstage/plugin-catalog-backend-module-msgraph/alpha')); /* highlight-add-end */ ``` -If you were providng a `schedule` programtically, this now needs to be set via configuration +If you were providing a `schedule` in code, this now needs to be set via configuration. +All other Microsoft Graph configuration in `app-config.yaml` remains the same. ```yaml title="app-config.yaml" catalog: @@ -285,7 +533,6 @@ catalog: frequency: PT4H timeout: PT30M /* highlight-add-end */ - ``` If you were providing transformers, these can be configured by extending `microsoftGraphOrgEntityProviderTransformExtensionPoint` @@ -297,7 +544,7 @@ import { microsoftGraphOrgEntityProviderTransformExtensionPoint } from '@backsta backend.add( createBackendModule({ pluginId: 'catalog', - moduleId: 'microsoftGraphExtensions', + moduleId: 'microsoft-graph-extensions', register(env) { env.registerInit({ deps: { @@ -330,21 +577,21 @@ depends on the appropriate extension point and interacts with it. ```ts title="packages/backend/src/index.ts" /* highlight-add-start */ -import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { createBackendModule } from '@backstage/backend-plugin-api'; /* highlight-add-end */ /* highlight-add-start */ const catalogModuleCustomExtensions = createBackendModule({ pluginId: 'catalog', // name of the plugin that the module is targeting - moduleId: 'customExtensions', + moduleId: 'custom-extensions', register(env) { env.registerInit({ deps: { catalog: catalogProcessingExtensionPoint, // ... and other dependencies as needed }, - init({ catalog /* ..., other dependencies */ }) { + async init({ catalog /* ..., other dependencies */ }) { // Here you have the opportunity to interact with the extension // point before the plugin itself gets instantiated catalog.addEntityProvider(new MyEntityProvider()); // just an example @@ -399,21 +646,21 @@ depends on the appropriate extension point and interacts with it. ```ts title="packages/backend/src/index.ts" /* highlight-add-start */ -import { eventsExtensionPoint } from '@backstage/plugin-events-node'; +import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha'; import { createBackendModule } from '@backstage/backend-plugin-api'; /* highlight-add-end */ /* highlight-add-start */ const eventsModuleCustomExtensions = createBackendModule({ pluginId: 'events', // name of the plugin that the module is targeting - moduleId: 'customExtensions', + moduleId: 'custom-extensions', register(env) { env.registerInit({ deps: { events: eventsExtensionPoint, // ... and other dependencies as needed }, - init({ events /* ..., other dependencies */ }) { + async init({ events /* ..., other dependencies */ }) { // Here you have the opportunity to interact with the extension // point before the plugin itself gets instantiated events.addSubscribers(new MySubscriber()); // just an example @@ -453,6 +700,26 @@ const backend = createBackend(); backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); ``` +With the new Backend System version of the Scaffolder plugin, any provider specific actions will need to be installed separately. +For example - GitHub actions are now collected under the `@backstage/plugin-scaffolder-backend-module-github` package. + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); + +/* highlight-add-next-line */ +backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); +``` + +And of course you'll need to install those separately as well. + +```bash +# from the repository root +yarn add --cwd packages/backend @backstage/plugin-scaffolder-backend-module-github +``` + +You can find a list of the available modules under the [plugins directory](https://github.com/backstage/backstage/tree/master/plugins) in the monorepo. + If you have other customizations made to `plugins/scaffolder.ts`, such as adding custom actions, read on. Otherwise, you should be able to just delete that file at this point. @@ -464,21 +731,21 @@ depends on the appropriate extension point and interacts with it. ```ts title="packages/backend/src/index.ts" /* highlight-add-start */ -import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; import { createBackendModule } from '@backstage/backend-plugin-api'; /* highlight-add-end */ /* highlight-add-start */ const scaffolderModuleCustomExtensions = createBackendModule({ pluginId: 'scaffolder', // name of the plugin that the module is targeting - moduleId: 'customExtensions', + moduleId: 'custom-extensions', register(env) { env.registerInit({ deps: { scaffolder: scaffolderActionsExtensionPoint, // ... and other dependencies as needed }, - init({ scaffolder /* ..., other dependencies */ }) { + async init({ scaffolder /* ..., other dependencies */ }) { // Here you have the opportunity to interact with the extension // point before the plugin itself gets instantiated scaffolder.addActions(new MyAction()); // just an example @@ -507,3 +774,255 @@ going easily, but feel free to move it out to where it fits best. As you migrate your entire plugin flora to the new backend system, you will probably make more and more of these modules as "first class" things, living right next to the implementations that they represent, and being exported from there. + +### The Auth Plugin + +A basic installation of the auth plugin with a Microsoft provider will look as follows. + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-microsoft-provider')); +/* highlight-add-end */ +``` + +An additional step you'll need to take is to add the resolvers to your configuration, here's an example: + +```yaml title:"app-config.yaml" +auth: + environment: development + providers: + microsoft: + development: + clientId: ${AZURE_CLIENT_ID} + clientSecret: ${AZURE_CLIENT_SECRET} + tenantId: ${AZURE_TENANT_ID} + signIn: + resolvers: + - resolver: emailMatchingUserEntityAnnotation + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName +``` + +> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +#### Auth Plugin Modules and Their Resolvers + +As you may have noticed in the above example you'll need to import the `auth-backend` and an `auth-backend-module`. The following sections outline each of them and their resolvers. + +All of the following modules include the following common resolvers: + +- [emailMatchingUserEntityProfileEmail](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-node/src/sign-in/commonSignInResolvers.ts#L29) +- [emailLocalPartMatchingUserEntityName](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-node/src/sign-in/commonSignInResolvers.ts#L54) + +##### Atlassian + +Setup: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-atlassian-provider')); +/* highlight-add-end */ +``` + +Additional resolvers: + +- [usernameMatchingUserEntityName](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-backend-module-atlassian-provider/src/resolvers.ts#L33C16-L33C46) + +##### GCP IAM + +Setup: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-gcp-iap-provider')); +/* highlight-add-end */ +``` + +Additional resolvers: + +- [emailMatchingUserEntityAnnotation](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-backend-module-gcp-iap-provider/src/resolvers.ts#L32C16-L32C49) + +##### GitHub + +Setup: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-github-provider')); +/* highlight-add-end */ +``` + +Additional resolvers: + +- [usernameMatchingUserEntityName](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-backend-module-github-provider/src/resolvers.ts#L33C16-L33C46) + +##### GitLab + +Setup: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-gitlab-provider')); +/* highlight-add-end */ +``` + +Additional resolvers: + +- [usernameMatchingUserEntityName](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-backend-module-gitlab-provider/src/resolvers.ts#L33C16-L33C46) + +##### Google + +Setup: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-google-provider')); +/* highlight-add-end */ +``` + +Additional resolvers: + +- [emailMatchingUserEntityAnnotation](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-backend-module-google-provider/src/resolvers.ts#L33C16-L33C49) + +##### Microsoft + +Setup: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-microsoft-provider')); +/* highlight-add-end */ +``` + +Additional resolvers: + +- [emailMatchingUserEntityAnnotation](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-backend-module-microsoft-provider/src/resolvers.ts#L33C16-L33C49) + +##### oauth2 + +Setup: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-oauth2-provider')); +/* highlight-add-end */ +``` + +Additional resolvers: + +- [usernameMatchingUserEntityName](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-backend-module-oauth2-provider/src/resolvers.ts#L33C16-L33C46) + +##### oauth2 Proxy + +Setup: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add( + import('@backstage/plugin-auth-backend-module-oauth2-proxy-provider'), +); +/* highlight-add-end */ +``` + +Additional resolvers: + +- [forwardedUserMatchingUserEntityName](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-backend-module-oauth2-proxy-provider/src/resolvers.ts#L27C16-L27C51) + +##### Okta + +Setup: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-okta-provider')); +/* highlight-add-end */ +``` + +Additional resolvers: + +- [emailMatchingUserEntityAnnotation](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-backend-module-okta-provider/src/resolvers.ts#L34C16-L34C49) + +##### Pinniped + +Setup: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('@backstage/plugin-auth-backend-module-pinniped-provider')); +/* highlight-add-end */ +``` + +##### VMware Cloud + +Setup: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add( + import('@backstage/plugin-auth-backend-module-vmware-cloud-provider'), +); +/* highlight-add-end */ +``` + +Additional resolvers: + +- [vmwareCloudSignInResolvers](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.ts#L29C18-L29C44) + +#### Custom Resolver + +You may have a case where the common resolvers or the ones that are included with the auth module you use won't work for your needs. In this case you will need to create a custom resolver. Instead of the 2nd import for your auth provider module you would provide your own: + +```ts title="packages/backend/src/index.ts" +/* highlight-add-start */ +export const authModuleGoogleProvider = createBackendModule({ + pluginId: 'auth', + moduleId: 'googleProvider', + register(reg) { + reg.registerInit({ + deps: { providers: authProvidersExtensionPoint }, + async init({ providers }) { + providers.registerProvider({ + providerId: 'google', + factory: createOAuthProviderFactory({ + authenticator: googleAuthenticator, + async signInResolver(info, ctx) { + // custom resolver ... + }, + }), + }); + }, + }); + }, +}); +/* highlight-add-end */ + +const backend = createBackend(); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(authModuleGoogleProvider); +/* highlight-add-end */ +``` 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 7d97694aec..168646afab 100644 --- a/docs/backend-system/building-plugins-and-modules/01-index.md +++ b/docs/backend-system/building-plugins-and-modules/01-index.md @@ -6,8 +6,6 @@ sidebar_label: Overview description: Building backend plugins and modules using the new backend system --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - > NOTE: If you have an existing backend and/or backend plugins that are not yet > using the new backend system, see [migrating](./08-migrating.md). @@ -104,8 +102,8 @@ import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node' import { MyCustomProcessor } from './MyCustomProcessor'; export const catalogModuleExampleCustomProcessor = createBackendModule({ - moduleId: 'exampleCustomProcessor', pluginId: 'catalog', + moduleId: 'example-custom-processor', register(env) { env.registerInit({ deps: { diff --git a/docs/backend-system/building-plugins-and-modules/02-testing.md b/docs/backend-system/building-plugins-and-modules/02-testing.md index d14f7de09c..f8af2bcabe 100644 --- a/docs/backend-system/building-plugins-and-modules/02-testing.md +++ b/docs/backend-system/building-plugins-and-modules/02-testing.md @@ -6,8 +6,6 @@ sidebar_label: Testing description: Learn how to test your backend plugins and modules --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - Utilities for testing backend plugins and modules are available in `@backstage/backend-test-utils`. This section describes those facilities. @@ -120,7 +118,7 @@ describe('MyDatabaseClass', () => { // "physical" databases to test against is much costlier than creating the // "logical" databases within them that the individual tests use. const databases = TestDatabases.create({ - ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3', 'MYSQL_8'], + ids: ['POSTGRES_16', 'POSTGRES_12', 'SQLITE_3', 'MYSQL_8'], }); // Just an example of how to conveniently bundle up the setup code 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 269a09b55c..8a31251ee8 100644 --- a/docs/backend-system/building-plugins-and-modules/08-migrating.md +++ b/docs/backend-system/building-plugins-and-modules/08-migrating.md @@ -6,8 +6,6 @@ sidebar_label: Migration Guide description: How to migrate existing backend plugins to the new backend system --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - Migrating an existing backend plugin to the new backend system is fairly straightforward. The process is similar across the majority of plugins which just return a `Router` that is then wired up in the `index.ts` file of your backend. The primary thing that we need to do is to make sure that the dependencies that are required by the plugin are available, and then registering the router with the HTTP router service. Let's look at an example of migrating the Kubernetes backend plugin. In the existing (old) system, the kubernetes backend is structured like this: @@ -196,7 +194,7 @@ import { GoogleContainerEngineSupplier } from './GoogleContainerEngineSupplier'; export default createBackendModule({ pluginId: 'kubernetes', - moduleId: 'gke.supplier', + moduleId: 'gke-supplier', register(env) { env.registerInit({ deps: { diff --git a/docs/backend-system/core-services/01-index.md b/docs/backend-system/core-services/01-index.md index 0163d45108..9074998719 100644 --- a/docs/backend-system/core-services/01-index.md +++ b/docs/backend-system/core-services/01-index.md @@ -6,8 +6,6 @@ sidebar_label: Overview description: Core backend service APIs --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - The default backend provides several [core services](https://github.com/backstage/backstage/blob/master/packages/backend-plugin-api/src/services/definitions/coreServices.ts) out of the box which includes access to configuration, logging, URL Readers, databases and more. All core services are available through the `coreServices` namespace in the `@backstage/backend-plugin-api` package. diff --git a/docs/backend-system/index.md b/docs/backend-system/index.md index c74dd34102..22ea4c6c59 100644 --- a/docs/backend-system/index.md +++ b/docs/backend-system/index.md @@ -6,10 +6,8 @@ sidebar_label: Introduction description: The Backend System --- -> **NOTE: The new backend system is in alpha, and some plugins do not yet fully implement it.** - ## Status -The new backend system is in alpha, but many plugins have already been migrated. We recommend all plugins to migrate to the new system, and you can also try it out in your own production deployments. +The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system. You can find an example backend setup in [the `backend-next` package](https://github.com/backstage/backstage/tree/master/packages/backend-next). diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index c9ace49d4d..675a8de947 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -53,7 +53,7 @@ yarn build:backend --config ../../app-config.yaml Once the host build is complete, we are ready to build our image. The following `Dockerfile` is included when creating a new app with `@backstage/create-app`: -```Dockerfile +```dockerfile FROM node:18-bookworm-slim # Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend. @@ -74,8 +74,10 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ USER node # This should create the app dir as `node`. -# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. -# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. +# If it is instead created as `root` then the `tar` command below will +# fail: `can't create directory 'packages/': Permission denied`. +# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) +# so the app dir is correctly created as `node`. WORKDIR /app # This switches many Node.js dependencies to production mode. @@ -158,7 +160,7 @@ host build. The following `Dockerfile` executes the multi-stage build and should be added to the repo root: -```Dockerfile +```dockerfile # Stage 1 - Create yarn install skeleton layer FROM node:18-bookworm-slim AS packages @@ -229,8 +231,10 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ USER node # This should create the app dir as `node`. -# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. -# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. +# If it is instead created as `root` then the `tar` command below will +# fail: `can't create directory 'packages/': Permission denied`. +# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) +# so the app dir is correctly created as `node`. WORKDIR /app # Copy the install dependencies from the build stage and context @@ -309,7 +313,7 @@ package, which is done as follows: .addRouter('', await app(appEnv)); ``` 3. Remove the `@backstage/plugin-app-backend` and the app package dependency - (e.g. `app`) from `packages/backend/packages.json`. If you don't remove the + (e.g. `app`) from `packages/backend/package.json`. If you don't remove the app package dependency the app will still be built and bundled with the backend. @@ -321,3 +325,16 @@ an NGINX image is available in the Note that if you're building a separate docker build of the frontend you probably need to adjust `.dockerignore` appropriately. Most likely by making sure `packages/app/dist` is not ignored. + +## Troubleshooting Tips + +When building Docker images you may run into problems from time to time, there are two handy flags you can use to help: + +- `--progress=plain`: this will give you a more verbose output and not fold the logs into sections. This is very useful when have an error but it just shows you the last command and possibly an exit code. Using this flag you are more likely to see where the error actually is. +- `--no-cache`: this will rebuild all the layers every time. This is helpful when you want to be sure that it's building from scratch. + +Here's an example of these flags in use: + +```sh +docker image build . -f packages/backend/Dockerfile --tag backstage --progress=plain --no-cache +``` diff --git a/docs/faq/index.md b/docs/faq/index.md new file mode 100644 index 0000000000..93e3e90fbe --- /dev/null +++ b/docs/faq/index.md @@ -0,0 +1,15 @@ +--- +id: index +title: Overview +description: FAQ Overview +--- + +This section contains answers to frequently asked questions about Backstage. + +### [Product FAQ](../faq/product.md) + +Questions related to product and design. + +### [Technical FAQ](../faq/technical.md) + +General technical questions about Backstage. diff --git a/docs/faq/product.md b/docs/faq/product.md new file mode 100644 index 0000000000..9d2a32f33e --- /dev/null +++ b/docs/faq/product.md @@ -0,0 +1,66 @@ +--- +id: product +title: Product FAQ +description: Questions related to product and design. +--- + +### Can we call Backstage something different? So that it fits our company better? + +Yes, Backstage is just a platform for building your own developer portal. We +happen to call our internal version Backstage, as well, as a reference to our +music roots. You can call your version whatever suits your team, company, or +brand. + +### Is Backstage a monitoring platform? + +No, but it can be! Backstage is designed to be a developer portal for all your +infrastructure tooling, services, and documentation. So, it's not a monitoring +platform — but that doesn't mean you can't integrate a monitoring tool into +Backstage by writing [a plugin](#what-is-a-plugin-in-backstage). + +### How is Backstage licensed? + +Backstage was released as open source software by Spotify and is licensed under +[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). + +### Why did we open source Backstage? + +We hope to see Backstage become the infrastructure standard everywhere. When we +saw how much Backstage improved developer experience and productivity +internally, we wanted to share those gains. After all, if Backstage can create +order in an engineering environment as open and diverse as ours, then we're +pretty sure it can create order (and boost productivity) anywhere. To learn +more, read our blog post, +"[What the heck is Backstage anyway?](https://backstage.io/blog/2020/03/18/what-is-backstage)" + +### Will Spotify's internal plugins be open sourced, too? + +Yes, we've already started releasing open source versions of some of the plugins +we use here, and we'll continue to do so. +[Plugins](#what-is-a-plugin-in-backstage) are the building blocks of +functionality in Backstage. We have over 120 plugins inside Spotify — many of +those are specialized for our use, so will remain internal and proprietary to +us. But we estimate that about a third of our existing plugins make good open +source candidates. (And we'll probably end up writing some brand new ones, too.) + +### What's the roadmap for Backstage? + +We envision three phases, which you can learn about in +[our project roadmap](../overview/roadmap.md). Even though the open source version +of Backstage is relatively new compared to our internal version, we have already +begun work on various aspects of all three phases. Looking at the +[milestones for active issues](https://github.com/backstage/backstage/milestones) +will also give you a sense of our progress. + +### My company doesn't have thousands of developers or services. Is using Backstage excessive for our needs? + +Not at all! A core reason to adopt Backstage is to standardize how software is +built at your company. It's easier to decide on those standards as a small +company, and grows in importance as the company grows. Backstage sets a +foundation, and an early investment in your infrastructure becomes even more +valuable as you grow. + +### Our company has a strong design language system/brand that we want to incorporate. Does Backstage support this? + +Yes! The Backstage UI is built using Material UI. With the theming capabilities +of Material UI, you are able to adapt the interface to your brand guidelines. diff --git a/docs/FAQ.md b/docs/faq/technical.md similarity index 74% rename from docs/FAQ.md rename to docs/faq/technical.md index 7b935140e6..55bb1af615 100644 --- a/docs/FAQ.md +++ b/docs/faq/technical.md @@ -1,74 +1,9 @@ --- -id: FAQ -title: FAQ -description: All FAQ related to Backstage +id: technical +title: Technical FAQ +description: General technical questions about Backstage. --- -## Product FAQ - -### Can we call Backstage something different? So that it fits our company better? - -Yes, Backstage is just a platform for building your own developer portal. We -happen to call our internal version Backstage, as well, as a reference to our -music roots. You can call your version whatever suits your team, company, or -brand. - -### Is Backstage a monitoring platform? - -No, but it can be! Backstage is designed to be a developer portal for all your -infrastructure tooling, services, and documentation. So, it's not a monitoring -platform — but that doesn't mean you can't integrate a monitoring tool into -Backstage by writing [a plugin](#what-is-a-plugin-in-backstage). - -### How is Backstage licensed? - -Backstage was released as open source software by Spotify and is licensed under -[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). - -### Why did we open source Backstage? - -We hope to see Backstage become the infrastructure standard everywhere. When we -saw how much Backstage improved developer experience and productivity -internally, we wanted to share those gains. After all, if Backstage can create -order in an engineering environment as open and diverse as ours, then we're -pretty sure it can create order (and boost productivity) anywhere. To learn -more, read our blog post, -"[What the heck is Backstage anyway?](https://backstage.io/blog/2020/03/18/what-is-backstage)" - -### Will Spotify's internal plugins be open sourced, too? - -Yes, we've already started releasing open source versions of some of the plugins -we use here, and we'll continue to do so. -[Plugins](#what-is-a-plugin-in-backstage) are the building blocks of -functionality in Backstage. We have over 120 plugins inside Spotify — many of -those are specialized for our use, so will remain internal and proprietary to -us. But we estimate that about a third of our existing plugins make good open -source candidates. (And we'll probably end up writing some brand new ones, too.) - -### What's the roadmap for Backstage? - -We envision three phases, which you can learn about in -[our project roadmap](overview/roadmap.md). Even though the open source version -of Backstage is relatively new compared to our internal version, we have already -begun work on various aspects of all three phases. Looking at the -[milestones for active issues](https://github.com/backstage/backstage/milestones) -will also give you a sense of our progress. - -### My company doesn't have thousands of developers or services. Is using Backstage excessive for our needs? - -Not at all! A core reason to adopt Backstage is to standardize how software is -built at your company. It's easier to decide on those standards as a small -company, and grows in importance as the company grows. Backstage sets a -foundation, and an early investment in your infrastructure becomes even more -valuable as you grow. - -### Our company has a strong design language system/brand that we want to incorporate. Does Backstage support this? - -Yes! The Backstage UI is built using Material UI. With the theming capabilities -of Material UI, you are able to adapt the interface to your brand guidelines. - -## Technical FAQ - ### What technology does Backstage use? Backstage is a large scale [TypeScript](https://www.typescriptlang.org/) @@ -116,7 +51,7 @@ type of content. Plugins all use a common set of platform APIs and reusable UI components. Plugins can fetch data either from the backend or an API exposed through the proxy. -Learn more about [the different components](overview/what-is-backstage.md) that +Learn more about [the different components](../overview/what-is-backstage.md) that make up Backstage. ### Why can't I dynamically install plugins without modifications to the app? @@ -253,7 +188,7 @@ data is shared with. Yes. The core frontend framework could be used for building any large-scale web application where (1) multiple teams are building separate parts of the app, and (2) you want the overall experience to be consistent. That being said, in -[Phase 2](overview/roadmap.md) of the project we will add features that are +[Phase 2](../overview/roadmap.md) of the project we will add features that are needed for developer portals and systems for managing software ecosystems. Our ambition will be to keep Backstage modular. diff --git a/docs/features/kubernetes/authentication.md b/docs/features/kubernetes/authentication.md index e0cddefbcf..5057218f17 100644 --- a/docs/features/kubernetes/authentication.md +++ b/docs/features/kubernetes/authentication.md @@ -4,23 +4,26 @@ title: Kubernetes Authentication description: Authentication in Kubernetes plugin --- -The authentication process in Kubernetes relies on `KubernetesAuthProviders`, which are -not the same as the application's auth providers, the default providers are defined in -`plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts`, you can -add custom providers there if needed. +The authentication process in Kubernetes relies on `KubernetesAuthProviders` -- +which are not the same as the application's auth providers. the default +providers are defined in +`plugins/kubernetes-react/src/kubernetes-auth-provider/KubernetesAuthProviders.ts`; +you can add custom providers there if needed. -These providers are configured so your Kubernetes plugin can locate and access the -clusters you have access to, some of them have special requirements in the third party in -question, like Microsoft Entra ID (formerly Azure Active Directory) subscription or Azure RBAC support active on the cluster. +These providers are configured so your Kubernetes plugin can locate and access +the clusters you have access to, some of them have special requirements in the +third party in question, like Microsoft Entra ID (formerly Azure Active +Directory) subscription or Azure RBAC support active on the cluster. -The providers currently available are divided into server side and client side. +The providers currently available are summarized below: ## Server Side Providers -These providers authenticate your _application_ with the cluster, meaning anyone that is -logged in into your backstage app will be granted the same access to Kubernetes objects, including guest users. +These providers authenticate your _application_ with the cluster, meaning anyone +that is logged in into your Backstage app will be granted the same access to +Kubernetes objects, including guest users. -The providers available as server side are: +The server side providers are: - `aws` - `azure` @@ -30,11 +33,19 @@ The providers available as server side are: ### AWS -For AWS, in addition to the "kubernetes" configuration, you will have to set up AWS authentication. The AWS server-side authentication provider uses [AWS Identity and Access Management (IAM)][3] to authenticate to the target Account(s), you can read more about it on the page for the [Integration AWS node][4]. +For AWS, in addition to Kubernetes configuration, you will have to set up +AWS authentication. The AWS server-side authentication provider uses [AWS +Identity and Access Management (IAM)][3] to authenticate to the target +Account(s); you can read more about it on the page for the [Integration AWS +node][4]. -Using the plugin, you can authenticate to several AWS accounts using either [static AWS Access keys][5] or short-lived Access keys generated by [assuming a role][6], for either case you will need to install the [AWS CLI utility][7] and set it up following the steps in the linked documentation. +Using the plugin, you can authenticate to several AWS accounts using either +[static AWS Access keys][5] or short-lived Access keys generated by [assuming a +role][6], for either case you will need to install the [AWS CLI utility][7] and +set it up following the steps in the linked documentation. -If you have generated static AWS security credentials, the configuration block for AWS will look like this: +If you have generated static AWS security credentials, the configuration block +for AWS will look like this: ```yaml aws: @@ -47,7 +58,8 @@ aws: accountDefaults: ``` -If your environment is set up to assume a role, the configuration would instead look like this: +If your environment is set up to assume a role, the configuration would instead +look like this: ```yaml aws: @@ -58,7 +70,8 @@ aws: accountDefaults: ``` -Either of these sections needs to be present for the Kubernetes configuration to use the `aws` `authProvider`. The Kubernetes configuration looks like this: +Either of these sections needs to be present for the Kubernetes configuration to +use the `aws` `authProvider`. The Kubernetes configuration looks like this: ```yaml kubernetes: @@ -68,23 +81,37 @@ kubernetes: - type: 'config' clusters: - url: https://..eks.amazonaws.com - name: + name: ${CLUSTER_NAME_TO_DISPLAY} authProvider: 'aws' caData: ${EKS_CA_DATA} + authMetadata: + kubernetes.io/aws-assume-role: ${ROLE_ARN_TO_ASSUME} + kubernetes.io/aws-external-id: ${ID_FROM_AWS_ADMIN} + kubernetes.io/x-k8s-aws-id: ${CLUSTER_NAME_IN_AWS_CONSOLE} ``` -You get both, the cluster `url` and `caData` directly from the AWS console by going to `EKS` > `Your cluster` > `Overview` > `Details`. You will find them under 'API server endpoint' and 'Certificate authority' respectively. +You get both the cluster URL and CA directly from the AWS console by going to +`EKS` > `Your cluster` > `Overview` > `Details`. You will find them under 'API +server endpoint' and 'Certificate authority' respectively. + +If Backstage needs to assume a role when authenticating with EKS clusters, the +`kubernetes.io/aws-assume-role` parameter can be set to the ARN of the desired +role. the `kubernetes.io/aws-external-id` parameter in the config corresponds to +the `ExternalId` parameter of the [`AssumeRole` API in STS][8]. ### Azure -The Azure server side authentication provider works by authenticating on the server with -the Azure CLI, please note that [Microsoft Entra authentication][1] is a requirement and has to +The Azure provider works by authenticating on the server with the Azure CLI, +please note that [Microsoft Entra authentication][1] is a requirement and has to be enabled in your AKS cluster, then follow these steps: -- [Install the Azure CLI][2] in the environment where the backstage application will run. -- Login with your Azure/Microsoft account with `az login` in the server's terminal. -- Go to your AKS cluster's resource page in Azure Console and follow the steps in the - `Connect` tab to set the subscription and get your credentials for `kubectl` integration. +- [Install the Azure CLI][2] in the environment where the backstage application + will run. +- Login with your Azure/Microsoft account with `az login` in the server's + terminal. +- Go to your AKS cluster's resource page in Azure Console and follow the steps + in the `Connect` tab to set the subscription and get your credentials for + `kubectl` integration. - Configure your cluster to use the `azure` auth provider like this: ```yaml @@ -98,18 +125,19 @@ kubernetes: skipTLSVerify: true ``` -To get the API server address for your Azure cluster, go to the Azure console page for the -cluster resource, go to `Overview` > `Properties` tab > `Networking` section and copy paste -the API server address directly in that `url` field. +To get the API server address for your Azure cluster, go to the Azure console +page for the cluster resource, go to `Overview` > `Properties` tab > +`Networking` section and copy paste the API server address directly in that +`url` field. ## Client Side Providers -These providers authenticate your _user_ with the cluster. Each Backstage user will be -prompted for credentials and will have access to the clusters as long as the user has been -authorized to access said cluster. If the cluster is listed in the `clusterLocatorMethods`, -but the user hasn't been authorized to access, the user will see the cluster listed but -will not see any resources in the plugin page for that cluster, and the error will show -as `401` or similar. +These providers authenticate a _user_ with the cluster. Each Backstage user will +be prompted for credentials and will have access to the clusters as long as the +user has been authorized to access said cluster. If Backstage is configured to +communicate with a cluster but the user isn't authorized to access it, they will +see the cluster listed but will not see any resources in the plugin page for +that cluster. The error will show as `401` or similar. The providers available as client side are: @@ -124,3 +152,4 @@ The providers available as client side are: [5]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html [6]: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html [7]: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html +[8]: https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html#API_AssumeRole_RequestParameters diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 494e51320a..331883be35 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -65,20 +65,72 @@ This is an array used to determine where to retrieve cluster configuration from. Valid cluster locator methods are: - [`catalog`](#catalog) -- [`localKubectlProxy`](#localkubectlproxy) - [`config`](#config) - [`gke`](#gke) +- [`localKubectlProxy`](#localkubectlproxy) - [custom `KubernetesClustersSupplier`](#custom-kubernetesclusterssupplier) #### `catalog` -This cluster locator method will read cluster information from the catalog. +This cluster locator method will gather +[Resources](https://backstage.io/docs/features/software-catalog/system-model#resource) +of +[type](https://backstage.io/docs/features/software-catalog/descriptor-format#spectype-required-4) +`kubernetes-cluster` from the catalog and treat them as clusters for the +purposes of the Kubernetes plugin. In order for a resource to be detected by +this method, it must also have the following +[annotations](https://backstage.io/docs/features/software-catalog/descriptor-format#annotations-optional) +(as seen +[here](https://github.com/backstage/backstage/blob/86baccb2d7d378baed74eaebf017c60b410986e5/plugins/kubernetes-backend/src/cluster-locator/CatalogClusterLocator.ts#L51-L61) +in the code): -#### `localKubectlProxy` +- [`kubernetes.io/api-server`](https://backstage.io/docs/reference/plugin-kubernetes-common.annotation_kubernetes_api_server/), + denoting the base URL of the Kubernetes control plane +- [`kubernetes.io/api-server-certificate-authority`](https://backstage.io/docs/reference/plugin-kubernetes-common.annotation_kubernetes_api_server_ca/), + containing a base64-encoded certificate authority bundle in PEM format; + Backstage will check that the control plane presents a certificate signed by + this authority. +- [`kubernetes.io/auth-provider`](https://backstage.io/docs/reference/plugin-kubernetes-common.annotation_kubernetes_auth_provider/), + denoting the strategy to use to authenticate with the control plane. -This cluster locator method will assume a locally running [`kubectl proxy`](https://kubernetes.io/docs/tasks/extend-kubernetes/http-proxy-access-api/#using-kubectl-to-start-a-proxy-server) process using the default port (8001). +There are many other annotations that can be applied to a cluster resource to +configure the way Backstage communicates, documented +[here](https://backstage.io/docs/reference/plugin-kubernetes-common#variables) +in the API reference. Here is a YAML snippet illustrating an example of a +cluster in the catalog: -NOTE: This cluster locator method is for local development only and should not be used in production. +```yaml +apiVersion: backstage.io/v1alpha1 +kind: Resource +metadata: + name: my-cluster + annotations: + kubernetes.io/api-server: 'https://127.0.0.1:53725' + kubernetes.io/api-server-certificate-authority: # base64-encoded CA + kubernetes.io/auth-provider: 'oidc' + kubernetes.io/oidc-token-provider: 'microsoft' + kubernetes.io/skip-metrics-lookup: 'true' +spec: + type: kubernetes-cluster + owner: user:guest +``` + +Note that it is insecure to store a Kubernetes service account token in an +annotation on a catalog entity (where it could easily be accidentally revealed +by the catalog API) -- therefore there is no annotation corresponding to the +[`serviceAccountToken` field](#clustersserviceaccounttoken-optional) used by +the [`config`](#config) cluster locator. Accordingly, the catalog cluster +locator does not support the [`serviceAccount`](#clustersauthprovider) auth +strategy. + +This method can be quite helpful when used in combination with an ingestion +procedure like the +[`GkeEntityProvider`](https://backstage.io/docs/reference/plugin-catalog-backend-module-gcp.gkeentityprovider/) +(installation documented +[here](https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-gcp#installation)) +or the +[`AwsEKSClusterProcessor`](https://backstage.io/docs/reference/plugin-catalog-backend-module-aws.awseksclusterprocessor/) +to automatically update the set of clusters tracked by Backstage. #### `config` @@ -341,6 +393,7 @@ For example: - type: 'gke' projectId: 'gke-clusters' region: 'europe-west1' # optional + authProvider: 'google' # optional skipTLSVerify: false # optional skipMetricsLookup: false # optional exposeDashboard: false # optional @@ -365,6 +418,18 @@ The Google Cloud project to look for Kubernetes clusters in. The Google Cloud region to look for Kubernetes clusters in. Defaults to all regions. +##### `authProvider` (optional) + +Set the authentication method for discovering clusters and gathering information +about resources. + +Defaults to `google` which leverages the logged in user's Google OAuth credentials. + +Set to `googleServiceAccount` to leverage +Application Default Credentials (https://cloud.google.com/docs/authentication/application-default-credentials). +To use a service account JSON key (not recommended), set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable +on the Backstage backend to the path of the service account key file. + ##### `skipTLSVerify` (optional) This determines whether the Kubernetes client verifies the TLS certificate @@ -388,6 +453,12 @@ Defaults to `false`. Array of key value labels used to filter out clusters which don't have the matching [resource labels](https://cloud.google.com/resource-manager/docs/creating-managing-labels). +#### `localKubectlProxy` + +This cluster locator method will assume a locally running [`kubectl proxy`](https://kubernetes.io/docs/tasks/extend-kubernetes/http-proxy-access-api/#using-kubectl-to-start-a-proxy-server) process using the default port (8001). + +NOTE: This cluster locator method is for local development only and should not be used in production. + #### Custom `KubernetesClustersSupplier` If the configuration-based cluster locators do not work for your use-case, @@ -593,7 +664,7 @@ SingleTenant Cluster: In the example above, we configured the "backstage.io/kubernetes-cluster" annotation on the entity `catalog-info.yaml` file to specify that the current component is running in a single cluster called "dice-cluster", so this cluster must have been specified in the `app-config.yaml`, under the Kubernetes clusters configuration (for more details, see [`Configuring Kubernetes clusters`](#configuring-kubernetes-clusters)). -If you do not specify the annotation by `default Backstage fetches all` defined Kubernetes cluster. +If you do not specify the annotation, by default Backstage fetches from all defined Kubernetes clusters. [1]: https://cloud.google.com/kubernetes-engine [2]: https://cloud.google.com/docs/authentication/production#linux-or-macos diff --git a/docs/features/kubernetes/installation.md b/docs/features/kubernetes/installation.md index 216c4db3f4..5111b5ad2e 100644 --- a/docs/features/kubernetes/installation.md +++ b/docs/features/kubernetes/installation.md @@ -130,7 +130,7 @@ export class CustomClustersSupplier implements KubernetesClustersSupplier { static create(refreshInterval: Duration) { const clusterSupplier = new CustomClustersSupplier(); - // setup refresh, e.g. using a copy of https://github.com/backstage/backstage/blob/master/plugins/search-backend-node/src/runPeriodically.ts + // setup refresh, e.g. using a copy of https://github.com/backstage/backstage/blob/master/plugins/kubernetes-backend/src/service/runPeriodically.ts runPeriodically( () => clusterSupplier.refreshClusters(), refreshInterval.toMillis(), diff --git a/docs/features/search/declarative-integration.md b/docs/features/search/declarative-integration.md index f0083e4d4b..c331d810ef 100644 --- a/docs/features/search/declarative-integration.md +++ b/docs/features/search/declarative-integration.md @@ -51,18 +51,19 @@ _Example disabling the search page extension_ # app-config.yaml app: extensions: - - plugin.search.page: false # ✨ + - page:search: false # ✨ + - nav-item:search: false # ✨ ``` -_Example setting the search sidebar item label_ +_Example setting the search sidebar item title_ ```yaml # app-config.yaml app: extensions: - - plugin.search.nav.index: # ✨ + - nav-item:search: # ✨ config: - label: 'Search Page' + title: 'Search Page' ``` > **Known limitations:** diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index a5662999ae..e49bdb4fa5 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -391,8 +391,6 @@ There are other more specific search results layout components that also accept ## How to migrate your backend installation to use Search together with the new backend system -> DISCLAIMER: The new backend system is in alpha, and so are the search backend support for the new backend system. We don't recommend you to migrate your backend installations to the new system yet. But if you want to experiment, this is the guide for you! - Recently, the Backstage maintainers [announced the new Backend System](https://backstage.io/blog/2023/02/15/backend-system-alpha). The search plugins are now migrated to support the new backend system. In this guide you will learn how to update your backend set up. In "packages/backend-next/index.ts", install the search plugin [1], the search engine [2], and the search collators/decorators modules [3]: diff --git a/docs/features/software-catalog/api.md b/docs/features/software-catalog/api.md index 3ccd89b286..3ab3c0df96 100644 --- a/docs/features/software-catalog/api.md +++ b/docs/features/software-catalog/api.md @@ -31,13 +31,190 @@ with a `Bearer` token, which should then be the Backstage token returned by the These are the endpoints that deal with reading of entities directly. What it exposes are final entities - i.e. the output of all processing and the stitching -process, not the raw originally ingested entity data. See [The Life of an -Entity](life-of-an-entity.md) for more details about this process and +process, not the raw originally ingested entity data. See +[The Life of an Entity](./life-of-an-entity.md) for more details about this process and distinction. +### `GET /entities/by-query` + +Query entities. Supports the following query parameters, described in the section below: + +- [`filter`](#filtering), for selecting only a subset of all entities +- [`fields`](#field-selection), for selecting only parts of the full data + structure of each entity +- `limit` for limiting the number of entities returned (20 is the default) +- [`orderField`](#ordering), for deciding the order of the entities +- [`fullTextFilter`](#full-text-filtering), for filtering the entities by text +- [`cursor`](#pagination), for retrieving the next or previous batch of entities + +The return type is JSON, on the following form + +```json +{ + "items": [{ "kind": "Component", "metadata": { "name": "foo" } }], + "totalItems": 4, + "pageInfo": { + "nextCursor": "a-cursor", + "prevCursor": "another-cursor" + } +} +``` + +#### Filtering + +You can pass in one or more filter sets that get matched against each entity. +Each filter set is a number of conditions that all have to match for the +condition to be true (conditions effectively have an AND between them). At least +one filter set has to be true for the entity to be part of the result set +(filter sets effectively have an OR between them). + +Example: + +```text +/entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type + + Return entities that match + + Filter set 1: + Condition 1: kind = user + AND + Condition 2: metadata.namespace = default + + OR + + Filter set 2: + Condition 1: kind = group + AND + Condition 2: spec.type exists +``` + +Each condition is either on the form ``, or on the form `=`. +The first form asserts on the existence of a certain key (with any value), and +the second asserts that the key exists and has a certain value. All checks are +always case _insensitive_. + +In all cases, the key is a simplified JSON path in a given piece of entity data. +Each part of the path is a key of an object, and the traversal also descends +through arrays. There are two special forms: + +- Array items that are simple value types (such as strings) match on a key-value + pair where the key is the item as a string, and the value is the string `true` +- Relations can be matched on a `relations.=` form + +Let's look at a simplified example to illustrate the concept: + +```json +{ + "a": { + "b": ["c", { "d": 1 }], + "e": 7 + } +} +``` + +This would match any one of the following conditions: + +- `a` +- `a.b` +- `a.b.c` +- `a.b.c=true` +- `a.b.d` +- `a.b.d=1` +- `a.e` +- `a.e=7` + +Some more real world usable examples: + +- Return all orphaned entities: + + `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` + +- Return all users and groups: + + `/entities/by-query?filter=kind=user&filter=kind=group` + +- Return all service components: + + `/entities/by-query?filter=kind=component,spec.type=service` + +- Return all entities with the `java` tag: + + `/entities/by-query?filter=metadata.tags.java` + +- Return all users who are members of the `ops` group (note that the full + [reference](references.md) of the group is used): + + `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops` + +#### Full text filtering + +TODO + +#### Field selection + +By default the full entities are returned, but you can pass in a `fields` query +parameter which selects what parts of the entity data to retain. This makes the +response smaller and faster to transfer, and may allow the catalog to perform +more efficient queries. + +The query parameter value is a comma separated list of simplified JSON paths +like above. Each path corresponds to the key of either a value, or of a subtree +root that you want to keep in the output. The rest is pruned away. For example, +specifying `?fields=metadata.name,metadata.annotations,spec` retains only the +`name` and `annotations` fields of the `metadata` of each entity (it'll be an +object with at most two keys), keeps the entire `spec` unchanged, and cuts out +all other roots such as `relations`. + +Some more real world usable examples: + +- Return only enough data to form the full ref of each entity: + + `/entities/by-query?fields=kind,metadata.namespace,metadata.name` + +#### Ordering + +By default the entities are returned ordered by their internal uid. You can +customize the `orderField` query parameters to affect that ordering. + +For example, to return entities by their name: + +`/entities/by-query?orderField=metadata.name,asc` + +Each parameter can be followed by `asc` for ascending lexicographical order or +`desc` for descending (reverse) lexicographical order. + +#### Pagination + +You may pass the `cursor` query parameters to perform cursor based pagination +through the set of entities. The value of `cursor` will be returned in the response, under the `pageInfo` property: + +```json + "pageInfo": { + "nextCursor": "a-cursor", + "prevCursor": "another-cursor" + } +``` + +If `nextCursor` exists, it can be used to retrieve the next batch of entities. Following the same approach, +if `prevCursor` exists, it can be used to retrieve the previous batch of entities. + +- [`filter`](#filtering), for selecting only a subset of all entities +- [`fields`](#field-selection), for selecting only parts of the full data + structure of each entity +- `limit` for limiting the number of entities returned (20 is the default) +- [`orderField`](#ordering), for deciding the order of the entities +- `fullTextFilter` + **NOTE**: [`filter`, `orderField`, `fullTextFilter`] and `cursor` are mutually exclusive. This means that, + it isn't possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters, + as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration. + ### `GET /entities` -Lists entities. Supports the following query parameters, described in sections +Lists entities. + +**NOTE**: This endpoint is deprecated in favor of `GET /entities/by-query`, which provides a more efficient implementation and cursor based pagination. + +The endpoint supports the following query parameters, described in sections below: - [`filter`](#filtering), for selecting only a subset of all entities diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index 924c59a6fa..526ba15072 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -782,7 +782,7 @@ Describes the following entity kind: An API describes an interface that can be exposed by a component. The API can be defined in different formats, like [OpenAPI](https://swagger.io/specification/), -[AsyncAPI](https://www.asyncapi.com/docs/specifications/latest/), +[AsyncAPI](https://www.asyncapi.com/docs/reference/specification/latest), [GraphQL](https://graphql.org/learn/schema/), [gRPC](https://developers.google.com/protocol-buffers), or other formats. @@ -838,7 +838,7 @@ The current set of well-known and common values for this field is: - `openapi` - An API definition in YAML or JSON format based on the [OpenAPI](https://swagger.io/specification/) version 2 or version 3 spec. - `asyncapi` - An API definition based on the - [AsyncAPI](https://www.asyncapi.com/docs/specifications/latest/) spec. + [AsyncAPI](https://www.asyncapi.com/docs/reference/specification/latest) spec. - `graphql` - An API definition based on [GraphQL schemas](https://spec.graphql.org/) for consuming [GraphQL](https://graphql.org/) based APIs. diff --git a/docs/features/software-catalog/life-of-an-entity.md b/docs/features/software-catalog/life-of-an-entity.md index 583be7df20..d1c01141e4 100644 --- a/docs/features/software-catalog/life-of-an-entity.md +++ b/docs/features/software-catalog/life-of-an-entity.md @@ -341,7 +341,7 @@ the three-dots menu option of entity views does offer this option, and the orphaned status can be seen in an info box at the top of the entity's overview page. -However, if you were to try to do an explicit depletion on an entity that's +However, if you were to try to do an explicit deletion on an entity that's being kept actively updated by a parent entity, it would just reappear again shortly thereafter when the processing loops reconsider the parent entity that's still in there. diff --git a/docs/features/software-templates/authorizing-parameters-steps-and-actions.md b/docs/features/software-templates/authorizing-parameters-steps-and-actions.md index 1351676c37..27e8d48023 100644 --- a/docs/features/software-templates/authorizing-parameters-steps-and-actions.md +++ b/docs/features/software-templates/authorizing-parameters-steps-and-actions.md @@ -128,10 +128,10 @@ class ExamplePermissionPolicy implements PermissionPolicy { } ``` -With this permission policy, the user `spiderman` won't be able to execute the debug:log action. +With this permission policy, the user `spiderman` won't be able to execute the `debug:log` action. You can also restrict the input provided to the action by combining multiple rules. -In the example below, `spiderman` won't be able to execute debug:log when passing `{ "message": "not-this!" }` as action input: +In the example below, `spiderman` won't be able to execute `debug:log` when passing `{ "message": "not-this!" }` as action input: ```ts title="packages/backend/src/plugins/permission.ts" /* highlight-add-start */ diff --git a/docs/features/software-templates/ui-options-examples.md b/docs/features/software-templates/ui-options-examples.md index b402f68aee..7f0e5b225f 100644 --- a/docs/features/software-templates/ui-options-examples.md +++ b/docs/features/software-templates/ui-options-examples.md @@ -119,6 +119,117 @@ entity: defaultNamespace: payment ``` +## MultiEntityPicker + +The input props that can be specified under `ui:options` for the `MultiEntityPicker` field extension. + +### `allowArbitraryValues` + +Whether to allow arbitrary user input. Defaults to true. + +`allowArbitraryValues` provides input validation when selecting an entity as the values you enter will correspond to a valid entity. + +- Adding a valid entity with `allowArbitraryValues` as `false` + +```yaml +entity: + title: Entities + type: array + description: Entities of the component + ui:field: MultiEntityPicker + ui:options: + allowArbitraryValues: false +``` + +- Adding an arbitrary entity with `allowArbitraryValues` as `true` (default value) + +```yaml +entity: + title: Entities + type: array + description: Entities of the component + ui:field: MultiEntityPicker + ui:options: + allowArbitraryValues: true +``` + +### `catalogFilter` + +`catalogFilter` supports filtering options by any field(s) of an entity. + +- Get all entities of kind `Group` + +```yaml +entity: + title: Entities + type: array + description: Entities of the component + ui:field: MultiEntityPicker + ui:options: + catalogFilter: + - kind: Group +``` + +- Get entities of kind `Group` and spec.type `team` + +```yaml +entity: + title: Entities + type: array + description: Entities of the component + ui:field: MultiEntityPicker + ui:options: + catalogFilter: + - kind: Group + spec.type: team +``` + +For the full details on the spec.\* values see [here](../software-catalog/descriptor-format.md#kind-group). + +### `defaultKind` + +The default entity kind. + +```yaml +system: + title: System + type: array + description: Systems of the component + ui:field: MultiEntityPicker + ui:options: + catalogFilter: + kind: System + defaultKind: System +``` + +### `defaultNamespace` + +The ID of a namespace that the entity belongs to. The default value is `default`. + +- Listing all entities in the `default` namespace (default value) + +```yaml +entity: + title: Entity + type: array + description: Entities of the component + ui:field: MultiEntityPicker + ui:options: + defaultNamespace: default +``` + +- Listing all entities in the `payment` namespace + +```yaml +entity: + title: Entity + type: array + description: Entities of the component + ui:field: MultiEntityPicker + ui:options: + defaultNamespace: payment +``` + ## `OwnerPicker` The input props that can be specified under `ui:options` for the `OwnerPicker` field extension. diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 7019ecfb28..35458446c2 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -231,8 +231,53 @@ spec: inputType: tel ``` +### Using Secrets + +You may want to mark things as secret and make sure that these values are protected and not available through REST endpoints. You can do this by using the built in `ui:field: Secret`. + +You can define this property as any normal parameter, however the consumption of this parameter will not be available through `${{ parameters.myKey }}` you will instead need to use `${{ secrets.myKey }}` in your `template.yaml`. + +Parameters will be automatically masked in the review step. + +```yaml +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: v1beta3-demo + title: Test Action template + description: scaffolder v1beta3 template demo +spec: + owner: backstage/techdocs-core + type: service + + parameters: + - title: Authenticaion + description: Provide authentication for the resource + required: + - username + - password + properties: + username: + type: string + # use the built in Secret field extension + ui:field: Secret + password: + type: string + ui:field: Secret + + steps: + - id: setupAuthentication + action: auth:create + input: + # make sure to use ${{ secrets.parameterName }} to reference these values + username: ${{ secrets.username }} + password: ${{ secrets.password }} +``` + ### Hide or mask sensitive data on Review step +> Note: this approach is soon to be deprecated, please mark things as secret by using the `Secret` field extension instead as mentioned above. + Sometimes, specially in custom fields, you collect some data on Create form that must not be shown to the user on Review step. To hide or mask this data, you can use `ui:widget: password` or set some properties of `ui:backstage`: diff --git a/docs/features/techdocs/cli.md b/docs/features/techdocs/cli.md index cc356f68d1..c824eb7d7d 100644 --- a/docs/features/techdocs/cli.md +++ b/docs/features/techdocs/cli.md @@ -91,6 +91,9 @@ Options: --docker-option Extra options to pass to the docker run command, e.g. "--add-host=internal.host:192.168.11.12" (can be added multiple times). --no-docker Do not use Docker, use MkDocs executable in current user environment. + --mkdocs-parameter-clean Pass "--clean" parameter to mkdocs server running in containerized environment. + --mkdocs-parameter-dirtyreload Pass "--dirtyreload" parameter to mkdocs server running in containerized environment. + --mkdocs-parameter-strict Pass "--strict" parameter to mkdocs server running in containerized environment. --mkdocs-port Port for MkDocs server to use (default: "8000") --preview-app-bundle-path Preview documentation using a web app other than the included one. --preview-app-port Port where the preview will be served. diff --git a/docs/features/techdocs/creating-and-publishing.md b/docs/features/techdocs/creating-and-publishing.md index f63ff43b7f..1251f67276 100644 --- a/docs/features/techdocs/creating-and-publishing.md +++ b/docs/features/techdocs/creating-and-publishing.md @@ -9,8 +9,8 @@ This section will guide you through how to: - [Create a basic documentation setup](#create-a-basic-documentation-setup) - [Use any software template](#use-any-software-template) - - [Use the documentation template](#use-the-documentation-template) - [Enable documentation for an already existing entity](#enable-documentation-for-an-already-existing-entity) + - [Create a standalone documentation](#create-a-standalone-documentation) - [Writing and previewing your documentation](#writing-and-previewing-your-documentation) ## Prerequisites @@ -41,23 +41,6 @@ default, we highly recommend you to set that up. Follow our how-to guide [How to add documentation setup to your software templates](./how-to-guides.md#how-to-add-the-documentation-setup-to-your-software-templates) to get started. -### Use the documentation template - -There could be _some_ situations where you don't want to keep your docs close to -your code, but still want to publish documentation - for example, an onboarding -tutorial. For this use case, we have put together a documentation template. Your -Backstage instance should by default have a documentation template added. If -not, copy the catalog locations from the -[create-app template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/app-config.yaml.hbs) -to add the documentation template. The template creates a component with -**only** TechDocs configuration and default markdown files, and is otherwise -empty. - -![Documentation Template](../../assets/techdocs/documentation-template.png) - -Create an entity from the documentation template and you will get the needed -setup for free. - ### Enable documentation for an already existing entity Prerequisites: @@ -115,6 +98,56 @@ This is a basic example of documentation. Commit your changes, open a pull request and merge. You will now get your updated documentation next time you run Backstage! +### Create a standalone documentation + +There could be _some_ situations where you don't want to keep your docs close to +your code, but still want to publish documentation - for example, an onboarding +tutorial. For this case, you can create a documentation component, which will be +published as a standalone part of TechDocs. + +First, create an entity for your documentation. A minimal example could look like +this: + +```yaml title="catalog-info.yaml" +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: a-unique-name-for-your-docs + annotations: + # this could also be `url:` if the documentation isn't in the same location + backstage.io/techdocs-ref: dir:. +spec: + type: documentation + lifecycle: experimental + owner: user-or-team-name +``` + +Next, create the config file for [mkdocs](https://www.mkdocs.org/), which will be +used to parse your docs: + +```yaml title="mkdocs.yml" +site_name: a-unique-name-for-your-docs +site_description: An informative description +plugins: + - techdocs-core +nav: + - Getting Started: index.md +``` + +Finally, add your index.md Markdown file, in a folder named `docs/` with your desired +documentation in Markdown. Your file structure should now look like this: + +``` +your-great-documentation/ + docs/ + index.md + catalog-info.yaml + mkdocs.yml +``` + +Last but not least, register your component in the software catalog using +[one of several options](../software-catalog/index.md#adding-components-to-the-catalog). + ## Writing and previewing your documentation Using the [techdocs-cli](https://github.com/backstage/backstage/tree/master/packages/techdocs-cli) you can diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 29f985904f..afc257f20b 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -504,6 +504,18 @@ folder (/docs) or replace the content in this file. Done! You now have support for TechDocs in your own software template! +### Prevent download of Google fonts + +If your Backstage instance does not have internet access, the generation will fail. TechDocs tries to download the Roboto font from Google. You can disable it by adding the following lines to mkdocs.yaml: + +```yaml +theme: + name: material + font: false +``` + +> Note: The addition `name: material` is necessary. Otherwise it will not work + ## How to enable iframes in TechDocs TechDocs uses the [DOMPurify](https://github.com/cure53/DOMPurify) library to diff --git a/docs/frontend-system/architecture/01-index.md b/docs/frontend-system/architecture/01-index.md index 380ebaa335..8585eb76fc 100644 --- a/docs/frontend-system/architecture/01-index.md +++ b/docs/frontend-system/architecture/01-index.md @@ -6,7 +6,7 @@ sidebar_label: Overview description: The structure and architecture of the new Frontend System --- -> **NOTE: The new frontend system is in a highly experimental phase** +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** ## Building Blocks diff --git a/docs/frontend-system/architecture/02-app.md b/docs/frontend-system/architecture/02-app.md index 52fd20faee..1ffae71981 100644 --- a/docs/frontend-system/architecture/02-app.md +++ b/docs/frontend-system/architecture/02-app.md @@ -1,12 +1,12 @@ --- -id: apps +id: app title: App Instances sidebar_label: App # prettier-ignore description: App instances --- -> **NOTE: The new frontend system is in a highly experimental phase** +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** ## The App Instance @@ -45,4 +45,35 @@ A common type of data that is shared between extensions is React elements and co ## Feature Discovery -TODO +App feature discovery lets you automatically discover and install features provided by dependencies in your app. In practice, it means that you don't need to manually `import` features in code, but they are instead installed as soon as you add them as a dependency in your `package.json`. + +Because feature discovery needs to interact with the compilation process, it is only available when using the `@backstage/cli` to build your app. It is hooked into the WebPack compilation process by scanning your app package for compatible dependencies, which are then made part of the app compilation bundle. + +Since the `@backstage/cli` is a more stable component than the new frontend system, feature discovery is currently marked as an experimental feature of the CLI and needs to be enabled manually. To enable it, add the following configuration to your `app-config.yaml`: + +```yaml +app: + experimental: + packages: all +``` + +This will cause all dependencies in your app package to be installed automatically. If this is not desired, you can use include or exclude filters to narrow down the set of packages: + +```yaml +app: + experimental: + packages: + # Only the following packages will be included + include: + - '@backstage/plugin-catalog' + - '@backstage/plugin-scaffolder' +--- +app: + experimental: + packages: + # All but the following package will be included + exclude: + - '@backstage/plugin-catalog' +``` + +Note that you do not need to manually exclude packages that you also import explicitly in code, since plugin instances are deduplicated by the app. You will never end up with duplicate plugin installations except if they are in fact two different plugin instances with different IDs. diff --git a/docs/frontend-system/architecture/03-extensions.md b/docs/frontend-system/architecture/03-extensions.md index 3d08bf1721..6d86415817 100644 --- a/docs/frontend-system/architecture/03-extensions.md +++ b/docs/frontend-system/architecture/03-extensions.md @@ -6,7 +6,7 @@ sidebar_label: Extensions description: Frontend extensions --- -> **NOTE: The new frontend system is in a highly experimental phase** +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** As mentioned in the [previous section](./02-app.md), Backstage apps are built up from a tree of extensions. This section will go into more detail about what extensions are, how to create and use them, and how to create your own extensibility patterns. @@ -18,6 +18,10 @@ Each extensions has a number of different properties that define how it behaves ### ID + + The ID of an extension is used to uniquely identity it, and it should ideally by unique across the entire Backstage ecosystem. For each frontend app instance there can only be a single extension for any given ID. Installing multiple extensions with the same ID will either result in an error or one of the extensions will override the others. The ID is also used to reference the extensions from other extensions, in configuration, and in other places such as developer tools and analytics. ### Output @@ -56,7 +60,7 @@ Extensions are created using the `createExtension` function from `@backstage/fro ```tsx const extension = createExtension({ - id: 'my-extension', + name: 'my-extension', // This is the attachment point, `id` is the ID of the parent extension, // while `input` is the name of the input to attach to. attachTo: { id: 'my-parent', input: 'content' }, @@ -67,10 +71,10 @@ const extension = createExtension({ element: coreExtensionData.reactElement, }, // This factory is called to instantiate the extensions and produce its output. - factory({ bind }) { - bind({ + factory() { + return { element:
Hello World
, - }); + }; }, }); ``` @@ -81,24 +85,288 @@ Note that while the `createExtension` is public API and used in many places, it ## Extension Data -TODO +Communication between extensions happens in one direction, from one child extension through the attachment point to its parent. The child extension outputs data which is then passed as inputs to the parent extension. This data is called Extension Data, where the shape of each individual piece of data is described by an Extension Data Reference. These references are created separately from the extensions themselves, and can be shared across multiple different kinds of extensions. Each reference consists of an ID and a TypeScript type that the data needs to conform to, and represents one type of data that can be shared between extensions. + +### Extension Data References + +To create a new extension data reference to represent a type of shared extension data you use the `createExtensionDataRef` function. When defining a new reference you need to provide an ID and a TypeScript type, for example: + +```ts +export const reactElementExtensionDataRef = + createExtensionDataRef('my-plugin.reactElement'); +``` + +The `ExtensionDataRef` can then be used to describe an output property of the extension. This will enforce typing on the return value of the extension factory: + +```tsx +const extension = createExtension({ + // ... + output: { + element: reactElementExtensionDataRef, + }, + factory() { + return { + element:
Hello World
, + }; + }, +}); +``` + +### Extension Data Uniqueness + +Note that the key used in the output map, in this case `element`, is only used internally within the definition of the extension itself. That actual identifier for the data when consumed by other extensions is the ID of the reference, in this case `core.reactElement`. This means that you can not output multiple different values for the same extension data reference, as they would conflict with each other. That in turn makes overly generic extension data references a bad idea, for example a generic "string" type. Instead create separate references for each type of data that you want to share. + +```tsx +const extension = createExtension({ + // ... + output: { + // ❌ Bad example - outputting values of same type + element1: reactElementExtensionDataRef, + element2: reactElementExtensionDataRef, + }, + factory() { + return { + element1:
Hello World
, + element2:
Hello World
, + }; + }, +}); +``` + +### Core Extension Data + +We provide default `coreExtensionData`, which provides commonly used `ExtensionDataRef`s - e.g. for `React.JSX.Element` and `RouteRef`. They can be used when creating your own extension. For example, the React Element extension data that we defined above is already provided as `coreExtensionData.reactElement`. + + + +### Optional Extension Data + +By default all extension data is required, meaning that the extension factory must provide a value for each output. However, it is possible to make extension data optional by calling the `.optional()` method. This makes it optional for the factory function to return a value as part of its output. When calling the `.optional()` method you create a new copy of the extension data reference, it does not mutate the existing reference. + +```tsx +const extension = createExtension({ + // ... + output: { + element: coreExtensionData.reactElement.optional(), + }, + factory() { + return { + element: + Math.random() < 0.5 ? : undefined, + }; + }, +}); +``` ## Extension Inputs -TODO +The Extension Data can be passed up to other extensions through their extension inputs. Similar to the outputs seen before, let's create an example an extension with a extension input: -## Configuration +```tsx +const navigationExtension = createExtension({ + // ... + inputs: { + // [1]: Input + logo: createExtensionInput( + { + element: coreExtensionData.reactElement, + }, + { singleton: true, optional: true }, + ), + }, + factory({ inputs }) { + return { + element: ( + + ), + }; + }, + // ... +}); +``` -TODO +The input (see [1] above) is an object that we create using `createExtensionInput`. The first argument is the set of extension data that we accept via this input, and works just like the `output` option. The second argument is optional, and it allows us to put constraints on the extensions that are attached to our input. If the `singleton: true` option is set, only a single extension can attached at a time, and unless the `optional: true` option is set it will also be required that there is exactly on attached extension. -## Configuration Schema +So how can we now attach the output to the parent extension's input? If we think about a navigation component, like the Sidebar in Backstage, there might be plugins that want to attach a link to their plugin to this navigation component. In this case the plugin only needs to know the extension `id` and the name of the extension `input` to attach the extension `output` returned by the `factory` to the specified extension: -TODO +```tsx +const navigationItemExtension = createExtension({ + // ... + attachTo: { id: 'app/nav', input: 'items' }, + factory() { + return { + element: Home, + }; + }, +}); + +const navigationExtension = createExtension({ + // ... + // [2]: Extension `id` will be `app/nav` following the extension naming pattern + namespace: 'app', + name: 'nav', + output: { + element: coreExtensionData.reactElement, + }, + inputs: { + items: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + factory({ inputs }) { + return { + element: ( + + ), + }; + }, + // ... +}); +``` + +In this case the extension input `items` is an array, where each individual item is an extension that attached itself to the extension inputs of this `id`. + +With the `inputs` not only the `output` of an extensions item is passed to the extension, but also the `node`. However, it is discouraged to consume the `node` here unless needed. If we are looking at the `factory` function from the example above we could access the `node` like the following: + +```tsx + // ... + factory({ inputs }) { + return { + element: ( + + ), + }; + }, +``` + +## Extension Configuration + +With the `app-config.yaml` there is already the option to pass configuration to plugins or the app to e.g. define the `baseURL` of your app. For extensions this concept would be limiting as an extension can be independent of the plugin & initiated several times. Therefor we created a possibility to configure each extension individually through config. The extension config schema is created using the [`zod`](https://zod.dev/) library, which in addition to TypeScript type checking also provides runtime validation and coercion. If we continue with the example of the `navigationExtension` and now want it to contain a configurable title, we could make it available like the following: + +```tsx +const navigationExtension = createExtension({ + // ... + namespace: 'app', + name: 'nav', + // [3]: Extension `id` will be `app/nav` following the extension naming pattern + configSchema: createSchemaFromZod(z => + z.object({ + title: z.string().default('Sidebar Title'), + }), + ), + factory({ config }) { + return { + element: ( + + ), + }; + }, + // ... +}); +``` + +To now change the text of the title from "Sidebar Title" to "Backstage" we can look at the `id` of the extension & add the following to the `app-config.yaml`: + +```yaml +app: + # ... + extensions: + # ... + - app/nav: + config: + title: 'Backstage' +``` ## Extension Creators -TODO +With creating an extension by using `createExtension(...)` you have the advantage that the extension can be anything in your Backstage application. We realised that this comes with the trade-off of having to repeat boilerplate code for similar building blocks. Here extension creators come into play for covering common building blocks in Backstage like pages using `createPageExtension`, themes using the `createThemeExtension` or items for the navigation using `createNavItemExtension`. + +If we follow the example from above all items of the navigation have similarities, like they all want to be attached to the same extension with the same input as well as rendering the same navigation item component. Therefor `createExtension` can be abstracted for this use case to `createNavItemExtension` and if we add the extension to the app it will end up in the right place & looks like we expect a navigation item to look. + +```tsx +export const HomeNavIcon = createNavItemExtension({ + routeRef: routeRefForTheHomePage, + title: 'Home', + icon: HomeIcon, +}); +``` + +### Extension Kind + +With the example `HomeNavIcon` will end up on the extension input `items` of the extensions with the id `app/nav`. It raises the question what the `id` of the `HomeNavIcon` itself is. The extension creator for the navigation item has a defined `kind`, which by convention matches the own name. So in this example `createNavItemExtension` sets the kind to `nav-item`. + +The `id` of the extension is then build out of `namespace`, `name` & `kind` like the following - where `namespace` & `name` are optional properties that can be passed to the extension creator: + +``` +id: kind:namespace/name +``` + +For more information on naming of extension refer to the [naming patterns documentation](./08-naming-patterns.md). + +### Extension Creators in libraries + +Extension creators should be exported from frontend library packages (e.g. `*-react`) rather than plugin packages. + +If an extension is only for in-house tweaks, it's okay to put it in the plugin package. But if you want other open source plugins to use it, or you already have a `-react` package, always put extension creators in the `-react` package. ## Extension Boundary -TODO +The `ExtensionBoundary` wraps extensions with several React contexts for different purposes + +### Suspense + +All React elements rendered by extension creators should be wrapped in the extension boundary. With `Suspense` the extension can than load resources asynchronously with having a loading fallback. It also allows to lazy load the whole extension similar to how plugins are currently lazy loaded in Backstage. + +### Error Boundary + +Similar to plugins the `ErrorBoundary` for extension allows to pass in a fallback component in case there is an uncaught error inside of the component. With this the error can be isolated & it would prevent the rest of the plugin to crash. + +### Analytics + +Analytics information are provided through the `AnalyticsContext`, which will give `extensionId` & `pluginId` as context to analytics event fired inside of the extension. Additionally `RouteTracker` will capture an analytics event for routable extension to inform which extension metadata gets associated with a navigation event when the route navigated to is a gathered `mountPoint`. + +The `ExtensionBoundary` can be used like the following in an extension creator: + +```tsx +export function createSomeExtension< + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options): ExtensionDefinition { + return createExtension({ + // ... + factory({ config, inputs, node }) { + const ExtensionComponent = lazy(() => + options + .loader({ config, inputs }) + .then(element => ({ default: () => element })), + ); + + return { + path: config.path, + routeRef: options.routeRef, + element: ( + + + + ), + }; + }, + }); +} +``` diff --git a/docs/frontend-system/architecture/04-plugins.md b/docs/frontend-system/architecture/04-plugins.md index a1e85f8407..042d4fbc36 100644 --- a/docs/frontend-system/architecture/04-plugins.md +++ b/docs/frontend-system/architecture/04-plugins.md @@ -6,4 +6,88 @@ sidebar_label: Plugins description: Frontend plugins --- -> **NOTE: The new frontend system is in a highly experimental phase** +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +## Introduction + + + +## Creating a Plugin + + + +```ts +export const myPlugin = createPlugin({ + id: 'my-plugin', +}); +``` + + + +### Plugin ID + + + +### Plugin Extensions + + + +### Plugin Routes + + + +### Plugin External Routes + + + +### Plugin Feature Flags + + + +## Installing a Plugin in an App + + diff --git a/docs/frontend-system/architecture/05-extension-overrides.md b/docs/frontend-system/architecture/05-extension-overrides.md index 743a5ea59e..21028627bc 100644 --- a/docs/frontend-system/architecture/05-extension-overrides.md +++ b/docs/frontend-system/architecture/05-extension-overrides.md @@ -1,9 +1,167 @@ --- -id: extension overrides +id: extension-overrides title: Frontend Extension Overrides sidebar_label: Extension Overrides # prettier-ignore description: Frontend extension overrides --- -> **NOTE: The new frontend system is in a highly experimental phase** +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +## Introduction + +An extension override is a building block of the frontend system that allows you to programmatically override app or plugin extensions anywhere in your application. Since the entire application is built mostly out of extensions from the bottom up, this is a powerful feature. You can use it for example to provide your own app root layout, to replace the implementation of a Utility API with a custom one, to override how the catalog page renders itself, and much more. + +In general, most features should have a good level of customization built into them, so that users do not have to leverage extension overrides to achieve common goals. A well written feature often has [configuration](../../conf/) settings, or uses extension inputs for extensibility where applicable. An example of this is the search plugin, which allows you to provide result renderers as inputs rather than replacing the result page wholesale just to tweak how results are shown. Adopters should take advantage of those when possible, and only use extension overrides when it's necessary to entirely replace the extension. Check the respective extension documentation for guidance. + +## Override App Extensions + +In order to override an app extension, you must create a new extension and add it to the list of overridden features. The steps are: create your extension overrides and use them in Backstage. + +### Example + +In the example below, we create a file that exports custom extensions for the app's `light` and `dark` themes: + +```tsx title="packages/app/src/themes.ts" +import { + createThemeExtension, + createExtensionOverrides +} from '@backstage/frontend-plugin-api'; +import { apertureThemes } from './themes'; +import { ApertureLightIcon, ApertureDarkIcon } from './icons'; + +// Creating a light theme extension +const apertureLightTheme = createThemeExtension({ + // highlight-start + namespace: 'app', + name: 'light', + // highlight-end + title: 'Aperture Light Theme', + variant: 'light', + icon: , + Provider: ({ children }) => ( + + ), +}); + +// Creating a dark theme extension +const apertureDarkTheme = createThemeExtension({ + // highlight-start + namespace: 'app', + name: 'dark', + // highlight-end + title: 'Aperture Dark Theme', + variant: 'dark', + icon: , + Provider: ({ children }) => ( + + ), +}); + +// Creating an extension overrides preset +export createExtensionOverrides({ + extensions: [apertureLightTheme, apertureDarkTheme] +}); +``` + +Note that we declare `namespace` as `'app'` while creating the themes, so the system knows we are overriding app extensions. Additionally, to specifically override the `light` and `dark` theme extensions, we set the `name` option to `light` and `dark`. Therefore, to override app theme extensions, we ensure that the extension `namespace` and `name` match those of the default app theme extension definitions. + +Now we are able to use the overrides in a Backstage app: + +```tsx title="packages/app/src/App.tsx" +import { createApp } from '@backstage/frontend-app-api'; +import themeOverrides from './themes'; + +const app = createApp({ + // highlight-next-line + features: [themeOverrides], +}); + +export default app.createRoot(). +``` + +If the plugin you want to change is internal to your company or you just want to replace one of the application's core extensions, you can decide to store the overrides code directly in the app package or extract them to a separate package. + +Note that it can still be a good idea to split your overrides out into separate packages in large projects. But it's up to you to decide how to group the extensions into extension overrides. + +## Override Plugin Extensions + +To override an extension that is provided by a plugin, you need to provide a new extension that has the same ID as the existing extension. That is, all kind, namespace, and name options must match the extension you want to replace. This means that you typically need to provide an explicit `namespace` when overriding extensions from a plugin. + +:::info +We recommend that plugin developers share the extension IDs in their plugin documentation, but usually you can infer the ID by following the [naming patterns](./08-naming-patterns.md) documentation. +::: + +### Example + +Imagine you have a plugin with the ID `'search'`, and the plugin provides a page extension that you want to fully override with your own custom component. To do so, you need to create your page extension with an explicit `namespace` option that matches that of the plugin that you want to override, in this case `'search'`. If the existing extension also has an explicit `name` you'd need to set the `name` of your override extension to the same value as well. + +```tsx title="packages/app/src/search.ts" +import { createPageExtension } from '@backstage/frontend-plugin-api'; + +// Creating a custom search page extension +const customSearchPage = createPageExtension({ + // highlight-next-line + namespace: 'search', + // Omitting name since it is the index plugin page + defaultPath: '/search', + loader: () => import('./SearchPage').then(m => m.), +}); + +export createExtensionOverrides({ + extensions: [customSearchPage] +}); +``` + +Don't forget to configure your overrides in the `createApp` function: + +```tsx title="packages/app/src/App.tsx" +import { createApp } from '@backstage/frontend-app-api'; +import searchOverrides from './search'; + +const app = createApp({ + // highlight-next-line + features: [searchOverrides], +}); + +export default app.createRoot(); +``` + +Now let's talk about the last override case, orphan extensions. + +## Create Standalone Extensions + +Sometimes you just need to quickly create a new extension and not overwrite an app extension or plugin. You can also use overrides to create extensions, but remember that if you want to make this extension available for installation by other users, we recommend providing it via a plugin in a separate package. + +### Example + +Imagine you want to create a page that is currently only used by your application, like an Institutional page, for example. You can use overrides to extend the Backstage app to render it. To do so, simply create a page extension and pass it to the app as an override: + +```tsx title="packages/app/src/App.ts" +import { createApp } from '@backstage/frontend-app-api'; +import { + createPageExtension, + createExtensionOverrides, +} from '@backstage/frontend-plugin-api'; + +const app = createApp({ + features: [ + createExtensionOverrides({ + extensions: [ + // highlight-start + createPageExtension({ + name: 'institutional', + defaultPath: '/institutional', + loader: () => + import('./institutional').then(m => ), + }), + // highlight-end + ], + }), + ], +}); + +export default app.createRoot(); +``` + +Note that we are omitting `namespace` when creating the page extension. When we omit `namespace`, we are telling the system the new extension is standalone and not an application or plugin extension! diff --git a/docs/frontend-system/architecture/06-utility-apis.md b/docs/frontend-system/architecture/06-utility-apis.md index 135f60744a..fe714793d7 100644 --- a/docs/frontend-system/architecture/06-utility-apis.md +++ b/docs/frontend-system/architecture/06-utility-apis.md @@ -6,6 +6,25 @@ sidebar_label: Utility APIs description: Utility APIs --- -> **NOTE: The new frontend system is in a highly experimental phase** +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -See [Utility APIs docs](../../api/utility-apis.md). +## Overview + +Utility APIs are pieces of standalone functionality, interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/03-extensions.md) that can have inputs, be replaced, and be declaratively configured in your app-config. + +A common example of a utility API is a client interface to interact with the backend part of a plugin, such as the catalog client. Any frontend plugin can then request an implementation of that interface to make requests through. + +The following diagram shows a hypothetical application, which depends on two plugins and also provides some extra overrides. Note that both the plugins and the core framework provide utility APIs, and that they depended on each other. The app also chooses to use its overrides mechanism to supply a replacement implementation of one API, which takes precedence over the default one. Thus, all consumers of that API will be sure to get that new implementation provided to them. + +![frontend system utility apis diagram](../../assets/frontend-system/architecture-utility-apis.drawio.svg) + +## Extension structure + +All utility APIs implement the `createApiExtension.factoryDataRef` output data type, and must attach exclusively to the `core` extension's `apis` input no matter who provided them. These defaults are provided out of the box by the `createApiExtension` framework function. + +Since utility APIs are extensions, they can also have inputs in advanced use cases. This is occasionally useful for complex APIs that can themselves be extended with additional programmatic functionality by adopters. + +## Links + +- The [Utility APIs section](../utility-apis/01-index.md) of the plugin docs +- The legacy docs on [utility APIs](../../api/utility-apis.md) diff --git a/docs/frontend-system/architecture/07-routes.md b/docs/frontend-system/architecture/07-routes.md index 354eaeda13..11586573be 100644 --- a/docs/frontend-system/architecture/07-routes.md +++ b/docs/frontend-system/architecture/07-routes.md @@ -6,6 +6,432 @@ sidebar_label: Routes description: Frontend routes --- -> **NOTE: The new frontend system is in a highly experimental phase** +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** -See [routing system docs](../../plugins/composability.md#routing-system) +## Introduction + +Each Backstage plugin is an isolated piece of functionality that doesn't typically communicate directly with other plugins. In order to achieve this, there are many parts of the frontend system that provide a layer of indirection for cross-plugin communication, and the routing system is one of them. + +The Backstage routing system makes it possible to implement navigation across plugin boundaries, without each individual plugin knowing the concrete path or location of other plugins in the routing hierarchy, or even its own. This is achieved through the concept of route references, which are opaque reference values that can be shared and used to create concrete links to different parts of an app. The route ref paths can be configured both at plugin level (by plugin developers) and at the app level (by integrators). It is up to plugin developers to create route references for any page content in their plugin that they want it to be possible to link to or from. + +## Route References + +Plugin developers create a `RouteRef` to expose a path in Backstage's routing system. You will see below how routes are defined programmatically, but before diving into code, let us explain how to configure them at the app level. In spite of the fact that plugin developers choose a default route path for the routes their plugin provides, paths are configurable, so app integrators can set a custom path to a route whenever they like to (more information in the following sections). + +There are three types of route references: regular route, sub route, and external route, and we will cover both the concept and code definition for each. + +### Creating a Route Reference + +Route references, also known as "absolute" or "regular" routes, are created as follows: + +```tsx title="plugins/catalog/src/routes.ts" +import { createRouteRef } from '@backstage/frontend-plugin-api'; + +// Creates a route reference, which is not yet associated with any plugin page +export const indexRouteRef = createRouteRef(); +``` + +Note that you often want to create the route references themselves in a different file than the one that creates the plugin instance, for example a top-level `routes.ts`. This is to avoid circular imports when you use the route references from other parts of the same plugin. + +Route refs do not have any behavior themselves. They are an opaque value that represents route targets in an app, which are bound to specific paths at runtime. Their role is to provide a level of indirection to help link together different pages that otherwise wouldn't know how to route to each other. + +### Providing Route References to Plugins + +The code snippet in the previous section does not indicate which plugin the route belongs to. To do so, you have to use it in the creation of any kind of routable extension, such as a page extension: + +```tsx title="plugins/catalog/src/plugin.tsx" +import React from 'react'; +import { + createPlugin, + createPageExtension, +} from '@backstage/frontend-plugin-api'; +import { indexRouteRef } from './routes'; + +const catalogIndexPage = createPageExtension({ + // The `name` option is omitted because this is an index page + defaultPath: '/entities', + // highlight-next-line + routeRef: indexRouteRef, + loader: () => import('./components').then(m => ), +}); + +export default createPlugin({ + id: 'catalog', + // highlight-start + routes: { + index: indexRouteRef, + }, + // highlight-end + extensions: [catalogIndexPage], +}); +``` + +In the example above we associated the `indexRouteRef` with the `catalogIndexPage` extension and provided both the route ref and page via the Catalog plugin. So, When this plugin is installed in the app, the index page will become associated with the newly created `RouteRef`, making it possible to use the route ref to navigate the page extension. + +It may seem unclear why we configure the `routes` option when creating a plugin as the route has already been passed to the extension. We do that to make it possible for other plugins to route to our page, which is explained in detail in the [binding routes](#binding-external-route-references) section. + +### Defining References with Path Parameters + +Route references optionally accept a `params` option, which will require the listed parameter names to be present in the route path. Here is how you create a reference for a route that requires a `kind`, `namespace` and `name` parameters, like in this path `/entities/:kind/:namespace/:name`: + +```tsx title="plugins/catalog/src/routes.ts" +import { createRouteRef } from '@backstage/frontend-plugin-api'; + +export const detailsRouteRef = createRouteRef({ + // The parameters that must be included in the path of this route reference + // highlight-next-line + params: ['kind', 'namespace', 'name'], +}); +``` + +### Using a Route Reference + +Route references can be used to link to page in the same plugin, or to pages in different plugins. In this section we will cover the first scenario. If you are interested in linking to a page of a different plugin, please go to the [external routes](#external-route-references) section below. + +Suppose we are creating a plugin that renders a Catalog index page with a link to a "Foo" component details page. Here is the code for the index page: + +```tsx title="plugins/catalog/src/components/IndexPage.tsx" +import React from 'react'; +import { useRouteRef } from '@backstage/frontend-plugin-api'; +import { detailsRouteRef } from '../routes'; + +export const IndexPage = () => { + // highlight-next-line + const getDetailsPath = useRouteRef(detailsRouteRef); + return ( +
+

Index Page

+ + See "Foo" details + +
+ ); +}; +``` + +We use the `useRouteRef` hook to create a link generator function that returns the details page path. We then call the link generator, passing it an object with the kind, namespace, and name. These parameters are used to construct a concrete path to the "Foo" details page. + +Let's see how the details page can get the parameters from the URL: + +```tsx title="plugins/catalog/src/components/DetailsPage.tsx" +import React from 'react'; +import { useRouteRefParams } from '@backstage/frontend-plugin-api'; +import { detailsRouteRef } from '../routes'; + +export const DetailsPage = () => { + // highlight-next-line + const params = useRouteRefParams(detailsRouteRef); + return ( +
+

Details Page

+
    + {/* highlight-start */} +
  • Kind: {params.kind}
  • +
  • Namespace: {params.namespace}
  • +
  • Name: {params.name}
  • + {/* highlight-end */} +
+
+ ); +}; +``` + +In the code above, we are using the `useRouteRefParams` hook to retrieve the entity-composed id from the URL. The parameter object contains three values: kind, namespace, and name. We can display these values or call an API using them. + +Since we are linking to pages of the same package, we are using a route ref directly. However, in the following sections, you will see how to link to pages of different plugins. + +## External Route References + +External routes are made for linking to a page of an external plugin. +For this section example, let's assume that we want to link from the Catalog entities list page to the Scaffolder create component page. + +We don't want to reference the Scaffolder plugin directly, since that would create an unnecessary dependency. It would also provide little flexibility in allowing the app to tie plugins together, with the links instead being dictated by the plugins themselves. To solve this, we use an `ExternalRouteRef`. Much like regular route references, they can be passed to `useRouteRef` to create concrete URLs, but they can not be used in page extensions and instead have to be associated with a target route using route bindings in the app. + +We create a new `RouteRef` inside the Scaffolder plugin, using a neutral name that describes its role in the plugin rather than a specific plugin page that it might be linking to, allowing the app to decide the final target. If the Catalog entity list page for example wants to link the Scaffolder create component page in the header, it might declare an `ExternalRouteRef` similar to this: + +```tsx title="plugins/catalog/src/routes.ts" +import { createExternalRouteRef } from '@backstage/frontend-plugin-api'; + +export const createComponentExternalRouteRef = createExternalRouteRef(); +``` + +External routes are also used in a similar way as regular routes: + +```tsx title="plugins/catalog/src/components/IndexPage.tsx" +import React from 'react'; +import { useRouteRef } from '@backstage/frontend-plugin-api'; +import { createComponentExternalRouteRef } from '../routes'; + +export const IndexPage = () => { + // highlight-next-line + const getCreateComponentPath = useRouteRef(createComponentExternalRouteRef); + return ( +
+

Index Page

+ {/* highlight-next-line */} + Create Component +
+ ); +}; +``` + +Given the above binding, using `useRouteRef(createComponentExternalRouteRef)` within the Catalog plugin will let us create a link to whatever path the Scaffolder create component page is mounted at. Note that there is no direct dependency between the Catalog plugin and Scaffolder, that is, we are not importing the `createComponentExternalRouteRef` from the Scaffolder package. + +Now the only thing left is to provide the page and external route via a plugin: + +```tsx title="plugins/catalog/src/plugin.tsx" +import React from 'react'; +import { + createPlugin, + createPageExtension, + useRouteRef, +} from '@backstage/frontend-plugin-api'; +import { indexRouteRef, createComponentExternalRouteRef } from './routes'; + +const catalogIndexPage = createPageExtension({ + defaultPath: '/entities', + routeRef: indexRouteRef, + loader: () => import('./components').then(m => ), +}); + +export default createPlugin({ + id: 'catalog', + routes: { + index: indexRouteRef, + }, + // highlight-start + externalRoutes: { + createComponent: createComponentExternalRouteRef, + }, + extensions: [catalogIndexPage], + // highlight-end +}); +``` + +External routes can also have parameters. For example, if you want to link to an entity's details page from Scaffolder, you'll need to create an external route that receives the same parameters the Catalog details page expects: + +```tsx title="plugins/scaffolder/src/routes.ts" +import { createExternalRouteRef } from '@backstage/frontend-plugin-api'; + +export const entityDetailsExternalRouteRef = createExternalRouteRef({ + // highlight-next-line + params: ['kind', 'namespace', 'name'], +}); +``` + +Now let's move on and configure the app to resolve these external routes, so that the Scaffolder links to the Catalog entity page, and the Catalog links to the Scaffolder page. + +### Binding External Route References + +The association of external routes is controlled by the app. Each `ExternalRouteRef` of a plugin should be bound to an actual `RouteRef`, usually from another plugin. The binding process happens once at app startup, and is then used through the lifetime of the app to help resolve concrete route paths. + +Using the above example of the Catalog entities list page to the Scaffolder create component page, we might do something like this in the app configuration file: + +```yaml title="app-config.yaml" +app: + routes: + bindings: + # point to the Scaffolder create component page when the Catalog create component ref is used + # highlight-next-line + catalog.createComponent: scaffolder.index + # point to the Catalog details page when the Scaffolder component details ref is used + # highlight-next-line + scaffolder.componentDetails: catalog.details +``` + +We also have the ability to express this in code as an option to `createApp`, but you of course only need to use one of these two methods: + +```tsx title="packages/app/src/App.tsx" +import { createApp } from '@backstage/frontend-app-api'; +import catalog from '@backstage/plugin-catalog'; +import scaffolder from '@backstage/plugin-scaffolder'; + +const app = createApp({ + // highlight-start + bindRoutes({ bind }) { + bind(catalog.externalRoutes, { + createComponent: scaffolder.routes.createComponent, + }); + bind(scaffolder.externalRoutes, { + componentDetails: catalog.routes.details, + }); + }, + // highlight-end +}); + +export default app.createRoot(); +``` + +Note that we are not importing and using the `RouteRef`s directly in the app, and instead rely on the plugin instance to access routes of the plugins. This provides better namespacing and discoverability of routes, as well as reduce the number of separate exports from each plugin package. + +Another thing to note is that this indirection in the routing is particularly useful for open source plugins that need to provide flexibility in how they are integrated. For plugins that you build internally for your own Backstage application, you can choose to use direct imports or even concrete route path strings directly. Although there can be some benefits to using the full routing system even in internal plugins: it can help you structure your routes, and as you will see further down it also helps you manage route parameters. + +### Optional External Route References + +It is possible to define an `ExternalRouteRef` as optional, so it is not required to bind it in the app. + +```tsx title="plugins/catalog/src/routes.ts" +import { createExternalRouteRef } from '@backstage/frontend-plugin-api'; + +export const createComponentExternalRouteRef = createExternalRouteRef({ + // highlight-next-line + optional: true, +}); +``` + +When calling `useRouteRef` with an optional external route, its return signature is changed to `RouteFunc | undefined`, and the returned value can be used to decide whether a certain link should be displayed or if an action should be taken: + +```tsx title="plugins/catalog/src/components/IndexPage.tsx" +import React from 'react'; +import { useRouteRef } from '@backstage/frontend-plugin-api'; +import { createComponentExternalRouteRef } from '../routes'; + +export const IndexPage = () => { + const getCreateComponentPath = useRouteRef(createComponentExternalRouteRef); + return ( +
+

Index Page

+ {/* Rendering the link only if the getCreateComponentPath is defined */} + {/* highlight-start */} + {getCreateComponentPath && ( + Create Component + )} + {/* highlight-end */} +
+ ); +}; +``` + +## Sub Route References + +The last kind of route ref that can be created is a `SubRouteRef`, which can be used to create a route ref with a fixed path relative to an absolute `RouteRef`. They are useful if you have a page that internally is mounted at a sub route of a page extension component, and you want other plugins to be able to route to that page. And they can be a useful utility to handle routing within a plugin itself as well. + +For example: + +```tsx title ="plugins/catalog/src/routes.ts" +import { + createRouteRef, + createSubRouteRef, +} from '@backstage/frontend-plugin-api'; + +export const indexRouteRef = createRouteRef(); +// highlight-start +export const detailsSubRouteRef = createSubRouteRef({ + parent: indexRouteRef, + path: '/details', +}); +// highlight-end +``` + +There are substantial differences between creating subroutes and regular or external routes because subroutes are associated with regular routes, and the sub route path must be specified. The path string must include the parameters if this sub route has them: + +```tsx title ="plugins/catalog/src/routes.ts" +// Omitting rest of the previous example file +export const detailsSubRouteRef = createSubRouteRef({ + parent: indexRouteRef, + // highlight-next-line + path: '/:name/:namespace/:kind', +}); +``` + +Using subroutes in a page extension is as simple as this: + +```tsx title="plugins/catalog/src/components/IndexPage.ts" +import React from 'react'; +import { Routes, Route, useLocation } from 'react-router-dom'; +import { useRouteRef } from '@backstage/frontend-plugin-api'; +import { indexRouteRef, detailsSubRouteRef } from '../routes'; +import { DetailsPage } from './DetailsPage'; + +export const IndexPage = () => { + const { pathname } = useLocation(); + const getIndexPath = useRouteRef(indexRouteRef); + const getDetailsPath = useRouteRef(detailsSubRouteRef); + return ( +
+

Index Page

+ {/* Linking to the details sub route */} + {pathname === getIndexPath() ? ( + // highlight-start + + Show details + + // highlight-end + ) : ( + // highlight-next-line + Hide details + )} + {/* Registering the details sub route */} + + } /> + +
+ ); +}; +``` + +This is how you can get the parameters of a sub route URL: + +```tsx title="plugins/catalog/src/components/DetailsPage.ts" +import React from 'react'; +import { useParams } from 'react-router-dom'; + +export const DetailsPage = () => { + // highlight-next-line + const params = useParams(); + return ( +
+

Details Sub Page

+
    + {/* highlight-start */} +
  • Kind: {params.kind}
  • +
  • Namespace: {params.namespace}
  • +
  • Name: {params.name}
  • + {/* highlight-end */} +
+
+ ); +}; +``` + +Finally, see how a plugin can provide subroutes: + +```tsx title="plugins/catalog/src/plugin.ts" +import React from 'react'; +import { + createPlugin, + createPageExtension, +} from '@backstage/frontend-plugin-api'; +import { indexRouteRef, detailsSubRouteRef } from './routes'; + +const catalogIndexPage = createPageExtension({ + defaultPath: '/entities', + routeRef: indexRouteRef, + loader: () => import('./components').then(m => ), +}); + +export default createPlugin({ + id: 'catalog', + routes: { + index: indexRouteRef, + // highlight-next-line + details: detailsSubRouteRef, + }, + extensions: [catalogIndexPage], +}); +``` diff --git a/docs/frontend-system/architecture/08-naming-patterns.md b/docs/frontend-system/architecture/08-naming-patterns.md new file mode 100644 index 0000000000..0916d9fd51 --- /dev/null +++ b/docs/frontend-system/architecture/08-naming-patterns.md @@ -0,0 +1,142 @@ +--- +id: naming-patterns +title: Frontend System Naming Patterns +sidebar_label: Naming Patterns +# prettier-ignore +description: Naming patterns in the frontend system +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +These are the naming patterns to adhere to within the frontend system. They help us keep exports and IDs consistent across packages and make it easier to understand the usage and intent of exports and IDs. + +As a rule, all names should be camel case, with the exceptions of plugin and extension IDs, which should use kebab case. + +## Plugins + +| Description | Pattern | Examples | +| ----------- | ------------ | ------------------------------------- | +| ID | `''` | `'catalog'`, `'user-settings'` | +| Symbol | `Plugin` | `catalogPlugin`, `userSettingsPlugin` | + +Example: + +```ts +// This declaration is only for internal usage in tests. This could also be a direct default export. +export const userSettingsPlugin = createPlugin({ + id: 'user-settings', + ... +}) + +// The plugin instance should be the default export of the package, typically this is placed in src/index.ts +export { userSettingsPlugin as default } from './plugin'; +``` + +Note that while we use this naming pattern for the plugin instance this is only for internal usage within the package. Plugins are always exported as the default export of the plugin package. + +## Extensions + +| Description | Pattern | Examples | +| ----------- | ------------------------------- | ------------------------------------------------------------------- | +| Creator | `createExtension` | `createPageExtension`, `createEntityCardExtension` | +| ID | `[:][/]` | `'core.nav'`, `'page:user-settings'`, `'entity-card:catalog/about'` | +| Symbol | `[][]` | `coreNav`, `userSettingsPage`, `catalogAboutEntityCard` | + +When you create a new extension you never provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the extension creator function used to create the extension, the only exception is if you use `createExtension` directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted. + +Example: + +```ts +// This is an extension creator that is used to create an extension of the 'page' kind. +export function createPageExtension(options) { + return createExtension({ + kind: 'page', // Kinds are kebab-case + // ...options + }); +} + +// The namespace is inferred from the plugin ID, in this case 'catalog' +// The final ID for this extension will be 'page:catalog/entity' +const catalogEntityPage = createPageExtension({ + name: 'entity', + // ... +}); + +// The name is omitted, because the catalog plugin only provides a single extension of this kind +// The final ID for this extension will be 'search-result-list-item:catalog' +const catalogSearchResultListItem = createSearchResultListItemExtension({ + // ... +}); + +// Note that the extensions themselves are not exported, only the plugin instance +export const catalogPlugin = createPlugin({ + id: 'catalog', + extensions: [catalogEntityPage, catalogSearchResultListItem /* ... */], +}); +``` + +## Extension Data + +| Description | Pattern | Examples | +| -------------------- | ------------------------------------- | ----------------------------------------------------------------------------- | +| Interface | `ExtensionData` | `SearchResultItemExtensionData` | +| Standalone Reference | `ExtensionDataRef` | `searchResultItemExtensionDataRef` | +| Standalone ID | `.` | `'search.search-result-item'` | +| Grouped Reference | `ExtensionData.` | `coreExtensionData.reactElement`, `catalogFilterExtensionData.functionFilter` | +| Grouped ID | `.` | `'core.react-element'`, `'catalog-filter.function-filter'` | +| Creator Reference | `createExtension.DataRef` | `createGraphiQLEndpointExtension.endpointDataRef` | +| Creator ID | `..` | `'graphiql.graphiql-endpoint.endpoint'` | + +Extension data references can be defined in a couple of different ways, depending on the intended usage, all of which are covered below. + +#### Standalone Extension Data + +The most simple way of defining extension data is a standalone reference. This is useful when you want to export a single reference that isn't closely tied to a specific kind of extension. Because this creates an extra export for each reference, the two other ways of defining extension data are preferred when possible. + +```ts +// A separate named type declaration is only needed for bespoke complex extension data types +export interface SearchResultItemExtensionData { + /* ... */ +} + +export const searchResultItemExtensionDataRef = + createExtensionDataRef( + 'search.search-result-item', + ); +``` + +#### Grouped Extension Data + +This way of defining extension data is similar to the standalone way, but it used when you want to export multiple pieces of grouped extension data for general use. This avoids separate exports and helps make related extension data references easier to discover. The name of the group should generally by the same as the namespace of the exporting package, typically the plugin ID. If the group needs to be more specific it should be prefixed with the namespace. + +```ts +export const coreExtensionData = { + reactElement: createExtensionDataRef('core.react-element'), + routePath: createExtensionDataRef('core.route-path'), +}; +``` + +#### Extension Creator Extension Data + +This is a convenient way of defining extension data when that data is only meant to be produced by a specific extension creator. It avoids additional exports and clearly signals that this piece of data belongs to this particular kind of extension. + +```ts +export function createGraphiQLEndpointExtension(options) { + /* ... */ +} + +// Use a TypeScript namespace to merge the extension data references with the extension creator +export namespace createGraphiQLEndpointExtension { + export const endpointDataRef = createExtensionDataRef( + 'graphiql.graphiql-endpoint.endpoint', + ); +} +``` + +## Extension Inputs + +Extension inputs do not have naming patterns for all types of input, but there are some specific use-cases where we encourage using a recognizable input name. + +| Name | Description | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `children` | An extension input that accepts `coreExtensionData.reactElement` data and nothing else, used in a way that is equivalent of the `children` property in React. | diff --git a/docs/frontend-system/architecture/08-references.md b/docs/frontend-system/architecture/08-references.md new file mode 100644 index 0000000000..24a9e5df2a --- /dev/null +++ b/docs/frontend-system/architecture/08-references.md @@ -0,0 +1,17 @@ +--- +id: references +title: Value References +sidebar_label: Value References +# prettier-ignore +description: Value References +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + + diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md new file mode 100644 index 0000000000..f8d80f9382 --- /dev/null +++ b/docs/frontend-system/building-plugins/01-index.md @@ -0,0 +1,222 @@ +--- +id: index +title: Building Frontend Plugins +sidebar_label: Overview +# prettier-ignore +description: Building frontend plugins using the new frontend system +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +This section covers how to build your own frontend [plugins](../architecture/04-plugins.md) and +[overrides](../architecture/05-extension-overrides.md). They are sometimes collectively referred to as +frontend _features_, and what you install to build up a Backstage frontend [app](../architecture/02-app.md). + +## Creating a new plugin + +This guide assumes that you already have a Backstage project set up. Even if you only want to develop a single plugin for publishing, we still recommend that you do so in a standard Backstage monorepo project, as you often end up needing multiple packages. For instructions on how to set up a new project, see our [getting started](../../getting-started/index.md#prerequisites) documentation. + +To create a frontend plugin, run `yarn new`, select `plugin`, and fill out the rest of the prompts. This will create a new package at `plugins/`, which will be the main entrypoint for your plugin. + +:::info +The created plugin will currently be templated for use in the legacy frontend system, and you will need to replace the existing plugin wiring code. +::: + +## The plugin instance + +The starting point of a frontend plugin is the `createPlugin` function, which accepts a single options object as its only parameter. It is imported from `@backstage/frontend-plugin-api`, which is where you will find most of the common APIs for building plugins. + +This is how to create a minimal plugin: + +```tsx title="in src/plugin.ts" +import { createPlugin } from '@backstage/frontend-plugin-api'; + +export const examplePlugin = createPlugin({ + id: 'example', + extensions: [], +}); +``` + +```tsx title="in src/index.ts" +export { examplePlugin as default } from './plugin'; +``` + +Note that we export the plugin as the default export of our package from `src/index.ts`. This is important, as it is how users of our plugin are able to seamlessly install the plugin package in a Backstage app without having to reference the plugin instance through code. + +The plugin ID should be a lowercase dash-separated string, while the plugin instance variable should be the camel case version of the ID with a `Plugin` suffix. For more details on naming patterns within the frontend system, see [the article on naming patterns](../architecture/08-naming-patterns.md). By sticking to these naming patterns you ensure that users of your plugin more easily recognize the exports and features provided by your plugin. + +## Adding extensions + +The plugin that we created above is empty, and doesn't provide any actual functionality. To add functionality to a plugin you need to create and provide it with one or more [extensions](../architecture/03-extensions.md). Let's continue by adding a standalone page to our plugin, as well as a navigation item that allows users to navigate to the page. + +To create a new extension you typically use pre-defined [extension creators](../architecture/03-extensions.md#extension-creators), provided either by the framework itself or by other plugins. In this case we'll need to use `createPageExtension` and `createNavItemExtension`, both from `@backstage/frontend-plugin-api`. We will also need to [create a route reference](../architecture/07-routes.md#creating-a-route-reference) to use as a reference for out page, allowing us to dynamically create URLs that link to our page. + +```tsx title="in src/routes.ts" +import { createRouteRef } from '@backstage/frontend-plugin-api'; + +// Typically all routes are defined in src/routes.ts, in order to avoid circular imports. + +// This will be the route reference for our example page. If you want to link +// to the page from somewhere else, you can use this reference to generate the target path. +export const rootRouteRef = createRouteRef(); +``` + +```tsx title="in src/plugin.ts" +import { + createPlugin, + createPageExtension, + createNavItemExtension, +} from '@backstage/frontend-plugin-api'; +import { rootRouteRef } from './routes'; + +// Note that these extensions aren't exported, only the plugin itself is. +// You can export it locally for testing purposes, but don't export it from the plugin package. +const examplePage = createPageExtension({ + routeRef: rootRouteRef, + + // This is the default path of this page, but integrators are free to override it + defaultPath: '/example', + + // Page extensions are always dynamically loaded using React.lazy(). + // All of the functionality of this page is implemented in the + // ExamplePage component, which is a regular React component. + // highlight-next-line + loader: () => import('./components/ExamplePage').then(m => ), +}); + +// This nav item is provided to the app.nav extension, and will by default be rendered as a sidebar item +const exampleNavItem = createNavItemExtension({ + routeRef: rootRouteRef, + title: 'Example', + icon: ExampleIcon, // Custom SvgIcon, or one from the Material UI icon library +}); + +// The same plugin as above, now with the extensions added +export const examplePlugin = createPlugin({ + id: 'example', + extensions: [examplePage, exampleNavItem], + // We can also make routes available to other plugins. + // highlight-start + routes: { + root: rootRouteRef, + }, + // highlight-end +}); +``` + +What we've built here is a very common type of plugin. It's a top-level tool that provides a single page, along with a method for navigating to that page. The implementation of the page component, in this case the highlighted `ExamplePage`, can be arbitrarily complex. It can be anything from a single simple information page, to a full-blown application with multiple sub-pages. + +We have also provided external access to our route reference by passing it to the plugin `routes` option. This makes it possible for app integrators to bind an external link from a different plugin to our plugin page. You can read more about how this works in the [External Route References](../architecture/07-routes.md#external-route-references) section. + +## Utility APIs + +Another type of extensions that is commonly used are [Utility APIs](../utility-apis/01-index.md). They can encapsulate shared pieces of functionality of your plugin, for example an API client for a backend service. You can optionally export your Utility API for other plugins to use, or allow integrators to replace the implementation of your Utility API with their own. For details on how to define and provide your own Utility API in your plugin, see the section on [creating Utility APIs](../utility-apis/02-creating.md). + +What we'll show here is a complete example of a simple Utility API used only within the plugin itself: + +```tsx title="src/api.ts - Defining an interface, API reference, and default implementation" +import { createApiRef } from '@backstage/frontend-plugin-api'; + +export interface ExampleApi { + getExample(): { example: string }; +} + +export const exampleApiRef = createApiRef({ + id: 'plugin.example', +}); + +export class DefaultExampleApi implements ExampleApi { + getExample(): { example: string } { + return { example: 'Hello World!' }; + } +} +``` + +```tsx title="src/components/ExamplePage.tsx - Using the API in our page component" +import { useApi } from '@backstage/frontend-plugin-api'; +import { exampleApiRef } from '../api'; + +export function ExamplePage() { + // highlight-next-line + const exampleApi = useApi(exampleApiRef); + + return ( +
+

Example Page

+

Example: {exampleApi.getExample().example}

+
+ ); +} +``` + +```tsx title="in src/plugin.ts - Registering a factory for our API" +import { + createApiFactory, + createApiExtension, +} from '@backstage/frontend-plugin-api'; +import { exampleApiRef, DefaultExampleApi } from './api'; + +// highlight-add-start +const exampleApi = createApiExtension({ + factory: createApiFactory({ + api: exampleApiRef, + deps: {}, + factory: () => new DefaultExampleApi(), + }), +}); +// highlight-add-end + +/* Omitted definitions for examplePage, exampleNavItem, and rootRouteRef. */ + +export const examplePlugin = createPlugin({ + id: 'example', + extensions: [ + // highlight-add-next-line + exampleApi, + examplePage, + exampleNavItem, + ], + routes: { + root: rootRouteRef, + }, +}); +``` + +## Plugin specific extensions + +There are many different plugins that you can extend with additional functionality through extensions. One such plugin is [the catalog plugin](../../features/software-catalog/), one of the core features of Backstage. It lets you catalog the software in your organization, where each item in the catalog has its own page that can be populated with tools and information relating to that catalog entity. In this example we will explore how our plugin can provide such a tool to display on an entity page. + +```tsx title="in src/plugin.ts - An example entity content extension" +import { createEntityContentExtension } from '@backstage/plugin-catalog-react'; + +// Entity content extensions are similar to page extensions in that they are rendered at a route, +// although they also have a title to support in-line navigation between the different content. +// Just like a page extension the content is lazy loaded, and you can also provide a +// route reference if you want to be able to generate a URL that links to the content. +const exampleEntityContent = createEntityContentExtension({ + defaultPath: 'example', + defaultTitle: 'Example', + loader: () => + import('./components/ExampleEntityContent').then(m => ( + + )), +}); + +export const examplePlugin = createPlugin({ + id: 'example', + extensions: [ + // highlight-add-next-line + exampleEntityContent + exampleApi, + examplePage, + exampleNavItem, + ], + routes: { + root: rootRouteRef, + }, +}); +``` + +The `ExampleEntityContent` itself is again a regular React component where you can implement any functionality you want. To access the entity that the content is being rendered for, you can use the `useEntity` hook from `@backstage/plugin-catalog-react`. You can see a full list of APIs provided by the catalog React library in [the API reference](../../reference/plugin-catalog-react.md). + +For a more complete list of the different types of extensions that you can create for your plugin, see the [extension types](./03-extension-types.md) section. diff --git a/docs/frontend-system/building-plugins/02-testing.md b/docs/frontend-system/building-plugins/02-testing.md new file mode 100644 index 0000000000..c6621d6584 --- /dev/null +++ b/docs/frontend-system/building-plugins/02-testing.md @@ -0,0 +1,235 @@ +--- +id: testing +title: Frontend System Testing Plugins +sidebar_label: Testing +# prettier-ignore +description: Testing plugins in the frontend system +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +# Testing Frontend Plugins + +> NOTE: The new frontend system is in alpha, and some plugins do not yet fully implement it. + +Utilities for testing frontend features and components are available in `@backstage/frontend-test-utils`. + +## Testing React components + +A component can be used for more than one extension, and it should be tested independently of an extension environment. + +Use the `renderInTestApp` helper to render a given component inside a Backstage test app: + +```tsx +import React from 'react'; +import { screen } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/frontend-test-utils'; +import { EntityDetails } from './plugin'; + +describe('Entity details component', () => { + it('should render the entity name and owner', async () => { + await renderInTestApp(); + + await expect( + screen.findByText('The entity "test" is owned by "tools"'), + ).resolves.toBeInTheDocument(); + }); +}); +``` + +To mock [Utility APIs](../architecture/06-utility-apis.md) that are used by your component you can use the `TestApiProvider` to override individual API implementations. In the snippet below, we wrap the component within a `TestApiProvider` in order to mock the catalog client API: + +```tsx +import React from 'react'; +import { screen } from '@testing-library/react'; +import { + renderInTestApp, + TestApiProvider, +} from '@backstage/frontend-test-utils'; +import { stringifyEntityRef } from '@backstage/catalog-model'; +import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; +import { EntityDetails } from './plugin'; + +describe('Entity details component', () => { + it('should render the entity name and owner', async () => { + const catalogApiMock = { + async getEntityFacets() { + return { + facets: { + 'relations.ownedBy': [{ count: 1, value: 'group:default/tools' }], + }, + }, + } + } satisfies Partial; + + const entityRef = stringifyEntityRef({ + kind: 'Component', + namespace: 'default', + name: 'test', + }); + + await renderInTestApp( + + + , + ); + + await expect( + screen.findByText('The entity "test" is owned by "tools"'), + ).resolves.toBeInTheDocument(); + }); +}); +``` + +## Testing extensions + +To facilitate testing of frontend extensions, the `@backstage/frontend-test-utils` package provides a tester class which starts up an entire frontend harness, complete with a number of default features. You can then provide overrides for extensions whose behavior you need to adjust for the test run. + +A number of features (frontend extensions and overrides) are also accepted by the tester. Here are some examples of how these facilities can be useful: + +### Single extension + +In order to test an extension in isolation, you simply need to pass it into the tester factory, then call the render method on the returned instance: + +```tsx +import { screen } from '@testing-library/react'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { indexPageExtension } from './plugin'; + +describe('Index page', () => { + it('should render a the index page', () => { + createExtensionTester(indexPageExtension).render(); + + expect(screen.getByText('Index Page')).toBeInTheDocument(); + }); +}); +``` + +### Extension preset + +There are some extensions that rely on other extensions existence, such as a page that links to another page. In that case, you can add more than one extension to the preset of features you want to render in the test, as shown below: + +```tsx +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { indexPageExtension, detailsPageExtension } from './plugin'; + +describe('Index page', async () => { + it('should link to the details page', () => { + createExtensionTester(indexPageExtension) + // Adding more extensions to the preset being tested + .add(detailsPageExtension) + .render(); + + await expect(screen.findByText('Index Page')).toBeInTheDocument(); + + await userEvent.click(screen.getByRole('link', { name: 'See details' })); + + await expect( + screen.findByText('Details Page'), + ).resolves.toBeInTheDocument(); + }); +}); +``` + +### Mocking apis + +If your extensions requires implementation of APIs that aren't wired up by default, you'll have to add overrides to the preset of features being tested: + +```tsx +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { createApiFactory } from '@backestage/core-plugin-api'; +import { + createExtensionOverrides, + configApiRef, + analyticsApiRef, +} from '@backstage/frontend-plugin-api'; +import { + createExtensionTester, + MockConfigApi, + MockAnalyticsApi, +} from '@backstage/frontend-test-utils'; +import { indexPageExtension } from './plugin'; + +describe('Index page', () => { + it('should capture click events in analytics', async () => { + // Mocking the analytics api implementation + const analyticsApiMock = new MockAnalyticsApi(); + + const analyticsApiOverride = createApiExtension({ + factory: createApiFactory({ + api: analyticsApiRef, + factory: () => analyticsApiMock, + }), + }); + + createExtensionTester(indexPageExtension) + // Overriding the analytics api extension + .add(analyticsApiOverride) + .render(); + + await userEvent.click( + await screen.findByRole('link', { name: 'See details' }), + ); + + expect(analyticsApiMock.getEvents()[0]).toMatchObject({ + action: 'click', + subject: 'See details', + }); + }); +}); +``` + +### Setting configuration + +In the case that your extension can be configured, you can test this capability by passing configuration values as follows: + +```tsx +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { indexPageExtension, detailsPageExtension } from './plugin'; + +describe('Index page', () => { + it('should accepts a custom title via config', async () => { + createExtensionTester(indexPageExtension, { + // Configuration specific of index page + config: { title: 'Custom index' }, + }) + .add(detailsExtensionPage, { + // Configuration specific of details page + config: { title: 'Custom details' }, + }) + .render({ + // Configuration specific of the instance + config: { + app: { + title: 'Custom app', + }, + }, + }); + + await expect( + screen.findByRole('heading', { name: 'Custom app' }), + ).resolves.toBeInTheDocument(); + + await expect( + screen.findByRole('heading', { name: 'Custom index' }), + ).resolves.toBeInTheDocument(); + + await userEvent.click(screen.getByRole('link', { name: 'See details' })); + + await expect( + screen.findByText('Custom details'), + ).resolves.toBeInTheDocument(); + }); +}); +``` + +That's all for testing features! + +## Missing something? + +If there's anything else you think needs to be covered in the docs or that you think isn't covered by the test utilities, please create an issue in the Backstage repository. You are always welcome to contribute as well! diff --git a/docs/frontend-system/building-plugins/03-extension-types.md b/docs/frontend-system/building-plugins/03-extension-types.md new file mode 100644 index 0000000000..2bea073ed6 --- /dev/null +++ b/docs/frontend-system/building-plugins/03-extension-types.md @@ -0,0 +1,59 @@ +--- +id: extension-types +title: Frontend System Extension Types +sidebar_label: Extension Types +# prettier-ignore +description: Extension types provided by the frontend system and core features +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +This section covers many of the [extension types](../architecture/03-extensions.md#extension-creators) available at your disposal when building Backstage frontend plugins. + +## Built-in extension types + +These are the extension types provided by the Backstage frontend framework itself. + +### Api - [Reference](../../reference/frontend-plugin-api.createapiextension.md) + +An API extension is used to add or override [Utility API factories](../utility-apis/01-index.md) in the app. They are commonly used by plugins for both internal and shared APIs. There are also many built-in Api extensions provided by the framework that you are able to override. + +### Component - [Reference](../../reference/frontend-plugin-api.createcomponentextension.md) + +Components extensions are used to override the component associated with a component reference throughout the app. + +### NavItem - [Reference](../../reference/frontend-plugin-api.createnavitemextension.md) + +Navigation item extensions are used to provide menu items that link to different parts of the app. By default nav items are attached to the app nav extension, which by default is rendered as the left sidebar in the app. + +### Page - [Reference](../../reference/frontend-plugin-api.createpageextension.md) + +Page extensions provide content for a particular route in the app. By default pages are attached to the app routes extensions, which renders the root routes. + +### SignInPage - [Reference](../../reference/frontend-plugin-api.createsigninpageextension.md) + +Sign-in page extension have a single purpose - to implement a custom sign-in page. They are always attached to the app root extension and are rendered before the rest of the app until the user is signed in. + +### Theme - [Reference](../../reference/frontend-plugin-api.createthemeextension.md) + +Theme extensions provide custom themes for the app. They are always attached to the app extension and you can have any number of themes extensions installed in an app at once, letting the user choose which theme to use. + +### Translation - [Reference](../../reference/frontend-plugin-api.createtranslationextension.md) + +Translation extension provide custom translation messages for the app. They can be used both to override the default english messages to custom ones, as well as provide translations for additional languages. + +## Core feature extension types + +These are the extension types provided by the Backstage core feature plugins. + +### EntityCard - [Reference](https://github.com/backstage/backstage/blob/master/plugins/catalog-react/api-report-alpha.md) + +Creates entity cards to be displayed on the entity pages of the catalog plugin. + +### EntityContent - [Reference](https://github.com/backstage/backstage/blob/master/plugins/catalog-react/api-report-alpha.md) + +Creates entity content to be displayed on the entity pages of the catalog plugin. + +### SearchResultListItem - [Reference](https://github.com/backstage/backstage/blob/master/plugins/search-react/api-report-alpha.md) + +Creates search result list items for different types of search results, to be displayed in search result lists. diff --git a/docs/frontend-system/index.md b/docs/frontend-system/index.md index 3970c09f00..201718f6d3 100644 --- a/docs/frontend-system/index.md +++ b/docs/frontend-system/index.md @@ -6,10 +6,10 @@ sidebar_label: Introduction description: The Frontend System --- -> **NOTE: The new frontend system is in a highly experimental phase** +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** ## Status -The new frontend system is in an experimental phase and we do not recommend any plugins or apps to migrate. +The new frontend system is in alpha, and only a few plugins support the system so far. We do not yet recommend migrating any apps to the new system. If you add support for the new system to your plugin, please do so under a `/alpha` sub-path export. -You can find an example app setup in [the `app-next` package](https://github.com/backstage/backstage/tree/master/packages/app-next). +You can find an example app setup in the [`app-next` package](https://github.com/backstage/backstage/tree/master/packages/app-next). diff --git a/docs/frontend-system/utility-apis/01-index.md b/docs/frontend-system/utility-apis/01-index.md new file mode 100644 index 0000000000..2d0f0a9698 --- /dev/null +++ b/docs/frontend-system/utility-apis/01-index.md @@ -0,0 +1,43 @@ +--- +id: index +title: Utility APIs +sidebar_label: Overview +# prettier-ignore +description: Working with Utility APIs in the New Frontend System +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +As described [in the architecture section](../architecture/06-utility-apis.md), utility APIs are pieces of shared functionality - interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/03-extensions.md) that can accept inputs, be declaratively configured in your app-config, or transparently be replaced entirely with custom implementations that fulfill the same contract. + +## Creating utility APIs + +> For details, [see the main article](./02-creating.md). + +Backstage apps, plugins, and the core Backstage framework can all expose utility APIs for general use. + +Some are available out of the box, such as the API for reading app configuration. Some are provided by third party plugins, such as the catalog client API that both the catalog itself and your own code can leverage to talk to the catalog backend. Some, you may create yourself and make available inside your Backstage instance for use within your private ecosystem of plugins. + +[The main article](./02-creating.md) describes the process of creating and exposing utility APIs of your own, for sharing functionality or configurability across plugins and apps. + +## Consuming utility APIs + +> For details, [see the main article](./03-consuming.md). + +Once utility APIs are created, there are a few ways that they can be accessed to be consumed. + +Some utility APIs in turn depend on other utility APIs. This powerful composability lets you leverage already-written reusable pieces. In particular, you may want to rely on Backstage's framework-provided APIs e.g. for reading app configuration and many other use cases. Sometimes you request utility APIs inside your React components, e.g. for accessing i18n strings, or emitting analytics events. + +These are described in detail in [the main article](./03-consuming.md) + +## Configuring utility APIs + +> For details, [see the main article](./04-configuring.md). + +Most utility APIs are usable directly without any configuration. But they are proper extensions, and can therefore have their implementations entirely swapped out by your app for advanced use cases. They can also be built with the ability to configured in your app-config, or to have inputs that extend their functionality. + +These cases are all described in [the main article](./04-configuring.md). + +## Migrating from the old frontend system + +If you want to learn how to migrate your own utility APIs from the old frontend system to the new one, that's described in [a dedicated migration guide](./05-migrating.md). diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md new file mode 100644 index 0000000000..8935b31b75 --- /dev/null +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -0,0 +1,145 @@ +--- +id: creating +title: Creating Utility APIs +sidebar_label: Creating APIs +# prettier-ignore +description: Creating new utility APIs in your plugins and app +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +This section describes how to make a Utility API from scratch, or to add configurability and inputs to an existing one. If you are instead interested in migrating an existing Utility API from the old frontend system, check out [the migrating section](./05-migrating.md). + +## Creating the Utility API contract + +The first step toward exposing a utility API is to define its TypeScript contract, as well as an API reference for consumers use to access the implementation. If you want your API to be accessible by other plugins this should be done in [your plugin's `-react` package](../../architecture-decisions/adr011-plugin-package-structure.md), so that it can be imported separately. If you just want to use the API within your own plugin it is fine to place the definition within the plugin itself. In this example, we have an Example plugin that wants to expose a utility API for performing some type of work. + +```tsx title="in @internal/plugin-example-react" +import { createApiRef } from '@backstage/frontend-plugin-api'; + +/** + * The work interface for the Example plugin. + * @public + */ +export interface WorkApi { + /** + * Performs some work. + */ + doWork(): Promise; +} + +/** + * API Reference for {@link WorkApi}. + * @public + */ +export const workApiRef = createApiRef({ + id: 'plugin.example.work', +}); +``` + +Both of these are properly exported publicly from the package, so that consumers can reach them. + +## Providing an extension through your plugin + +The plugin itself now wants to provide this API and its default implementation, in the form of an API extension. Doing so means that when users install the Example plugin, an instance of the Work utility API will also be automatically available in their apps - both to the Example plugin itself, and to others. We do this in the main plugin package, not the `-react` package. + +```tsx title="in @internal/plugin-example" +import { + createApiExtension, + createApiFactory, + createPlugin, + storageApiRef, + StorageApi, +} from '@backstage/frontend-plugin-api'; +import { WorkApi, workApiRef } from '@internal/plugin-example-react'; + +class WorkImpl implements WorkApi { + constructor(options: { storageApiRef: StorageApi }) { + /* TODO */ + } + async doWork() { + /* TODO */ + } +} + +const exampleWorkApi = createApiExtension({ + factory: createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => { + return new WorkImpl({ storageApi }); + }, + }), +}); + +/** + * The Example plugin. + * @public + */ +export default createPlugin({ + id: 'example', + extensions: [exampleWorkApi], +}); +``` + +For illustration we make a skeleton implementation class and the API extension and factory for it, in the same file. These are not exported to the public surface of the plugin package; only the plugin is, as the default export. Users who install the plugin will now get the utility API automatically as well. + +The code also illustrates how the API factory declares a dependency on another utility API - the core storage API in this case. An instance of that utility API is then provided to the factory function. + +The resulting extension ID of the work API will be the kind `api:` followed by the plugin ID as the namespace, in this case ending up as `api:plugin.example.work`. Check out [the naming patterns doc](../architecture/08-naming-patterns.md) for more information on how this works. You can now use this ID to refer to the API in app-config and elsewhere. + +## Adding configurability + +Here we will describe how to amend a utility API with the capability of having extension config, which is driven by [your app-config](../../conf/writing.md). You do this by giving an extension config schema to your API extension factory function. Let's make the required additions to our original work example API. + +```tsx title="in @internal/plugin-example" +/* highlight-add-next-line */ +import { createSchemaFromZod } from '@backstage/frontend-plugin-api'; + +const exampleWorkApi = createApiExtension({ + /* highlight-add-start */ + api: workApiRef, + configSchema: createSchemaFromZod(z => + z.object({ + goSlow: z.boolean().default(false), + }), + ), + /* highlight-add-end */ + /* highlight-remove-next-line */ + factory: createApiFactory({ + /* highlight-add-next-line */ + factory: ({ config }) => createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => { + /* highlight-add-start */ + if (config.goSlow) { + /* ... */ + } + /* highlight-add-end */ + }, + }), +}); +``` + +We wanted users to be able to set a `goSlow` extension config parameter for our API instances. So we passed in a `configSchema` to `createApiExtension` which matches that interface. This example builds it using [the zod library](https://zod.dev/). The actual extension config values will then be passed in a type safe manner in to the `factory` which is now a callback, wherein we can do what we wish with them. When changing to the callback form, we also had to add a top level `api: workApiRef` under `createApiExtension`. + +Note that the expression "extension config" as used here, is _not_ the same thing as the `configApi` which gives you access to the full app-config. The extension config discussed here is instead the particular configuration settings given to your utility API instance. This is discussed more [in the Configuring section](./04-configuring.md). + +Note also that the extension config schema contained a default value fo the `goSlow` field. This is an important consideration. You want users of your API to be able to get maximum value out of it, without having to dive deep into how to configure it. For that reason you generally want to provide as many sane defaults as possible, while letting users override them rarely but with purpose, only when called for. If you have an extension config schema without defaults, the framework will refuse to instantiate the utility API on startup unless the user had configured those values explicitly. Since it had a default value, the TypeScript code and interfaces also don't have to defensively allow `undefined` - we know that it'll have either the default value or an overridden value when we start consuming the extension config data. + +## Adding inputs + +Inputs are added to Utility APIs in the same way as other extension types: + +- Declaring a set of `inputs` on your extension +- If needed, create custom extension data types to be used in those inputs +- If needed, export an extension creator function for creating that particular attachment type + +This is a power use case and not very commonly used. + + + +## Next steps + +See [the Consuming section](./03-consuming.md) to see how to consume this new utility API in various ways. If you wish to configure and add inputs to it, check out [the Configuring section](./04-configuring.md). diff --git a/docs/frontend-system/utility-apis/03-consuming.md b/docs/frontend-system/utility-apis/03-consuming.md new file mode 100644 index 0000000000..5c53c8617e --- /dev/null +++ b/docs/frontend-system/utility-apis/03-consuming.md @@ -0,0 +1,69 @@ +--- +id: consuming +title: Consuming Utility APIs +sidebar_label: Consuming APIs +# prettier-ignore +description: Consuming utility APIs +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +All of the utility API extensions that were passed into your app through installed plugins, get instantiated and configured in the right order by the framework, and are then made available for consumption. You can interact with these instances in the following ways. + +## Via React hooks + +The most common consumption pattern for utility APIs is to call the `useApi` hook inside React components to get an implementation via its API ref. This applies whether it was originally provided from the core framework or from a plugin. + +```tsx +import { useApi, configApiRef } from '@backstage/frontend-plugin-api'; + +const MyComponent = () => { + const configApi = useApi(configApiRef); + const title = configApi.getString('app.title'); + // ... +}; +``` + +The `useApi` hook always returns a value, or throws an exception if the API ref could not be resolved to a registered implementation. For advanced use cases, where you explicitly want to optionally request a utility API that may or may not have been provided at runtime, you can use the underlying `useApiHolder` hook instead. + +```tsx +import { useApiHolder, configApiRef } from '@backstage/frontend-plugin-api'; + +const MyComponent = () => { + const apis = useApiHolder(); + const configApi = apis.get(configApiRef); // may return undefined + if (configApi) { + const title = configApi.getString('app.title'); + // ... + } +}; +``` + +## Via dependencies + +Your utility APIs can depend on other utility APIs in their factories. You do this by declaring `deps` on your `createApiFactory`, and reading the outcome in your `factory`. + +```tsx +import { + configApiRef, + createApiExtension, + createApiFactory, + discoveryApiRef, +} from '@backstage/frontend-plugin-api'; +import { MyApiImpl } from './MyApiImpl'; + +const myApi = createApiExtension({ + factory: createApiFactory({ + api: myApiRef, + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + }, + factory: ({ configApi, discoveryApi }) => { + return new MyApiImpl({ configApi, discoveryApi }); + }, + }), +}); +``` + +Note how the `deps` section essentially assigns free-form names that you choose, to API refs. Here we for example map `configApiRef` to the name `configApi`, but that's just a convention. The framework will ensure that all of those deps get instantiated and passed into your `factory` function with the same set of names as your `deps`. At that point, `configApi` refers to an actual functioning instance of that API ref. diff --git a/docs/frontend-system/utility-apis/04-configuring.md b/docs/frontend-system/utility-apis/04-configuring.md new file mode 100644 index 0000000000..7acf23decf --- /dev/null +++ b/docs/frontend-system/utility-apis/04-configuring.md @@ -0,0 +1,76 @@ +--- +id: configuring +title: Configuring Utility APIs +sidebar_label: Configuring +# prettier-ignore +description: Configuring, extending, and overriding utility APIs +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +Utility APIs are extensions and can therefore optionally be amended with configurability, as well as inputs that other extensions attach themselves to. This section describes how to make use of that as a consumer of such utility APIs. + +## Configuring + +To configure your Utility API extension, first you'll need to know its ID. That ID is formed from the API ref ID; check [the naming patterns docs](../architecture/08-naming-patterns.md) for details. + +Our example work API from [the creating section](./02-creating.md) would have the ID `api:plugin.example.work`. You configure it and all other extensions under the `app.extensions` section of your app-config. + +```yaml title="in e.g. app-config.yaml or app-config.production.yaml" +app: + extensions: + - api:plugin.example.work: + config: + goSlow: false + - # ... other extensions +``` + +It's important to note that the `extensions` are a list (mind the initial `-`), and that the `api:plugin.example.work` entry is an object such that the `config` key needs to be indented below it. If you do not get those two pieces right, the application may not start up correctly. + +The extension config schema will tell you what parameters it supports. Here we override the `goSlow` extension config value, which replaces the default. + +## Attaching extensions to inputs + +Like with other extension types, you add input attachments to a Utility API by declaring the `attachTo` section of that attachment to point to the Utility APIs ID and input name. + +Well written input-enabled extension often have extension creator functions that help you make such attachments. Those functions typically set the `attachTo` section correctly on your behalf so that you don't have to figure them out. + +## Replacing a Utility API implementation + +Like with other extension types, you replace Utility APIs with your own custom implementation using [extension overrides](../architecture/05-extension-overrides.md). + +```tsx title="in your app" +/* highlight-add-start */ +import { createExtensionOverrides } from '@backstage/frontend-plugin-api'; + +class CustomWorkImpl implements WorkApi { + /* ... */ +} + +const myOverrides = createExtensionOverrides({ + extensions: [ + createApiExtension({ + api: workApiRef, + factory: () => + createApiFactory({ + api: workApiRef, + factory: () => new CustomWorkImpl(), + }), + }), + ], +}); +/* highlight-add-end */ + +// Remember to pass the overrides to your createApp +export default createApp({ + features: [ + // ... other features + /* highlight-add-next-line */ + myOverrides, + ], +}); +``` + +In this example the overriding extension is kept minimal, but just like any other extension it can also have `deps`, configurability, and inputs. Check out [the Creating section](./02-creating.md) for more details about that. + +When you create a replacement extension, in general you may want to mimic its extension config schema or input shapes where applicable. This makes it an easier thing to slot in to an app, since it'll be responding to extensibility the same way as the original one did. diff --git a/docs/frontend-system/utility-apis/05-migrating.md b/docs/frontend-system/utility-apis/05-migrating.md new file mode 100644 index 0000000000..a81dc796ac --- /dev/null +++ b/docs/frontend-system/utility-apis/05-migrating.md @@ -0,0 +1,126 @@ +--- +id: migrating +title: Migrating Utility APIs from the old frontend system +sidebar_label: Migrating +# prettier-ignore +description: Migrating Utility APIs from the old frontend system +--- + +> **NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.** + +If you are migrating your plugins or app over from the old frontend system, there are a few things to keep in mind in regards to utility APIs. + +## Overview + +- Migrate your repo overall to the latest release of Backstage +- Follow the plugin migration guide +- Optionally change your package dependencies and code from `core-*-api` to `frontend-*-api` +- Keep the TypeScript interface and API ref exported as they were, except possibly reconsidering the choice of ID of the latter +- Wrap the old API factory call in an extension using `createApiExtension` +- Make sure that this extension is referenced by your migrated plugin + +## Prerequisites + +This guide assumes that you first [upgrade your repo](../../getting-started/keeping-backstage-updated.md) to the latest release of Backstage. This ensures that you do not have to fight several types of incompatibilities and updates at the same time. + +## Dependency changes + +In this article we will discuss some old interfaces that you used to import from the `@backstage/core-plugin-api` package. Those are now generally lifted over to `@backstage/frontend-plugin-api`, next to the new interfaces that are specific to the new frontend system. If you want to, you can already update your `package.json` and code imports to only use the new plugin API, but for the time being you don't have to. The old core exports will continue to work for the foreseeable future. + +To at least get access to the new interfaces, you'll need to run the following command. Note that it's just an example! It refers to `plugins/example`, which you'll have to change to the actual folder name that your package to migrate is in. + +```bash title="from your repo root" +yarn --cwd plugins/example add @backstage/frontend-plugin-api +``` + +## React package interface and ref changes + +Let's begin with [your `-react` package](../../architecture-decisions/adr011-plugin-package-structure.md). The act of exporting TypeScript interfaces and API refs have not changed from the old system. You can typically keep those as-is. For illustrative purposes, this is an example of an interface and its API ref: + +```tsx title="in @internal/plugin-example-react" +import { createApiRef } from '@backstage/frontend-plugin-api'; + +/** + * Performs some work. + * @public + */ +export interface WorkApi { + doWork(): Promise; +} + +/** + * The work interface for the Example plugin. + * @public + */ +export const workApiRef = createApiRef({ + id: 'plugin.example.work', +}); +``` + +In this example, the plugin ID already follows the common naming convention. If it doesn't, you may want to consider renaming that ID at this point. Don't worry, this won't hurt consumers in the old frontend system since the ID is mostly used for debugging purposes there. In the new system, it's much more important and appears in app-config files and similar. + +Note at the top of the file that it uses the updated import from `@backstage/frontend-plugin-api` that we migrated in the previous section, instead of the old `@backstage/core-plugin-api`. + +## Plugin package changes + +Now let's turn to the main plugin package where the plugin itself is exported. You will probably already have a `createPlugin` call in here. Before we changed the `core-plugin-api` imports it'll have looked somewhat similar to the following: + +```tsx title="in @internal/plugin-example, NOTE THIS IS LEGACY CODE" +import { + storageApiRef, + createPlugin, + createApiFactory, +} from '@backstage/core-plugin-api'; +import { workApiRef } from '@internal/plugin-example-react'; +import { WorkImpl } from './WorkImpl'; + +const exampleWorkApi = createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => new WorkImpl({ storageApi }), +}); + +/** @public */ +export const catalogPlugin = createPlugin({ + id: 'example', + apis: [exampleWorkApi], +}); +``` + +The major changes we'll make are + +- Optionally change the old imports to the new package as per the top section of this guide +- Wrap the existing API factory in a `createApiExtension` +- Change to the new version of `createPlugin` which exports this extension +- Change the plugin export to be the default instead + +The end result, after simplifying imports and cleaning up a bit, might look like this: + +```tsx title="in @internal/plugin-example" +import { + storageApiRef, + createPlugin, + createApiFactory, + createApiExtension, +} from '@backstage/frontend-plugin-api'; +import { workApiRef } from '@internal/plugin-example-react'; +import { WorkImpl } from './WorkImpl'; + +const exampleWorkApi = createApiExtension({ + factory: createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => new WorkImpl({ storageApi }), + }), +}); + +/** @public */ +export default createPlugin({ + id: 'example', + extensions: [exampleWorkApi], +}); +``` + +## Further work + +Since utility APIs are now complete extensions, you may want to take a bigger look at how they used to be used, and what the new frontend system offers. You may for example consider [adding configurability or inputs](./02-creating.md) to your API, if that makes sense for your current application. diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md index 3b042d5eb4..d6a13ca6e3 100644 --- a/docs/getting-started/app-custom-theme.md +++ b/docs/getting-started/app-custom-theme.md @@ -484,3 +484,7 @@ You can see more ways to use this in the [Storybook Sidebar examples](https://ba In addition to a custom theme, a custom logo, you can also customize the homepage of your app. Read the full guide on the [next page](homepage.md). + +## Migrating to Material UI v5 + +We now support Material UI v5 in Backstage. Check out our [migration guide](../tutorials/migrate-to-mui5.md) to get started. diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index 17f07811c0..5d797ecb4b 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -134,7 +134,7 @@ links: There are multiple authentication providers available for you to use with Backstage, feel free to follow -[the instructions for adding authentication](../auth/). +[the instructions for adding authentication](../auth/index.md). For this tutorial we choose to use GitHub, a free service most of you might be familiar with. For other options, see @@ -205,7 +205,7 @@ Restart Backstage from the terminal, by stopping it with `Control-C`, and starti To learn more about Authentication in Backstage, here are some docs you could read: -- [Authentication in Backstage](../auth/) +- [Authentication in Backstage](../auth/index.md) - [Using organizational data from GitHub](../integrations/github/org.md) ### Setting up a GitHub Integration @@ -254,7 +254,7 @@ integrations: Some helpful links, for if you want to learn more about: -- [Other available integrations](../integrations/) +- [Other available integrations](../integrations/index.md) - [Using GitHub Apps instead of a Personal Access Token](../integrations/github/github-apps.md#docsNav) ### Explore what we've done so far @@ -275,8 +275,8 @@ otherwise something went terribly wrong. ![Software template main screen, with a blue button to add an existing component](../assets/getting-started/b-existing-1.png) -- As URL use `https://github.com/backstage/demo/blob/master/catalog-info.yaml`. - This is used by our [demo site](https://demo.backstage.io). +- As URL use `https://github.com/backstage/backstage/blob/master/catalog-info.yaml`. + This is used in our [demo site](https://demo.backstage.io) catalog. ![Register a new component wizard, asking for an URL to the existing component YAML file](../assets/getting-started/b-existing-2.png) @@ -285,11 +285,16 @@ otherwise something went terribly wrong. ![Register a new component wizard, showing the metadata for the component YAML we use in this tutorial](../assets/getting-started/b-existing-3.png) - You should receive a message that your entities have been added. -- If you go back to `Home`, you should be able to find `demo`. You should be +- If you go back to `Home`, you should be able to find `backstage`. You should be able to click it and see the details ## Create a new component using a software template +> Note: if you're running Backstage with Node 20 or later, you'll need to pass the flag `--no-node-snapshot` to Node in order to +> use the templates feature. +> One way to do this is to specify the `NODE_OPTIONS` environment variable before starting Backstage: +> `export NODE_OPTIONS=--no-node-snapshot` + - Go to `create` and choose to create a website with the `Example Node.js Template` - Type in a name, let's use `tutorial` and click `Next Step` diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md index b44f7877a7..5611117636 100644 --- a/docs/getting-started/configure-app-with-plugins.md +++ b/docs/getting-started/configure-app-with-plugins.md @@ -15,7 +15,7 @@ The following steps assume that you have to it. We are using the -[CircleCI](https://github.com/backstage/backstage/blob/master/plugins/circleci/README.md) +[CircleCI](https://github.com/CircleCI-Public/backstage-plugin/tree/main/plugins/circleci) plugin in this example, which is designed to show CI/CD pipeline information attached to an entity in the software catalog. @@ -23,7 +23,7 @@ to an entity in the software catalog. ```bash # From your Backstage root directory - yarn add --cwd packages/app @backstage/plugin-circleci + yarn add --cwd packages/app @circleci/backstage-plugin ``` Note the plugin is added to the `app` package, rather than the root @@ -38,7 +38,7 @@ to an entity in the software catalog. import { EntityCircleCIContent, isCircleCIAvailable, - } from '@backstage/plugin-circleci'; + } from '@circleci/backstage-plugin'; /* highlight-add-end */ const cicdContent = ( diff --git a/docs/getting-started/contributors.md b/docs/getting-started/contributors.md index 123958d440..9ba338af96 100644 --- a/docs/getting-started/contributors.md +++ b/docs/getting-started/contributors.md @@ -11,4 +11,4 @@ Therefore we want to create a strong community of contributors -- all working to Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. ❤️ -To get you started we've put together a [Contributors Guide in the Backstage GitHub repo](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) that has all the information you need +To get you started we've put together a [Contributors Guide in the Backstage GitHub repo](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) that has all the information you need. diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index cd5a1725c9..11de12feb9 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -26,7 +26,7 @@ you to use the [Contributors](https://github.com/backstage/backstage/blob/master On MacOS, you will want to have run `xcode-select --install` to get the XCode command line build tooling in place. - An account with elevated rights to install the dependencies - `curl` or `wget` installed -- Node.js [Active LTS Release](https://nodejs.org/en/blog/release/) installed using one of these +- Node.js [Active LTS Release](https://nodejs.org/en/about/previous-releases) installed using one of these methods: - Using `nvm` (recommended) - [Installing nvm](https://github.com/nvm-sh/nvm#install--update-script) diff --git a/docs/integrations/aws-s3/discovery.md b/docs/integrations/aws-s3/discovery.md index 4a2b953bc9..ceb780141f 100644 --- a/docs/integrations/aws-s3/discovery.md +++ b/docs/integrations/aws-s3/discovery.md @@ -32,7 +32,7 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise - schedule: # optional; same options as in TaskScheduleDefinition + schedule: # same options as in TaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -52,7 +52,7 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise - schedule: # optional; same options as in TaskScheduleDefinition + schedule: # same options as in TaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -79,12 +79,6 @@ const builder = await CatalogBuilder.create(env); builder.addEntityProvider( AwsS3EntityProvider.fromConfig(env.config, { logger: env.logger, - // optional: alternatively, use scheduler with schedule defined in app-config.yaml - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - // optional: alternatively, use schedule scheduler: env.scheduler, }), ); diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index 7b91308b56..3b25a9ac75 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -71,7 +71,7 @@ The parameters available are: - **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched. - **`path:`** _(optional)_ Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml. - **`branch:`** _(optional)_ The branch name to use. -- **`schedule`** _(optional)_: +- **`schedule`**: - **`frequency`**: How often you want the task to run. The system does its best to avoid overlapping invocations. - **`timeout`**: diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md index 6e1533dea0..53315a4c48 100644 --- a/docs/integrations/bitbucketCloud/discovery.md +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -49,19 +49,6 @@ export default async function createPlugin( } ``` -Alternatively to the config-based schedule, you can use - -```ts -/* highlight-remove-next-line */ -scheduler: env.scheduler, -/* highlight-add-start */ -schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, -}), -/* highlight-add-end */ -``` - ### Installation with Events Support Please follow the installation instructions at @@ -131,7 +118,7 @@ catalog: filters: # optional projectKey: '^apis-.*$' # optional; RegExp repoSlug: '^service-.*$' # optional; RegExp - schedule: # optional; same options as in TaskScheduleDefinition + schedule: # same options as in TaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -153,7 +140,7 @@ catalog: Regular expression used to filter results based on the project key. - **`repoSlug`** _(optional)_: Regular expression used to filter results based on the repo slug. -- **`schedule`** _(optional)_: +- **`schedule`**: - **`frequency`**: How often you want the task to run. The system does its best to avoid overlapping invocations. - **`timeout`**: diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md index 05d0510553..ad59006ff1 100644 --- a/docs/integrations/bitbucketServer/discovery.md +++ b/docs/integrations/bitbucketServer/discovery.md @@ -38,12 +38,6 @@ export default async function createPlugin( builder.addEntityProvider( BitbucketServerEntityProvider.fromConfig(env.config, { logger: env.logger, - // optional: alternatively, use scheduler with schedule defined in app-config.yaml - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - // optional: alternatively, use schedule scheduler: env.scheduler, }), ); @@ -69,7 +63,7 @@ catalog: filters: # optional projectKey: '^apis-.*$' # optional; RegExp repoSlug: '^service-.*$' # optional; RegExp - schedule: # optional; same options as in TaskScheduleDefinition + schedule: # same options as in TaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -87,7 +81,7 @@ catalog: Regular expression used to filter results based on the project key. - **`repoSlug`** _(optional)_: Regular expression used to filter results based on the repo slug. -- **`schedule`** _(optional)_: +- **`schedule`**: - **`frequency`**: How often you want the task to run. The system does its best to avoid overlapping invocations. - **`timeout`**: diff --git a/docs/integrations/gerrit/discovery.md b/docs/integrations/gerrit/discovery.md index 5d822effb5..ee843d2454 100644 --- a/docs/integrations/gerrit/discovery.md +++ b/docs/integrations/gerrit/discovery.md @@ -26,18 +26,11 @@ Then add the plugin to the plugin catalog `packages/backend/src/plugins/catalog. ```ts /* packages/backend/src/plugins/catalog.ts */ import { GerritEntityProvider } from '@backstage/plugin-catalog-backend-module-gerrit'; -import { Duration } from 'luxon'; const builder = await CatalogBuilder.create(env); /** ... other processors and/or providers ... */ builder.addEntityProvider( GerritEntityProvider.fromConfig(env.config, { logger: env.logger, - // optional: alternatively, use scheduler with schedule defined in app-config.yaml - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - // optional: alternatively, use schedule scheduler: env.scheduler, }), ); @@ -57,7 +50,7 @@ catalog: host: gerrit-your-company.com branch: master # Optional query: 'state=ACTIVE&prefix=webapps' - schedule: # optional; same options as in TaskScheduleDefinition + schedule: # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index 46d59cbf1b..3c3d90d6e9 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -40,12 +40,6 @@ export default async function createPlugin( builder.addEntityProvider( GithubEntityProvider.fromConfig(env.config, { logger: env.logger, - // optional: alternatively, use scheduler with schedule defined in app-config.yaml - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - // optional: alternatively, use schedule scheduler: env.scheduler, }), ); @@ -85,12 +79,6 @@ export default async function createPlugin( /* highlight-add-start */ const githubProvider = GithubEntityProvider.fromConfig(env.config, { logger: env.logger, - // optional: alternatively, use scheduler with schedule defined in app-config.yaml - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - // optional: alternatively, use schedule scheduler: env.scheduler, }); env.eventBroker.subscribe(githubProvider); @@ -122,7 +110,7 @@ catalog: filters: branch: 'main' # string repository: '.*' # Regex - schedule: # optional; same options as in TaskScheduleDefinition + schedule: # same options as in TaskScheduleDefinition # supports cron, ISO duration, "human duration" as used in code frequency: { minutes: 30 } # supports ISO duration, "human duration" as used in code @@ -213,7 +201,7 @@ This provider supports multiple organizations via unique provider IDs. Defaults to `false`. Due to limitations in the GitHub API's ability to query for repository objects, this option cannot be used in conjunction with wildcards in the `catalogPath`. -- **`schedule`** _(optional)_: +- **`schedule`**: - **`frequency`**: How often you want the task to run. The system does its best to avoid overlapping invocations. - **`timeout`**: @@ -229,13 +217,12 @@ GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-r accounts). The snippet below refreshes the Backstage catalog data every 35 minutes, which issues an API request for each discovered location. If your requests are too frequent then you may get throttled by -rate limiting. You can change the refresh frequency of the catalog in your `packages/backend/src/plugins/catalog.ts` file: +rate limiting. You can change the refresh frequency of the catalog in your `app-config.yaml` file by controlling the `schedule`. -```typescript -schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 35 }, - timeout: { minutes: 30 }, -}), +```yaml +schedule: + frequency: { minutes: 35 } + timeout: { minutes: 3 } ``` More information about scheduling can be found on the [TaskScheduleDefinition](https://backstage.io/docs/reference/backend-tasks.taskscheduledefinition) page. diff --git a/docs/integrations/github/github-apps.md b/docs/integrations/github/github-apps.md index cc84704799..96363bc719 100644 --- a/docs/integrations/github/github-apps.md +++ b/docs/integrations/github/github-apps.md @@ -139,3 +139,13 @@ integration: - `Variables`: `Read & write` (if templates include GitHub Action Repository Variables) - `Secrets`: `Read & write` (if templates include GitHub Action Repository Secrets) - `Environments`: `Read & write` (if templates include GitHub Environments) + +### Troubleshooting + +`HttpError: This endpoint requires you to be authenticated.` + +This message tends to wrap a `NotFoundError: No app installation found` under the hood, which +is the result of not installing the app in your organization. Even if created via the `backstage-cli` +as a member and app manager of your organization, the app will not automatically install. You +must possess the `Owner` role in the organization to see the `Install` menu under your +app settings, then manually press `Install` to authorize the application. diff --git a/docs/local-dev/cli-build-system.md b/docs/local-dev/cli-build-system.md index c5013bf103..51c328c871 100644 --- a/docs/local-dev/cli-build-system.md +++ b/docs/local-dev/cli-build-system.md @@ -590,22 +590,25 @@ For your productivity working with unit tests it's quite essential to have your A complete launch configuration for VS Code debugging may look like this: -```json +```jsonc { "type": "node", - "name": "vscode-jest-tests", + "name": "vscode-jest-tests.v2", "request": "launch", + "args": [ + "repo", + "test", + "--runInBand", + "--watchAll=false", + "--testNamePattern", + "${jest.testNamePattern}", + "--runTestsByPath", + "${jest.testFile}" + ], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "disableOptimisticBPs": true, - "program": "${workspaceFolder}/node_modules/.bin/jest", - "cwd": "${workspaceFolder}", - "args": [ - "--config", - "node_modules/@backstage/cli/config/jest.js", - "--runInBand", - "--watchAll=false" - ] + "program": "${workspaceFolder}/node_modules/.bin/backstage-cli" } ``` diff --git a/docs/local-dev/debugging.md b/docs/local-dev/debugging.md index d2a4d02db0..39981f2e54 100644 --- a/docs/local-dev/debugging.md +++ b/docs/local-dev/debugging.md @@ -47,3 +47,44 @@ The resulting log should now have more information available for debugging: [1] 2023-04-12T00:51:44.118Z search info Collating documents for tools succeeded type=plugin documentType=tools [1] 2023-04-12T00:51:44.119Z backstage debug task: search_index_tools will next occur around 2023-04-11T21:01:44.118-04:00 type=taskManager task=search_index_tools ``` + +## Debugger + +### VSCode + +In your `launch.json`, add a new entry with the following, + +```jsonc +{ + "name": "Start Backend", + "type": "node", + "request": "launch", + "args": [ + "package", + "start" + ], + "cwd": "${workspaceFolder}/packages/backend", + "program": "${workspaceFolder}/node_modules/.bin/backstage-cli", + "skipFiles": [ + "/**" + ], + "console": "integratedTerminal" +}, +``` + +### WebStorm + +This section describes the process for enabling run configurations for Backstage in WebStorm. +Run configurations enable the use of debugging functionality such as steppers and breakpoints. + +1. Select `Edit Configurations` in the `Run` dropdown menu. Click the plus sign to add a new + configuration, then select `Node.js`. +2. In `Working directory`, input `{PROJECT_DIR}/packages/backend`. + Replace `{PROJECT_DIR}` with the path to your Backstage repo. +3. In `JavaScript file`, input `{PROJECT_DIR}/node_modules/@backstage/cli/bin/backstage-cli`. + Replace `{PROJECT_DIR}` with the path to your Backstage repo. +4. In `Application parameters`, input `package start`. +5. Optionally, for `Environment Variables`, input `LOG_LEVEL=debug`. +6. Click `Apply` to save the changes. +7. With the newly-created configuration selected, use the `Run` or `Debug` icons on the + toolbar to execute the newly created configuration. diff --git a/docs/openapi/01-getting-started.md b/docs/openapi/01-getting-started.md new file mode 100644 index 0000000000..eb3b1c1209 --- /dev/null +++ b/docs/openapi/01-getting-started.md @@ -0,0 +1,115 @@ +--- +id: 01-getting-started +title: Schema-first plugins with OpenAPI (Experimental) +description: Tutorial on how to start using OpenAPI schema-first development in your plugins. +--- + +# Getting started with OpenAPI in your Backstage plugins + +Target Audience: Plugin developers + +Difficulty: Medium + +## Goal + +The goal of this tutorial is to give you exposure to tools that more tightly couple your OpenAPI specification and plugin lifecycle. The tools we'll be presenting were created by the OpenAPI tooling project area and allow you to create, + +1. A typed `express` router that provides strong guardrails during development for input and output values. Support for query, path parameters and request body, as well as experimental support for headers and cookies. +2. An auto-generated client to interact with your plugin's backend. Support for all request types, parameters and body, as well as return types. Provides a low-level interface to allow more customization by higher level libraries. +3. Validation and verification tooling to ensure your API and specification stay in sync. Includes testing against your unit tests. + +## Prerequisites + +### Technical Knowledge + +This tutorial assumes that you're already familiar with the following, + +1. How to build a Backstage plugin. +2. `Express.js` and `Typescript` +3. OpenAPI 3.0 schemas + +### Setting up + +There are two required npm packages before we start, + +1. `@backstage/repo-tools`, this package contains all OpenAPI related commands for your plugins. We will be using this throughout the tutorial. +2. `@opticdev/optic`, this package is a dependency of `@backstage/repo-tools` but is only required for OpenAPI related commands. + +You should install both of the above packages in the _root_ of your workspace. + +## Storing your OpenAPI specification + +You should create a new folder, `src/schema` in your backend plugin to store your OpenAPI (and any other) specifications. For example, if you're adding a specification to the catalog plugin, you would add a `src/schema` folder to `plugins/catalog-backend`, making a `plugins/catalog-backend/src/schema` directory. This directory should have an `openapi.yaml` file inside. + +> Currently, only the `.yaml` extension is supported, not `.yml`. + +## Generating a typed express router from a spec + +Run `yarn backstage-repo-tools schema openapi generate `. This will create an `openapi.generated.ts` file in the `src/schema` directory that contains the OpenAPI schema as well as a generated express router with types. + +Use it like so, update your `router.ts` or `createRouter.ts` file with the following content, + +```diff ++ import { createOpenApiRouter } from '../schema/openapi.generated'; +- import Router from 'express-promise-router'; + +... +export async function createRouter( + options: RouterOptions, +): Promise { ++ const router = await createOpenApiRouter(); +- const router = Router(); +``` + +## Generating a typed client from a spec + +Run `yarn backstage-repo-tools schema openapi generate-client --input-spec /src/schema/openapi.yaml --output-directory `. `` should match the same backend plugin we've been using so far. `` is a new directory and npm package that you should create. The general pattern is `plugins/-client`. + +The generated client will have a directory `src/generated` that exports a `DefaultApiClient` class and all generated types. You can use the client like so, + +```diff ++ import { DefaultApiClient } from './generated'; + +export class CatalogClient implements CatalogApi { ++ private readonly apiClient: DefaultApiClient; + + constructor(options: { + discoveryApi: { getBaseUrl(pluginId: string): Promise }; + fetchApi?: { fetch: typeof fetch }; + }) { ++ this.apiClient = new DefaultApiClient(options); + } + ... +``` + +usage of the types will depend on your type names. + +You should be able to use the generated `DefaultApi.client.ts` file out of the box for your API needs. For full customization, you can use a wrapper around the generated client to adjust the flavor of your clients. + +For more information, see [the docs](./generate-client.md). + +## Validating your spec with test traffic + +Add the following lines to your `createRouter.test.ts` or `router.test.ts` file, + +```diff ++ import { wrapInOpenApiTestServer } from '@backstage/backend-openapi-utils'; ++ import { Server } from 'http'; + +... + +describe('createRouter', () => { +- let app: express.Express; ++ let app: express.Express | Server; + +... + +- app = express().use(router); ++ app = wrapInOpenApiTestServer(express().use(router)); +``` + +This adds a wrapper around the express server that allows it to reroute traffic for `supertest`. Run `yarn backstage-repo-tools schema openapi init` to create some required config files. Now, when you run `yarn backstage-repo-tools schema openapi test` your schema will now be tested against your test data. Any errors will be reported. + +Our command is a small wrapper over [`Optic`](https://github.com/opticdev/optic) which does all of the heavy lifting. + +For more information, see [the docs](./test-case-validation.md). diff --git a/docs/openapi/generate-client.md b/docs/openapi/generate-client.md new file mode 100644 index 0000000000..f2c7c64574 --- /dev/null +++ b/docs/openapi/generate-client.md @@ -0,0 +1,28 @@ +--- +id: generate-client +title: Generate a client from your OpenAPI spec +description: Documentation on how to create a client for a given OpenAPI spec +--- + +## How to generate a client with `repo-tools schema openapi generate-client`? + +### Prerequisites + +1. Set your OpenAPI file's `info.title` to your pluginID like so, + +```yaml +info: + # your pluginId + title: catalog +``` + +2. Find or create a new plugin to house your new generated client. Currently, we do not support generating an entirely new plugin and instead just generate client files. + +### Generating your client + +1. Run `yarn backstage-repo-tools schema openapi generate-client --input-spec --output-directory `. This will create a new folder in `/src/generated` to house the generated content. +2. You should use the generated files as follows, + +- `apis/DefaultApi.client.ts` - this is the client that you should use. It has types for all of the various operations on your API. +- `models/*` - These are the types generated from your OpenAPI file, ideally you should not need to use these directly and can instead use the inferred types from `apis/DefaultApi.client.ts`. +- everything else is directory specific and shouldn't be touched. diff --git a/docs/openapi/test-case-validation.md b/docs/openapi/test-case-validation.md index 50686b5ba0..0d93b6d5b5 100644 --- a/docs/openapi/test-case-validation.md +++ b/docs/openapi/test-case-validation.md @@ -1,3 +1,9 @@ +--- +id: test-case-validation +title: Validate your OpenAPI spec against test data +description: Documentation on how to use the `schema openapi test` command. +--- + ## OpenAPI Validation using Test Cases This is primarily performed by `backstage-repo-tools schema openapi test`. Any errors found in the generated specs can be either diff --git a/docs/overview/support.md b/docs/overview/support.md index c968405a99..38bb60784f 100644 --- a/docs/overview/support.md +++ b/docs/overview/support.md @@ -11,7 +11,7 @@ description: Support and Community Details and Links here if you want to contribute. - [RFCs](https://github.com/backstage/backstage/labels/rfc) - Help shape the technical direction by reviewing _Request for Comments_ issues. -- [FAQ](../FAQ.md) - Frequently Asked Questions. +- [FAQ](../faq/index.md) - Frequently Asked Questions. - [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md) - This is how we roll. - [Blog](https://backstage.io/blog/) - Announcements and updates. diff --git a/docs/overview/threat-model.md b/docs/overview/threat-model.md index cb1379aa27..a5adf8025d 100644 --- a/docs/overview/threat-model.md +++ b/docs/overview/threat-model.md @@ -34,7 +34,7 @@ The integrator is also responsible for maintaining the resolved NPM dependencies There are many common facilities that are configured centrally and available to all Backstage backend plugins. For example there is a `DatabaseManager` that provides access to a SQL database, `TaskScheduler` for scheduling long-running tasks, `Logger` as a general logging facility, and `UrlReader` for reading content from external sources. These are all configured either directly in code, or within the `backend` block of the static configuration. The appropriate care needs to be taken to ensure that any secrets remain confidential and no malicious configuration is injected. -In a typical Backstage setup, there is no boundary between plugins that run on the same host. Likewise, there is no boundary between plugins that share the same database access. Any plugin that is running on a host that has access to the logical database of any other plugin should be considered to have full access to that other plugin. For example, if you deploy the `auth` and `catalog` plugins on separate hosts with separate configuration and credentials, the `catalog` plugin is still considered to have full access to the `auth` plugin if the `catalog` plugin has access to the `auth` plugin's logical database. The only way to create a boundary between the two plugins is to deploy them in such a way that they do not have access to each others’ database. This applies to the database facility as well as any other shared resources, such as the cache. +In a typical Backstage setup, there is no boundary between plugins that run on the same host. Likewise, there is no boundary between plugins that share the same database access. Any plugin that is running on a host that has access to the logical database of any other plugin should be considered to have full access to that other plugin. For example, even if you deploy the `auth` and `catalog` plugins on separate hosts with separate configuration and credentials, the `catalog` plugin is still considered to have full access to the `auth` plugin as long as the `catalog` plugin has access to the `auth` plugin's logical database. The only way to create a boundary between the two plugins is to deploy them in such a way that they do not have access to each others’ database. This applies to the database facility as well as any other shared resources, such as the cache. The `UrlReader` facility is of particular interest for a secure Backstage configuration. In particular the `backend.reading.allow` configuration lists the hosts that you trust the backend to be able to read content from on behalf of users. It is extremely important that this list does not, for example, allow access to instance metadata endpoints of cloud providers, or other endpoints that your Backstage instance may have access to which contain sensitive information. In general it is recommended to keep the list minimal and only allow reading from required endpoints. The same concerns apply to custom implementations of the `UrlReader` interface, if you need to implement these through code. diff --git a/docs/overview/versioning-policy.md b/docs/overview/versioning-policy.md index c38ba404b5..2ff3588af1 100644 --- a/docs/overview/versioning-policy.md +++ b/docs/overview/versioning-policy.md @@ -158,7 +158,7 @@ package export. The Backstage project uses [Node.js](https://nodejs.org/) for both its development tooling and backend runtime. In order for expectations to be clear we use the -following schedule for determining the [Node.js releases](https://nodejs.org/en/about/releases/) that we support: +following schedule for determining the [Node.js releases](https://nodejs.org/en/about/previous-releases) that we support: - At any given point in time we support exactly two adjacent even-numbered releases of Node.js, for example v12 and v14. @@ -182,3 +182,11 @@ The TypeScript release cadence is roughly every three months. An important aspec Our policy is to support the last 3 TypeScript versions, for example 4.8, 4.9, and 5.0. Converted to time, this means that we typically support the TypeScript version from the last six to nine months, depending on where in the TypeScript release window we are. This policy applies as a snapshot at the time of any given Backstage release, new TypeScript releases only apply to the following Backstage main-line release, not to the current one. For anyone maintaining their own Backstage project, this means that you should strive to bump to the latest TypeScript version at least every 6 months, or you may encounter breakages as you upgrade Backstage packages. If you encounter any issues in doing so, please [file an issue in the main Backstage repository](https://github.com/backstage/backstage/issues/new/choose), as per this policy we should always support the latest version. In order to ensure that we do not start using new TypeScript features too early, the Backstage project itself uses the version at the beginning of the currently supported window, in the above example that would be version 4.8. + +## PostgreSQL Releases + +The Backstage project recommends and supports using PostgreSQL for persistent storage. + +The PostgreSQL [versioning policy](https://www.postgresql.org/support/versioning/) is to release a new major version every year with new features which is then supported for 5 years after its initial release. + +Our policy mirrors the PostgreSQL versioning policy - we will support the last 5 major versions. We will also test the newest and oldest versions in that range. For example, if the range we support is currently 12 to 16, then we would only test 12 and 16 explicitly. diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index cab138ed9e..c0f4e3e156 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -8,9 +8,17 @@ For some use cases, you may want to define custom [rules](./concepts.md#resource ## Define a custom rule -Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule in `packages/backend/src/plugins/permission.ts`, but you can put it anywhere that's accessible by your `backend` package. +Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule and create a condition in `packages/backend/src/plugins/permission.ts`. + +We use Zod in our example below. To install, run: + +```bash +yarn workspace backend add zod +``` ```typescript title="packages/backend/src/plugins/permission.ts" +... + import type { Entity } from '@backstage/catalog-model'; import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; import { createConditionFactory } from '@backstage/plugin-permission-node'; @@ -41,42 +49,55 @@ export const isInSystemRule = createCatalogPermissionRule({ }); const isInSystem = createConditionFactory(isInSystemRule); + +... ``` For a more detailed explanation on defining rules, refer to the [documentation for plugin authors](./plugin-authors/03-adding-a-resource-permission-check.md#adding-support-for-conditional-decisions). -## Provide the rule during plugin setup - -Now that we have a custom rule defined, we need provide it to the catalog plugin. This step is important because the catalog plugin will use the rule's `toQuery` and `apply` methods while evaluating conditional authorize results. There's no guarantee that the catalog and permission backends are running on the same server, so we must explicitly link the rule to ensure that it's available at runtime. - -The api for providing custom rules may differ between plugins, but there should typically be some integration point during the creation of the backend router. For the catalog, this integration point is exposed via `CatalogBuilder.addPermissionRules`. - -```typescript title="packages/backend/src/plugins/catalog.ts" -import { isInSystemRule } from './permission'; -// The CatalogBuilder with the addPermissionRules function is in the alpha path -import { CatalogBuilder } from '@backstage/plugin-catalog-backend/alpha'; - -... - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - builder.addPermissionRules(isInSystemRule); - ... - return router; -} -``` - -The new rule is now ready for use in a permission policy! - -## Use the rule in a policy - -Let's bring this all together by extending the example policy from the previous section. +Still in the `packages/backend/src/plugins/permission.ts` file, let's use the condition we just created in our `TestPermissionPolicy`. ```ts title="packages/backend/src/plugins/permission.ts" +... +/* highlight-remove-next-line */ +import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; /* highlight-add-next-line */ -import { isInSystem } from './catalog'; +import { catalogConditions, createCatalogConditionalDecision, createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; +/* highlight-remove-next-line */ +import { createConditionFactory } from '@backstage/plugin-permission-node'; +/* highlight-add-next-line */ +import { PermissionPolicy, PolicyQuery, createConditionFactory } from '@backstage/plugin-permission-node'; +/* highlight-add-start */ +import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; +import { AuthorizeResult, PolicyDecision, isResourcePermission } from '@backstage/plugin-permission-common'; +/* highlight-add-end */ +... + +export const isInSystemRule = createCatalogPermissionRule({ + name: 'IS_IN_SYSTEM', + description: 'Checks if an entity is part of the system provided', + resourceType: 'catalog-entity', + paramsSchema: z.object({ + systemRef: z + .string() + .describe('SystemRef to check the resource is part of'), + }), + apply: (resource: Entity, { systemRef }) => { + if (!resource.relations) { + return false; + } + + return resource.relations + .filter(relation => relation.type === 'partOf') + .some(relation => relation.targetRef === systemRef); + }, + toQuery: ({ systemRef }) => ({ + key: 'relations.partOf', + values: [systemRef], + }), +}); + +const isInSystem = createConditionFactory(isInSystemRule); class TestPermissionPolicy implements PermissionPolicy { async handle( @@ -95,20 +116,47 @@ class TestPermissionPolicy implements PermissionPolicy { { anyOf: [ catalogConditions.isEntityOwner({ - claims: user?.identity.ownershipEntityRefs ?? [] + claims: user?.identity.ownershipEntityRefs ?? [], }), - isInSystem('interviewing') - ] - } + isInSystem({ systemRef: 'interviewing' }), + ], + }, /* highlight-add-end */ ); } return { result: AuthorizeResult.ALLOW }; } +} + +... +``` + +## Provide the rule during plugin setup + +Now that we have a custom rule defined and added to our policy, we need provide it to the catalog plugin. This step is important because the catalog plugin will use the rule's `toQuery` and `apply` methods while evaluating conditional authorize results. There's no guarantee that the catalog and permission backends are running on the same server, so we must explicitly link the rule to ensure that it's available at runtime. + +The api for providing custom rules may differ between plugins, but there should typically be some integration point during the creation of the backend router. For the catalog, this integration point is exposed via `CatalogBuilder.addPermissionRules`. + +```typescript title="packages/backend/src/plugins/catalog.ts" +import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; +/* highlight-add-next-line */ +import { isInSystemRule } from './permission'; + +... + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const builder = await CatalogBuilder.create(env); + /* highlight-add-next-line */ + builder.addPermissionRules(isInSystemRule); + ... + return router; +} ``` The updated policy will allow catalog entity resource permissions if any of the following are true: - User owns the target entity -- Target entity is part of the `'interviewing'` system +- Target entity is part of the 'interviewing' system diff --git a/docs/plugins/add-to-directory.md b/docs/plugins/add-to-directory.md index dfa43c2ed7..8be0d764dd 100644 --- a/docs/plugins/add-to-directory.md +++ b/docs/plugins/add-to-directory.md @@ -21,7 +21,18 @@ description: A brief description of the plugin. # Max 170 characters documentation: # A link to your documentation E.g. Your github README iconUrl: # Used as the src attribute for your logo. # You can provide an external url or add your logo under static/img and provide a path -# relative to static/ e.g. img/my-logo.png +# relative to static/ e.g. /img/my-logo.png npmPackageName: # Your npm package name E.g. '@backstage/plugin-' quotes are required addedDate: # The date plugin added to directory E.g. '2022-10-01' quotes are required ``` + +## Submission Tips + +Here are a few tips to help speed up the review process when you submit your plugin: + +- For any icon that you use make sure you have the proper rights to use it. +- Make sure that your package had been published on the NPM registry and that it's public. +- Make sure your package on NPM has a link back to your code repo, this helps provide confidence that it's the right package. +- Where possible, please use an [NPM scope](https://docs.npmjs.com/about-scopes) that matches either your Organization name or user name, this provides trust in the plugin +- If your plugin has both a frontend and backend link the documentation to the frontend package but make sure it mentioned needing to install the backend package. +- Where possible include a screenshot of the features in you plugin documentation, it really does help when deciding to use a plugin. diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 73b8f9027a..d2e93bb08c 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -41,9 +41,11 @@ standalone mode. You can do a first-light test of your service: ```sh cd plugins/carmen-backend -yarn start +LEGACY_BACKEND_START=true yarn start ``` +> Note: `LEGACY_BACKEND_START=true` is needed while we transition fully to the [New Backend System](../backend-system/index.md). The templates have not been migrated yet; you can track this in [issue 21288](https://github.com/backstage/backstage/issues/21288) + This will think for a bit, and then say `Listening on :7007`. In a different terminal window, now run diff --git a/docs/plugins/internationalization.md b/docs/plugins/internationalization.md index 78b7de6342..fd12452cff 100644 --- a/docs/plugins/internationalization.md +++ b/docs/plugins/internationalization.md @@ -6,11 +6,11 @@ description: Documentation on adding internationalization to the plugin ## Overview -The Backstage core function provides internationalization for plugins +The Backstage core function provides internationalization for plugins. The underlying library is [`i18next`](https://www.i18next.com/) with some additional Backstage typescript magic for type safety with keys. ## For a plugin developer -When you are creating your plugin, you have the possibility to use `createTranslationRef` to define all messages for your plugin. For example +When you are creating your plugin, you have the possibility to use `createTranslationRef` to define all messages for your plugin. For example: ```ts import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; @@ -19,8 +19,13 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; export const myPluginTranslationRef = createTranslationRef({ id: 'plugin.my-plugin', messages: { - index_page_title: 'All your components', - create_component_button_label: 'Create new component', + indexPage: { + title: 'All your components', + createButtonTitle: 'Create new component', + }, + entityPage: { + notFound: 'Entity not found', + }, }, }); ``` @@ -33,14 +38,115 @@ import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const { t } = useTranslationRef(myPluginTranslationRef); return ( - + ); ``` +You will see how the initial dictionary structure and nesting gets converted into dot notation, so we encourage `camelCase` in key names and lean on the nesting structure to separate keys. + +### Guidelines for `i18n` messages and keys + +The API for `i18n` messages and keys can be pretty tricky to get right, as it's a pretty flexible API. We've put together some guidelines to help you get started that encourage good practices when thinking about translating plugins: + +#### Key names + +When defining messages it is recommended to use a nested structure that represents the semantic hierarchy in your translations. This allows for better organization and understanding of the structure. For example: + +```ts +export const myPluginTranslationRef = createTranslationRef({ + id: 'plugin.my-plugin', + messages: { + dashboardPage: { + title: 'All your components', + subtitle: 'Create new component', + widgets: { + weather: { + title: 'Weather', + description: 'Shows the weather', + }, + calendar: { + title: 'Calendar', + description: 'Shows the calendar', + }, + }, + }, + entityPage: { + notFound: 'Entity not found', + }, + }, +}); +``` + +Think about the semantic placement of content rather than the text content itself. Group related translations under a common prefix, and use nesting to represent relationships between different parts of your application. It's good to start grouping under extensions, page sections, or visual scopes and experiences. + +Translations should avoid using their own text content as key where possible, as this can lead to confusion if the translation changes. Instead prefer to use keys that describe the location or usage of the text. + +#### Common Key names + +This list is intended to grow over time, but below are some examples of common key names and patterns that we encourage you to use where possible: + +- `${page}.title` +- `${page}.subtitle` +- `${page}.description` + +- `${page}.header.title` + +#### Key reuse + +Reusing the same key in multiple places is discouraged. This helps prevent ambiguity, and instead keeps the usage of each key as clear as possible. Consider creating duplicate keys that are grouped under a semantic section instead. + +#### Flat keys + +Avoid a flat key structure at the root level, as it can lead to naming conflicts and make the translation file harder to manage and change evolve over time. Instead, group translations under a common prefix. + +```ts +export const myPluginTranslationRef = createTranslationRef({ + id: 'plugin.my-plugin', + messages: { + // this is BAD + title: 'My page', + subtitle: 'My subtitle', + // this is GOOD + dashboardPage: { + header: { + title: 'All your components', + subtitle: 'Create new component', + }, + }, + }, +}); +``` + +#### Plurals + +The `i18next` library, which is used as the underlying implementation, has built-in support for pluralization. You can use this feature as is described in [the documentation](https://www.i18next.com/translation-function/plurals). + +We encourage you to use this feature and avoid creating different key prefixes for pluralized content. For example: + +```ts +export const myPluginTranslationRef = createTranslationRef({ + id: 'plugin.my-plugin', + messages: { + dashboardPage: { + title: 'All your components', + subtitle: 'Create new component', + cards: { + title_one: 'You have one card', + title_two: 'You have two cards', + title_other: 'You have many cards ({{count}})', + }, + }, + entityPage: { + notFound: 'Entity not found', + }, + }, +}); +``` + ## For an application developer overwrite plugin messages In an app you can both override the default messages, as well as register translations for additional languages: @@ -53,7 +159,7 @@ In an app you can both override the default messages, as well as register transl + createTranslationMessages({ + ref: myPluginTranslationRef, + messages: { -+ create_component_button_label: 'Create new entity', ++ 'indexPage.createButtonTitle': 'Create new entity', + }, + }), + createTranslationResource({ diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md index be27e9427d..0fc44f283f 100644 --- a/docs/plugins/new-backend-system.md +++ b/docs/plugins/new-backend-system.md @@ -6,25 +6,24 @@ description: Details of the new backend system ## Status -The new backend system is in alpha, and some plugins do not yet fully implement it. But do feel free to try it out! We would love to hear back about your impressions. +The new backend system is released and ready for production use, and many plugins and modules have already been migrated. We recommend all plugins and deployments to migrate to the new system. You can find an example backend setup in [the backend-next package](https://github.com/backstage/backstage/tree/master/packages/backend-next). ## Overview -The new Backstage backend system is being built to help make it simpler to install backend plugins and to keep projects up to date. It also changes the foundation to one that makes it a lot easier to evolve plugins and the system itself with minimal disruption or cause for breaking changes. You can read more about the reasoning in the [original RFC](https://github.com/backstage/backstage/issues/11611). +The new Backstage backend system was built to help make it simpler to install backend plugins and to keep projects up to date. It also changed the foundation to one that makes it a lot easier to evolve plugins and the system itself with minimal disruption or cause for breaking changes. You can read more about the reasoning in the [original RFC](https://github.com/backstage/backstage/issues/11611). One of the goals of the new system was to reduce the code needed for setting up a Backstage backend and installing plugins. This is an example of how you create, add features, and start up your backend in the new system: ```ts import { createBackend } from '@backstage/backend-defaults'; -import { catalogPlugin } from '@backstage/plugin-catalog-backend'; // Create your backend instance const backend = createBackend(); // Install all desired features -backend.add(catalogPlugin()); +backend.add(import('@backstage/plugin-catalog-backend')); // Start up the backend await backend.start(); @@ -147,8 +146,8 @@ import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node' import { MyCustomProcessor } from './processor'; export const exampleCustomProcessorCatalogModule = createBackendModule({ - moduleId: 'exampleCustomProcessor', pluginId: 'catalog', + moduleId: 'example-custom-processor', register(env) { env.registerInit({ deps: { diff --git a/docs/plugins/plugin-development.md b/docs/plugins/plugin-development.md index 8d8db8c387..5ba1de4a7d 100644 --- a/docs/plugins/plugin-development.md +++ b/docs/plugins/plugin-development.md @@ -40,7 +40,7 @@ import { createRouteRef } from '@backstage/core-plugin-api'; // Note: This route ref is for internal use only, don't export it from the plugin export const rootRouteRef = createRouteRef({ - title: 'Example Page', + id: 'Example Page', }); ``` diff --git a/docs/publishing.md b/docs/publishing.md index f8964278b5..15813275de 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -53,6 +53,7 @@ Additional steps for the main line release - Add the release note file as [`/docs/releases/vx.y.0.md`](./releases) - Add an entry to [`/microsite/sidebar.json`](https://github.com/backstage/backstage/blob/master/microsite/sidebars.json) for the release note - Update the navigation bar item in [`/microsite/docusaurus.config.js`](https://github.com/backstage/backstage/blob/master/microsite/docusaurus.config.js) to point to the new release note + - Finally copy the content, without the metadata header, into the description of the [`Version Packages` Pull Request](https://github.com/backstage/backstage/pulls?q=is%3Aopen+is%3Apr+in%3Atitle+%22Version+Packages) Once the release has been published edit the newly created release in the [GitHub repository](https://github.com/backstage/backstage/releases) and replace the text content with the release notes. @@ -134,3 +135,9 @@ process is used to release an emergency fix as version `6.5.1` in the patch rele - [ ] The fix, which you can likely cherry-pick from your patch branch: `git cherry-pick origin/patch/v1.18.0^` - [ ] An updated `CHANGELOG.md` of all patched packages from the tip of the patch branch, `git checkout origin/patch/v1.18.0 -- {packages,plugins}/*/CHANGELOG.md`. Note that if the patch happens after any next-line releases you'll need to restore those entries in the changelog, placing the patch release entry beneath any next-line release entries. - [ ] A changeset with the message "Applied the fix from version `6.5.1` of this package, which is part of the `v1.18.1` release of Backstage." + +## Troubleshooting + +### When the release workflow is not triggered for some reason, such as a GitHub incident + +Ask one of the maintainers to force push master back to a previous commit and then push the release merge commit again. diff --git a/docs/releases/v1.21.0-changelog.md b/docs/releases/v1.21.0-changelog.md new file mode 100644 index 0000000000..2a93c55305 --- /dev/null +++ b/docs/releases/v1.21.0-changelog.md @@ -0,0 +1,3731 @@ +# Release v1.21.0 + +## @backstage/backend-common@0.20.0 + +### Minor Changes + +- 870db76: Implemented `readTree` for Gitea provider to support TechDocs functionality + +### Patch Changes + +- 7f04128: Allow a default cache TTL to be set through the app config +- 1ad8906: Use `Readable.from` to fix some of the stream issues +- d86a007: Fixed the AwsS3UrlReader host regex and host to allow the S3 reading for CN AWS domain +- bc67498: Updated dependency `archiver` to `^6.0.0`. + Updated dependency `@types/archiver` to `^6.0.0`. +- 706fc3a: Updated dependency `@kubernetes/client-node` to `0.20.0`. +- 2666675: Updated dependency `@google-cloud/storage` to `^7.0.0`. +- d15d483: Add command `--runAsDefaultUser` for `@techdocs/cli generate` to bypass running the docker builds as host user for macOS and Linux. +- d1e00aa: Expose an `onAuth` handler for `git` actions to provide custom credentials +- Updated dependencies + - @backstage/config-loader@1.6.0 + - @backstage/backend-app-api@0.5.9 + - @backstage/integration@1.8.0 + - @backstage/backend-dev-utils@0.1.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## @backstage/catalog-client@1.5.0 + +### Minor Changes + +- 3834067: The internals of `CatalogClient` are now auto-generated using the `backstage-repo-tools schema openapi generate-client` command. + +### Patch Changes + +- 82fa88b: Fixes a bug where some query parameters were double URL encoded. +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/cli@0.25.0 + +### Minor Changes + +- 3834067: Updates the ESLint config to ignore issues created by generated files in `**/src/generated/**`. + +### Patch Changes + +- 32018ff: Enable the `tsx` loader to work on Node 18.19 and up +- 0ffee55: Toned down the warning message when git is not found +- c6f3743: Added a warning when starting a standalone backend plugin that hasn't been updated to the new backend system. +- 3e358b0: Added deprecation warning for React Router v6 beta, please make sure you have migrated your apps to use React Router v6 stable as support for the beta version will be removed. See the [migration tutorial](https://backstage.io/docs/tutorials/react-router-stable-migration) for more information. +- 219d7f0: Updating template generation for scaffolder module +- 8cda3c7: Tweaked Node.js version check for when to use the new module register API with the new backend `package start` command. +- a3edc18: Updated dependency `vite-plugin-node-polyfills` to `^0.17.0`. +- 627554e: Updated dependency `@rollup/plugin-node-resolve` to `^15.0.0`. +- c07cee5: Updated dependency `@rollup/plugin-json` to `^6.0.0`. +- bd586a5: Updated dependency `bfj` to `^8.0.0`. +- 8056425: Updated dependency `@typescript-eslint/eslint-plugin` to `6.12.0`. +- 017c425: Updated dependency `@typescript-eslint/eslint-plugin` to `6.11.0`. +- 2565cc8: Updated dependency `@rollup/plugin-commonjs` to `^25.0.0`. +- 33e96e5: Switched the `@typescript-eslint/eslint-plugin` dependency back to using a `^` version range. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/eslint-plugin@0.1.4 + - @backstage/config-loader@1.6.0 + - @backstage/integration@1.8.0 + - @backstage/cli-node@0.2.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/config-loader@1.6.0 + +### Minor Changes + +- 24f5a85: Add "path" to `TransformFunc` context + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/core-compat-api@0.1.0 + +### Minor Changes + +- cf5cc4c: Discover plugins and routes recursively beneath the root routes in `collectLecacyRoutes` +- af7bc3e: Switched all core extensions to instead use the namespace `'app'`. +- f63dd72: The `collectLegacyRoutes` has been removed and is replaced by `convertLegacyApp` now being able to convert a `FlatRoutes` element directly. + +### Patch Changes + +- 03d0b6d: Added `convertLegacyRouteRef` utility to convert existing route refs to be used with the new experimental packages. +- a379243: Leverage the new `FrontendFeature` type to simplify interfaces +- 8226442: Added `compatWrapper`, which can be used to wrap any React element to provide bi-directional interoperability between the `@backstage/core-*-api` and `@backstage/frontend-*-api` APIs. +- 8f5d6c1: Updates to match the new extension input wrapping. +- c219b16: Made package public so it can be published +- b7adf24: Delete alpha DI compatibility helper for components, migrating components should be simple without a helper. +- 046e443: Updates for compatibility with the new extension IDs. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-app-api@1.11.2 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-app-api@0.4.0 + +### Minor Changes + +- e539735: Updated core extension structure to make space for the sign-in page by adding `core.router`. +- 44735df: Removed `featureLoader` from `createApp`, `features` instead accepts both `FrontendFeature` and `CreateAppFeatureLoader` +- af7bc3e: Switched all core extensions to instead use the namespace `'app'`. +- ea06590: The app no longer provides the `AppContext` from `@backstage/core-plugin-api`. Components that require this context to be available should use the `compatWrapper` helper from `@backstage/core-compat-api`. + +### Patch Changes + +- 5eb6b8a: Added the nav logo extension for customization of sidebar logo +- aeb8008: Add support for translation extensions. +- 1f12fb7: Create a core components extension that allows adopters to override core app components such as `Progress`, `BootErrorPage`, `NotFoundErrorPage` and `ErrorBoundaryFallback`. +- a379243: Leverage the new `FrontendFeature` type to simplify interfaces +- 60d6eb5: Removed `@backstage/plugin-graphiql` dependency. +- b7adf24: Use the new plugin type for error boundary components. +- 5970928: Collect and register feature flags from plugins and extension overrides. +- 9ad4039: Bringing over apis from core-plugin-api +- 8f5d6c1: Updates to match the new extension input wrapping. +- c35036b: A `configLoader` passed to `createApp` now returns an object, to make room for future expansion +- f27ee7d: Migrate analytics route tracker component. +- b8cb780: Added `createSpecializedApp`, which is a synchronous version of `createApp` where config and features already need to be loaded. +- c36e0b9: Renamed `AppRouteBinder` to `CreateAppRouteBinder` +- cb4197a: Forward ` node`` instead of `extensionId\` to resolved extension inputs. +- 8837a96: Updates to match the introduction of `ExtensionDefinition` and new extension ID naming patterns. +- a5a0473: Updates to provide `node` to extension factories instead of `id` and `source`. +- 5cdf2b3: Updated usage of `Extension` and `ExtensionDefinition` as they are now opaque. +- f9ef632: Updates to match the new `coreExtensionData` structure. +- f1183b7: Renamed the `component` option of `createComponentExtension` to `loader`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/core-app-api@1.11.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-plugin-api@0.4.0 + +### Minor Changes + +- af7bc3e: Switched all core extensions to instead use the namespace `'app'`. + +- 5cdf2b3: Changed `Extension` and `ExtensionDefinition` to use opaque types. + +- 8f5d6c1: Extension inputs are now wrapped into an additional object when passed to the extension factory, with the previous values being available at the `output` property. The `ExtensionInputValues` type has also been replaced by `ResolvedExtensionInputs`. + +- 8837a96: **BREAKING**: This version changes how extensions are created and how their IDs are determined. The `createExtension` function now accepts `kind`, `namespace` and `name` instead of `id`. All of the new options are optional, and are used to construct the final extension ID. By convention extension creators should set the `kind` to match their own name, for example `createNavItemExtension` sets the kind `nav-item`. + + The `createExtension` function as well as all extension creators now also return an `ExtensionDefinition` rather than an `Extension`, which in turn needs to be passed to `createPlugin` or `createExtensionOverrides` to be used. + +- f9ef632: Moved several extension data references from `coreExtensionData` to their respective extension creators. + +- a5a0473: The extension `factory` function now longer receives `id` or `source`, but instead now provides the extension's `AppNode` as `node`. The `ExtensionBoundary` component has also been updated to receive a `node` prop rather than `id` and `source`. + +### Patch Changes + +- a379243: Add the `FrontendFeature` type, which is the union of `BackstagePlugin` and `ExtensionOverrides` +- b7adf24: Update alpha component ref type to be more specific than any, delete boot page component and use new plugin type for error boundary component extensions. +- 5eb6b8a: Added the nav logo extension for customization of sidebar logo +- 1f12fb7: Create factories for overriding default core components extensions. +- 5970928: Add feature flags to plugins and extension overrides. +- e539735: Added `createSignInPageExtension`. +- 73246ec: Added translation APIs as well as `createTranslationExtension`. +- cb4197a: Forward ` node`` instead of `extensionId\` to resolved extension inputs. +- f27ee7d: Migrate analytics api and context files. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- f1183b7: Renamed the `component` option of `createComponentExtension` to `loader`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-test-utils@0.1.0 + +### Minor Changes + +- 59fabd5: New testing utility library for `@backstage/frontend-app-api` and `@backstage/frontend-plugin-api`. +- af7bc3e: Switched all core extensions to instead use the namespace `'app'`. + +### Patch Changes + +- 59fabd5: Added `createExtensionTester` for rendering extensions in tests. +- 7e4b0db: The `createExtensionTester` helper is now able to render more than one route in the test app. +- 818eea4: Updates for compatibility with the new extension IDs. +- b9aa6e4: Migrate `renderInTestApp` to `@backstage/frontend-test-utils` for testing individual React components in an app. +- e539735: Updates for `core.router` addition. +- c21c9cf: Re-export mock API implementations as well as `TestApiProvider`, `TestApiRegistry`, `withLogCollector`, and `setupRequestMockHandlers` from `@backstage/test-utils`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/frontend-app-api@0.4.0 + - @backstage/test-utils@1.4.6 + - @backstage/types@1.1.1 + +## @backstage/integration@1.8.0 + +### Minor Changes + +- 870db76: Implemented `readTree` for Gitea provider to support TechDocs functionality + +### Patch Changes + +- 99fb541: Updated dependency `@azure/identity` to `^4.0.0`. +- Updated dependencies + - @backstage/config@1.1.1 + +## @backstage/repo-tools@0.5.0 + +### Minor Changes + +- aea8f8d: **BREAKING**: API Reports generated for sub-path exports now place the name as a suffix rather than prefix, for example `api-report-alpha.md` instead of `alpha-api-report.md`. When upgrading to this version you'll need to re-create any such API reports and delete the old ones. +- 3834067: Adds a new command `schema openapi generate-client` that creates a Typescript client with Backstage flavor, including the discovery API and fetch API. This command doesn't currently generate a complete client and needs to be wrapped or exported manually by a separate Backstage plugin. See `@backstage/catalog-client/src/generated` for example output. + +### Patch Changes + +- f909e9d: Includes templates in @backstage/repo-tools package and use them in the CLI + +- da3c4db: Updates the `schema openapi generate-client` command to export all generated types from the generated directory. + +- 7959f23: The `api-reports` command now checks for api report files that no longer apply. + If it finds such files, it's treated basically the same as report errors do, and + the check fails. + + For example, if you had an `api-report-alpha.md` but then removed the alpha + export, the reports generator would now report that this file needs to be + deleted. + +- f49e237: Fixed a bug where `schema openapi init` created an invalid test command. + +- f91be2c: Updated dependency `@stoplight/types` to `^14.0.0`. + +- 45bfb20: Execute `openapi-generator-cli` from `@backstage/repo-tools` directory to force it to use our openapitools.json config file. + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/cli-node@0.2.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## @techdocs/cli@1.8.0 + +### Minor Changes + +- d15d483: Add command `--runAsDefaultUser` for `@techdocs/cli generate` to bypass running the docker builds as host user for macOS and Linux. +- b2dccad: Support passing additional `mkdocs-server` CLI parameters (`--dirtyreload`, `--strict` and `--clean`) when run in containerized mode. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-techdocs-node@1.11.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## @backstage/theme@0.5.0 + +### Minor Changes + +- 4d9e3b3: Added a global `OverrideComponentNameToClassKeys` for other plugins and packages to populate using module augmentation. This will in turn will provide component style override types for `createUnifiedTheme`. + +### Patch Changes + +- cd0dd4c: Align Material UI v5 `Paper` component background color in dark mode to v4. + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.1.0 + +### Minor Changes + +- 2a5891e: New module for `@backstage/plugin-auth-backend` that adds an atlassian auth provider + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.0 + +### Minor Changes + +- 271aa12: Release of `oauth2-proxy-provider` plugin + +### Patch Changes + +- a6be465: Exported the provider as default so it gets discovered when using `featureDiscoveryServiceFactory()` +- 510dab4: Change provider id from `oauth2ProxyProvider` to `oauth2Proxy` +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/errors@1.2.3 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.0 + +### Minor Changes + +- ed02c69: Add VMware Cloud auth backend module provider + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-azure-devops-backend@0.5.0 + +### Minor Changes + +- 844969c: **BREAKING** New `fromConfig` static method must be used now when creating an instance of the `AzureDevOpsApi` + + Added support for using the `AzureDevOpsCredentialsProvider` + +### Patch Changes + +- c70e4f5: Added multi-org support +- 646db72: Updated encoding of Org to use `encodeURIComponent` when building URL used to get credentials from credential provider +- 043b724: Introduced new `AzureDevOpsAnnotatorProcessor` that adds the needed annotations automatically. Also, moved constants to common package so they can be shared more easily +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-azure-devops-common@0.3.2 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog@1.16.0 + +### Minor Changes + +- e223f22: Properly support both function- and string-form visibility filter expressions in the new extensions exported via `/alpha`. +- b8e1eb2: The `columns` prop can be an array or a function that returns an array in order to override the default columns of the `CatalogIndexPage`. + +### Patch Changes + +- bc7e6d3: Fix copy entity url function in http contexts. + +- 5360097: Ensure that passed-in icons are taken advantage of in the presentation API + +- 4785d05: Add permission check to catalog create and refresh button + +- cd910c4: - Fixes bug where after unregistering an entity you are redirected to `/`. + + - Adds an optional external route `unregisterRedirect` to override this behaviour to another route. + +- 03d0b6d: The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. + +- 2d708d8: Internal naming updates for `/alpha` exports. + +- a5a0473: Internal refactor of alpha exports due to a change in how extension factories are defined. + +- 4d9e3b3: Register component overrides in the global `OverrideComponentNameToClassKeys` provided by `@backstage/theme`. This will in turn will provide component style override types for `createUnifiedTheme`. + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. + +- 78a10bb: Adding in spec.type chip to search results for clarity + +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. + +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. + +- 8587f06: Added pagination support to `CatalogIndexPage` + + `CatalogIndexPage` now offers an optional pagination feature, designed to accommodate adopters managing extensive catalogs. This new capability allows for better handling of large amounts of data. + + To activate the pagination mode, simply update your `App.tsx` as follows: + + ```diff + const routes = ( + + ... + - } /> + + } /> + ... + ``` + + In case you have a custom catalog page and you want to enable pagination, you need to pass the `pagination` prop to `EntityListProvider` instead. + +- fb8f3bd: Updated alpha translation message keys to use nested format and camel case. + +- 531e1a2: Updated alpha plugin to include the `unregisterRedirect` external route. + +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-search-react@1.7.4 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-catalog-backend@1.16.0 + +### Minor Changes + +- 7804597: Permission rules can now be added for the Catalog plugin through the `CatalogPermissionExtensionPoint` interface. + +### Patch Changes + +- 3834067: Update the OpenAPI spec to support the use of `openapi-generator`. +- 50ee804: Wrap single `pipelineLoop` of TaskPipeline in a span for better traces +- 7123c58: Updated dependency `@types/glob` to `^8.0.0`. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- a168507: Deprecated `EntitiesSearchFilter` and `EntityFilter`, which can now be imported from `@backstage/plugin-catalog-node` instead +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-openapi-utils@0.1.1 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/plugin-search-backend-module-catalog@0.1.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-events-node@0.2.17 + +## @backstage/plugin-catalog-node@1.6.0 + +### Minor Changes + +- a168507: Added `EntitiesSearchFilter` and `EntityFilter` from `@backstage/plugin-catalog-backend`, for reuse +- 7804597: Permission rules can now be added for the Catalog plugin through the `CatalogPermissionExtensionPoint` interface. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-home@0.6.0 + +### Minor Changes + +- 5a317f5: Added view of entities grouped by kind to make it easier to distinguish entities with different kind but same name + +### Patch Changes + +- 2633d64: Change user settings backend plugin id and fix when using user setting backend home page first will cause edit page loop render +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 2b72591: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- 6cd12f2: Updated dependency `@rjsf/utils` to `5.14.1`. + Updated dependency `@rjsf/core` to `5.14.1`. + Updated dependency `@rjsf/material-ui` to `5.14.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- 63c494e: Updated dependency `@rjsf/utils` to `5.14.2`. + Updated dependency `@rjsf/core` to `5.14.2`. + Updated dependency `@rjsf/material-ui` to `5.14.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- 54cef27: StarredEntities component calls `getEntitiesByRefs` instead of `getEntities` to improve performance since we have the `entityRefs` +- c8908d4: Use new option from RJSF 5.15 +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/core-app-api@1.11.2 + - @backstage/plugin-home-react@0.1.6 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-backend@0.14.0 + +### Minor Changes + +- 52050ad: You can now select `single` kubernetes cluster that the entity is part-of from all your defined kubernetes clusters, by passing `backstage.io/kubernetes-cluster` annotation with the defined cluster name. + + If you do not specify the annotation by `default it fetches all` defined kubernetes cluster. + + To apply + + catalog-info.yaml + + ```diff + annotations: + 'backstage.io/kubernetes-id': dice-roller + 'backstage.io/kubernetes-namespace': dice-space + + 'backstage.io/kubernetes-cluster': dice-cluster + 'backstage.io/kubernetes-label-selector': 'app=my-app,component=front-end' + ``` + +### Patch Changes + +- 6010564: The `kubernetes-node` plugin has been modified to house a new extension points for Kubernetes backend plugin; + `KubernetesClusterSupplierExtensionPoint` is introduced . + `kubernetesAuthStrategyExtensionPoint` is introduced . + `kubernetesFetcherExtensionPoint` is introduced . + `kubernetesServiceLocatorExtensionPoint` is introduced . + + The `kubernetes-backend` plugin was modified to use this new extension point. + +- 706fc3a: Updated dependency `@kubernetes/client-node` to `0.20.0`. + +- ae94d3c: Updated dependency `@aws-crypto/sha256-js` to `^5.0.0`. + +- 99fb541: Updated dependency `@azure/identity` to `^4.0.0`. + +- 42c1aee: Updated dependency `@google-cloud/container` to `^5.0.0`. + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-kubernetes-node@0.1.2 + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-react@0.2.0 + +### Minor Changes + +- 899d71a: Change `formatClusterLink` to be an API and make it async for further customization possibilities. + + **BREAKING** + If you have a custom k8s page and used `formatClusterLink` directly, you need to migrate to new `kubernetesClusterLinkFormatterApiRef` + +### Patch Changes + +- b5ae2e5: Add ID property to the table displaying kubernetes pods to avoid closing the info sidebar when the data reloads and needs to rerender. +- 706fc3a: Updated dependency `@kubernetes/client-node` to `0.20.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-lighthouse-backend@0.4.0 + +### Minor Changes + +- 7f0dbfd: Fixed crashes faced with custom schedule configuration. The configuration schema has been update to leverage the TaskScheduleDefinition interface. It is highly recommended to move the `lighthouse.shedule` and `lighthouse.timeout` respectively to `lighthouse.schedule.frequency` and `lighthouse.schedule.timeout`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-pagerduty@0.7.0 + +### Minor Changes + +- 5fca16f: This package has been deprecated, consider using [@pagerduty/backstage-plugin](https://github.com/pagerduty/backstage-plugin) instead. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-home-react@0.1.6 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder@1.17.0 + +### Minor Changes + +- df88d09: Add a new git repository url picker for `gitea`. This `GiteaRepoPicker` can be used in a template to scaffold a project to be cloned using gitea. +- 33edf50: Added support for dealing with user provided secrets using a new field extension `ui:field: Secret` + +### Patch Changes + +- 6806d10: Added `headerOptions` to `TemplateListPage` to optionally override default values. + Changed `themeId` of TemplateListPage from `website` to `home`. +- aaa6fb3: Minor updates for TypeScript 5.2.2+ compatibility +- 2b72591: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- 6cd12f2: Updated dependency `@rjsf/utils` to `5.14.1`. + Updated dependency `@rjsf/core` to `5.14.1`. + Updated dependency `@rjsf/material-ui` to `5.14.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- 63c494e: Updated dependency `@rjsf/utils` to `5.14.2`. + Updated dependency `@rjsf/core` to `5.14.2`. + Updated dependency `@rjsf/material-ui` to `5.14.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. +- b5fa691: Fixing `headerOptions` not being passed through the `TemplatePage` component +- c8908d4: Use new option from RJSF 5.15 +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-scaffolder-react@1.7.0 + - @backstage/catalog-client@1.5.0 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- d86cd98: Add dry run support for the `publish:gerrit` action. +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-github@0.1.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- cb6a65e: The `scaffolder.defaultCommitMessage` config value is now being used if provided and uses "initial commit" when it is not provided. +- 28949ea: Add a new action for creating github-autolink references for a repository: `github:autolinks:create` +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-react@1.7.0 + +### Minor Changes + +- 33edf50: Added support for dealing with user provided secrets using a new field extension `ui:field: Secret` + +### Patch Changes + +- 670c7cc: Fix bug where `properties` is set to empty object when it should be empty for schema dependencies +- fa66d1b: Fixed bug in `ReviewState` where `enum` value was displayed in step review instead of the corresponding label when using `enumNames` +- e516bf4: Step titles in the Stepper are now clickable and redirect the user to the corresponding step, as an alternative to using the back buttons. +- aaa6fb3: Minor updates for TypeScript 5.2.2+ compatibility +- 2aee53b: Add horizontal slider if stepper overflows +- 2b72591: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- 6cd12f2: Updated dependency `@rjsf/utils` to `5.14.1`. + Updated dependency `@rjsf/core` to `5.14.1`. + Updated dependency `@rjsf/material-ui` to `5.14.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- 63c494e: Updated dependency `@rjsf/utils` to `5.14.2`. + Updated dependency `@rjsf/core` to `5.14.2`. + Updated dependency `@rjsf/material-ui` to `5.14.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. +- c8908d4: Use new option from RJSF 5.15 +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- 5bb5240: Fixed issue for showing undefined for hidden form items +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-techdocs-node@1.11.0 + +### Minor Changes + +- d15d483: Add command `--runAsDefaultUser` for `@techdocs/cli generate` to bypass running the docker builds as host user for macOS and Linux. + +### Patch Changes + +- 99fb541: Updated dependency `@azure/identity` to `^4.0.0`. +- 2666675: Updated dependency `@google-cloud/storage` to `^7.0.0`. +- 4f773c1: Bumped the default TechDocs docker image version to the latest which was released several month ago +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/app-defaults@1.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/core-app-api@1.11.2 + - @backstage/plugin-permission-react@0.4.18 + +## @backstage/backend-app-api@0.5.9 + +### Patch Changes + +- 1da5f43: Ensure redaction of secrets that have accidental extra whitespace around them +- 9f8f266: Add redacting for secrets in stack traces of logs +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config-loader@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/cli-node@0.2.1 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-app-api@0.5.9 + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/backend-openapi-utils@0.1.1 + +### Patch Changes + +- aaa6fb3: Minor updates for TypeScript 5.2.2+ compatibility +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/backend-plugin-api@0.6.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## @backstage/backend-tasks@0.5.13 + +### Patch Changes + +- d8f488a: Allow tasks to run more often that the default work check interval, which is 5 seconds. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.2.9 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. + +- b7de76a: Added support for PostgreSQL versions 15 and 16 + + Also introduced a new `setDefaults(options: { ids?: TestDatabaseId[] })` static method that can be added to the `setupTests.ts` file to define the default database ids you want to use throughout your package. Usage would look like this: `TestDatabases.setDefaults({ ids: ['POSTGRES_12','POSTGRES_16'] })` and would result in PostgreSQL versions 12 and 16 being used for your tests. + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-app-api@0.5.9 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/cli-node@0.2.1 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/core-app-api@1.11.2 + +### Patch Changes + +- 3e358b0: Added deprecation warning for React Router v6 beta, please make sure you have migrated your apps to use React Router v6 stable as support for the beta version will be removed. See the [migration tutorial](https://backstage.io/docs/tutorials/react-router-stable-migration) for more information. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/core-components@0.13.9 + +### Patch Changes + +- e8f2ace: Added a new `/testUtils` sub-path that initially exports a `mockBreakpoint` helper. +- 381ed86: Add missing export for IconLinkVertical +- 5c8a3e3: Minor improvements to `Table` component. +- 752df93: Fixes a problem where the `LogViewer` was not able to handle very large logs +- 4d9e3b3: Register component overrides in the global `OverrideComponentNameToClassKeys` provided by `@backstage/theme`. This will in turn will provide component style override types for `createUnifiedTheme`. +- 07dfdf3: Updated dependency `linkifyjs` to `4.1.3`. +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- f291757: Update `linkify-react` to version `4.1.3` +- 175d86b: Fixed an issue where the `onChange` prop within `HeaderTabs` was triggering twice upon tab-switching. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/version-bridge@1.0.7 + +## @backstage/core-plugin-api@1.8.1 + +### Patch Changes + +- 03d0b6d: Removed the alpha `convertLegacyRouteRef` utility, which as been moved to `@backstage/core-compat-api` +- 0c93dc3: The `createTranslationRef` function from the `/alpha` subpath can now also accept a nested object structure of default translation messages, which will be flatted using `.` separators. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/create-app@0.5.8 + +### Patch Changes + +- 8ece804: Bumped create-app version. +- 0351e09: Bumped create-app version. +- 3f1192f: Bumped create-app version. +- a96c2d4: Include the `` for group entities by default +- 375b6f7: CircelCI plugin moved permanently +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## @backstage/dev-utils@1.0.25 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/core-app-api@1.11.2 + - @backstage/app-defaults@1.4.6 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + +## @backstage/eslint-plugin@0.1.4 + +### Patch Changes + +- 107dc46: The `no-undeclared-imports` rule will now prefer using version queries that already exist en the repo for the same dependency type when installing new packages. + +## @backstage/integration-react@1.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + +## @backstage/test-utils@1.4.6 + +### Patch Changes + +- e8f2ace: Deprecated `mockBreakpoint`, as it is now available from `@backstage/core-components/testUtils` instead. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/core-app-api@1.11.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-adr@0.6.11 + +### Patch Changes + +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- fb8f3bd: Updated alpha translation message keys to use nested format and camel case. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-react@1.7.4 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-adr-common@0.2.18 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-adr-backend@0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-adr-common@0.2.18 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-adr-common@0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-airbrake@0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/test-utils@1.4.6 + - @backstage/dev-utils@1.0.25 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-airbrake-backend@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + +## @backstage/plugin-allure@0.1.44 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-analytics-module-ga@0.1.36 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-analytics-module-ga4@0.1.7 + +### Patch Changes + +- af6f227: Disabled `send_page_view` to get rid of events duplication +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-analytics-module-newrelic-browser@0.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/config@1.1.1 + +## @backstage/plugin-apache-airflow@0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + +## @backstage/plugin-api-docs@0.10.2 + +### Patch Changes + +- 816d331: Add dependency on `graphql-config` to compensate for `graphql-language-service` needing it but not shipping the dep properly +- 615159e: Updated dependency `graphiql` to `3.0.10`. +- e16e7ce: Updated dependency `@asyncapi/react-component` to `1.2.2`. +- 82fb18b: Updated dependency `@asyncapi/react-component` to `1.2.6`. +- 53e2c06: Updated dependency `@asyncapi/react-component` to `1.1.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog@1.16.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-apollo-explorer@0.1.18 + +### Patch Changes + +- e296b94: Updated dependency `@apollo/explorer` to `^3.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## @backstage/plugin-app-backend@0.3.56 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config-loader@1.6.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.8 + +## @backstage/plugin-app-node@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/plugin-auth-backend@0.20.1 + +### Patch Changes + +- 7ac2575: `oauth2-proxy` auth implementation has been moved to `@backstage/plugin-auth-backend-module-oauth2-proxy-provider` +- 2a5891e: Migrate the atlassian auth provider to be implemented using the new `@backstage/plugin-auth-backend-module-atlassian-provider` module +- 783797a: fix static token issuer not being able to initialize +- e1c189b: The Okta provider implementation is moved to the new module +- a62764b: Updated dependency `passport` to `^0.7.0`. +- bcbbf8e: Updated dependency `@google-cloud/firestore` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.0 + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.5 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.5 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.2 + - @backstage/plugin-auth-backend-module-google-provider@0.1.5 + - @backstage/plugin-auth-backend-module-github-provider@0.1.5 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.5 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.5 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.3 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.5 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.1 + +### Patch Changes + +- e1c189b: Adds okta-provider backend module for the auth plugin +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + +## @backstage/plugin-auth-node@0.4.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-azure-devops@0.3.10 + +### Patch Changes + +- c70e4f5: Added multi-org support +- 7c9af0b: Added support for annotations that use a subpath for the host. Also validated that the annotations have the correct number of slashes. +- 043b724: Introduced new `AzureDevOpsAnnotatorProcessor` that adds the needed annotations automatically. Also, moved constants to common package so they can be shared more easily +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-azure-devops-common@0.3.2 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-azure-devops-common@0.3.2 + +### Patch Changes + +- c70e4f5: Added multi-org support +- 043b724: Introduced new `AzureDevOpsAnnotatorProcessor` that adds the needed annotations automatically. Also, moved constants to common package so they can be shared more easily + +## @backstage/plugin-azure-sites@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-azure-sites-backend@0.1.18 + +### Patch Changes + +- 99fb541: Updated dependency `@azure/identity` to `^4.0.0`. +- b7a13ed: Updated dependency `@azure/arm-appservice` to `^14.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-badges@0.2.52 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-badges-backend@0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bazaar@0.2.20 + +### Patch Changes + +- 5d79682: Internalize 'AboutField' to break catalog dependency +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bazaar-backend@0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bitbucket-cloud-common@0.2.15 + +### Patch Changes + +- acf9390: Updated dependency `ts-morph` to `^20.0.0`. +- Updated dependencies + - @backstage/integration@1.8.0 + +## @backstage/plugin-bitrise@0.1.55 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-catalog-backend-module-aws@0.3.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-backend-module-azure@0.1.27 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1 + +### Patch Changes + +- eb44e92: Support authenticated backends +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-openapi-utils@0.1.1 + - @backstage/backend-tasks@0.5.13 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.23 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-events-node@0.2.17 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.21 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.8 + +### Patch Changes + +- 42c1aee: Updated dependency `@google-cloud/container` to `^5.0.0`. +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.24 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-catalog-backend-module-github@0.4.6 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-events-node@0.2.17 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-catalog-backend-module-github@0.4.6 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.5 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.12 + +### Patch Changes + +- 43b2eb8: Ensure that cursors always come back as JSON on sqlite too +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-events-node@0.2.17 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.23 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.15 + +### Patch Changes + +- 99fb541: Updated dependency `@azure/identity` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.25 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.13 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.3.5 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-catalog-common@1.0.19 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-catalog-graph@0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.10.4 + +### Patch Changes + +- 03d0b6d: The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/catalog-client@1.5.0 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-react@1.9.2 + +### Patch Changes + +- 8587f06: Added pagination support to `EntityListProvider`. + +- 5360097: Ensure that passed-in icons are taken advantage of in the presentation API + +- fd9863c: Grouped all `/alpha` extension data reference exports under `catalogExtensionData`. + +- 08d9e67: Add default icon for kind resource. + +- aaa6fb3: Minor updates for TypeScript 5.2.2+ compatibility + +- a5a0473: Internal refactor of alpha exports due to a change in how extension factories are defined. + +- 4d9e3b3: Register component overrides in the global `OverrideComponentNameToClassKeys` provided by `@backstage/theme`. This will in turn will provide component style override types for `createUnifiedTheme`. + +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. + +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. + +- e223f22: Breaking alpha-API change to entity visibility filter functions to accept a bare entity as their first argument, instead of an object with an entity property. + + Functions that accept such filters now also support the string expression form of filters. + +- eee0ff2: Fixed a issue where `CatalogPage` wasn't using the chosen `initiallySelectedFilter` as intended. + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-unprocessed-entities@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-cicd-statistics@0.1.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-cicd-statistics@0.1.30 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-circleci@0.3.28 + +### Patch Changes + +- 375b6f7: CircelCI plugin moved permanently +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-cloudbuild@0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-code-climate@0.1.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-code-coverage@0.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-code-coverage-backend@0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/integration@1.8.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-codescene@0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-config-schema@0.1.48 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-cost-insights@0.12.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-cost-insights-common@0.1.2 + +## @backstage/plugin-devtools@0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.7 + +## @backstage/plugin-devtools-backend@0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config-loader@1.6.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.7 + +## @backstage/plugin-devtools-common@0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + - @backstage/types@1.1.1 + +## @backstage/plugin-dynatrace@8.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-entity-feedback@0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-feedback-backend@0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-validation@0.1.13 + +### Patch Changes + +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-events-backend@0.2.17 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17 + +## @backstage/plugin-events-backend-module-aws-sqs@0.2.11 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.17 + +## @backstage/plugin-events-backend-module-azure@0.1.18 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + - @backstage/plugin-events-node@0.2.17 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.1.18 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + - @backstage/plugin-events-node@0.2.17 + +## @backstage/plugin-events-backend-module-gerrit@0.1.18 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + - @backstage/plugin-events-node@0.2.17 + +## @backstage/plugin-events-backend-module-github@0.1.18 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17 + +## @backstage/plugin-events-backend-module-gitlab@0.1.18 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17 + +## @backstage/plugin-events-backend-test-utils@0.1.18 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.17 + +## @backstage/plugin-events-node@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/plugin-explore@0.4.14 + +### Patch Changes + +- aac659e: Added option to set `Direction` for the graph in the `GroupsDiagram` +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-react@1.7.4 + - @backstage/plugin-explore-react@0.0.34 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-explore-backend@0.0.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-search-backend-module-explore@0.1.12 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-explore-react@0.0.34 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-explore-common@0.0.2 + +## @backstage/plugin-firehydrant@0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-fossa@0.2.60 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-gcalendar@0.3.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/errors@1.2.3 + +## @backstage/plugin-gcp-projects@0.3.44 + +### Patch Changes + +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- d2f5662: Fix query parameter for project details page which should point to projectId rather than project name +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## @backstage/plugin-git-release-manager@0.3.40 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/integration@1.8.0 + +## @backstage/plugin-github-actions@0.6.9 + +### Patch Changes + +- 08d7e46: Github Workflow Runs UI is modified to show in optional Card view instead of table, with branch selection option +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-github-deployments@0.1.59 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-github-issues@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-github-pull-requests-board@0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-gitops-profiles@0.3.43 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-gocd@0.1.34 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-graphiql@0.3.1 + +### Patch Changes + +- 03d0b6d: The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## @backstage/plugin-graphql-voyager@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## @backstage/plugin-home-react@0.1.6 + +### Patch Changes + +- 2b72591: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- 6cd12f2: Updated dependency `@rjsf/utils` to `5.14.1`. + Updated dependency `@rjsf/core` to `5.14.1`. + Updated dependency `@rjsf/material-ui` to `5.14.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- 63c494e: Updated dependency `@rjsf/utils` to `5.14.2`. + Updated dependency `@rjsf/core` to `5.14.2`. + Updated dependency `@rjsf/material-ui` to `5.14.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + +## @backstage/plugin-ilert@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-jenkins@0.9.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.22 + +## @backstage/plugin-jenkins-backend@0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.22 + +## @backstage/plugin-jenkins-common@0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-kafka@0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-kafka-backend@0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-kubernetes@0.11.3 + +### Patch Changes + +- 899d71a: Change `formatClusterLink` to be an API and make it async for further customization possibilities. + + **BREAKING** + If you have a custom k8s page and used `formatClusterLink` directly, you need to migrate to new `kubernetesClusterLinkFormatterApiRef` + +- 706fc3a: Updated dependency `@kubernetes/client-node` to `0.20.0`. + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-kubernetes-react@0.2.0 + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-cluster@0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-kubernetes-react@0.2.0 + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-kubernetes-common@0.7.2 + +### Patch Changes + +- 706fc3a: Updated dependency `@kubernetes/client-node` to `0.20.0`. +- 5d79682: Remove unused dependency +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-node@0.1.2 + +### Patch Changes + +- 6010564: The `kubernetes-node` plugin has been modified to house a new extension points for Kubernetes backend plugin; + `KubernetesClusterSupplierExtensionPoint` is introduced . + `kubernetesAuthStrategyExtensionPoint` is introduced . + `kubernetesFetcherExtensionPoint` is introduced . + `kubernetesServiceLocatorExtensionPoint` is introduced . + + The `kubernetes-backend` plugin was modified to use this new extension point. + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-lighthouse@0.4.13 + +### Patch Changes + +- ffbf656: Updated README +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-linguist@0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-linguist-backend@0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-microsoft-calendar@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/errors@1.2.3 + +## @backstage/plugin-newrelic@0.3.43 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## @backstage/plugin-newrelic-dashboard@0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-nomad@0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-nomad-backend@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-octopus-deploy@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-opencost@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## @backstage/plugin-org@0.6.18 + +### Patch Changes + +- 59c24b9: Fix issue where members inside of `` would be rendered as squished when the card itself was shrunk down. +- 3a65d9c: Support member list scrollable when parent has specified height +- 4785d05: Add permission check to catalog create and refresh button +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-org-react@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-periskop@0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-periskop-backend@0.2.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + +## @backstage/plugin-permission-backend@0.5.31 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/plugin-permission-common@0.7.11 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-permission-node@0.7.19 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-permission-react@0.4.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/config@1.1.1 + +## @backstage/plugin-playlist@0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-react@1.7.4 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-playlist-common@0.1.13 + +## @backstage/plugin-playlist-backend@0.3.12 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-playlist-common@0.1.13 + +## @backstage/plugin-playlist-common@0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-proxy-backend@0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + +## @backstage/plugin-puppetdb@0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-rollbar@0.4.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-rollbar-backend@0.1.53 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-scaffolder-backend@1.19.2 + +### Patch Changes + +- 219d7f0: Refactor some methods to `-node` instead and use the new external modules +- aff34fc: Fix issue with Circular JSON dependencies in templating +- 48667b4: Fix creating env secret in github:environment:create action +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- 28949ea: Add a new action for creating github-autolink references for a repository: `github:autolinks:create` +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-backend-module-github@0.1.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.11 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.32 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.2.11 + +### Patch Changes + +- 219d7f0: Extract some more actions to this library +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.25 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.16 + +### Patch Changes + +- 7f8a801: Added examples for `sentry:project:create` scaffolder action and unit tests. +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.29 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-common@1.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node@0.2.9 + +### Patch Changes + +- 219d7f0: Refactor some methods to `-node` instead and use the new external modules +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-search@1.4.4 + +### Patch Changes + +- 03d0b6d: The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-react@1.7.4 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend@1.4.8 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-openapi-utils@0.1.1 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-catalog@0.1.12 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-elasticsearch@1.3.11 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-explore@0.1.12 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-pg@0.5.17 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.1 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-techdocs@0.1.12 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-techdocs-node@1.11.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-node@1.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-common@1.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + - @backstage/types@1.1.1 + +## @backstage/plugin-search-react@1.7.4 + +### Patch Changes + +- a5a0473: Internal refactor of alpha exports due to a change in how extension factories are defined. +- 84dabc5: Removed `@backstage/frontend-app-api` dependency. +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 6f280fa: Capture analytics even when number of results is not available, since the total result count is not something that is always available for all search engines and configurations. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-sentry@0.5.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-shortcuts@0.3.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-sonarqube@0.7.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-sonarqube-react@0.1.11 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-sonarqube-backend@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-sonarqube-react@0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-splunk-on-call@0.4.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-stack-overflow@0.1.23 + +### Patch Changes + +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-react@1.7.4 + - @backstage/plugin-home-react@0.1.6 + - @backstage/config@1.1.1 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-stack-overflow-backend@0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.1 + - @backstage/config@1.1.1 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-stackstorm@0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/errors@1.2.3 + +## @backstage/plugin-tech-insights@0.3.20 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend@0.5.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-tech-insights-node@0.4.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-tech-insights-node@0.4.14 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-node@0.4.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-radar@0.6.11 + +### Patch Changes + +- 03d0b6d: The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## @backstage/plugin-techdocs@1.9.2 + +### Patch Changes + +- 03d0b6d: The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-react@1.7.4 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-techdocs-react@1.1.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.25 + +### Patch Changes + +- 3f354e6: Move `@testing-library/react` to be a `peerDependency` +- 5d79682: Remove unnecessary catalog dependency +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog@1.16.0 + - @backstage/core-app-api@1.11.2 + - @backstage/test-utils@1.4.6 + - @backstage/plugin-techdocs@1.9.2 + - @backstage/plugin-search-react@1.7.4 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-techdocs-react@1.1.14 + +## @backstage/plugin-techdocs-backend@1.9.1 + +### Patch Changes + +- a402644: Regenerates a fresh token for each call to the search index when collating techdocs. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/integration@1.8.0 + - @backstage/plugin-techdocs-node@1.11.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-backend-module-techdocs@0.1.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.3 + +### Patch Changes + +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-techdocs-react@1.1.14 + +## @backstage/plugin-techdocs-react@1.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/plugin-todo@0.2.32 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-todo-backend@0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-openapi-utils@0.1.1 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-user-settings@0.7.14 + +### Patch Changes + +- 03d0b6d: The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- fb8f3bd: Updated alpha translation message keys to use nested format and camel case. +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/core-app-api@1.11.2 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-user-settings-backend@0.2.7 + +### Patch Changes + +- 2633d64: Change user settings backend plugin id and fix when using user setting backend home page first will cause edit page loop render +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-vault@0.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-vault-backend@0.4.1 + +### Patch Changes + +- b7de76a: Updated to test using PostgreSQL 12 and 16 +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-vault-node@0.1.1 + +## @backstage/plugin-vault-node@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/plugin-xcmetrics@0.2.46 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/errors@1.2.3 + +## example-app@0.2.90 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-app-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/plugin-api-docs@0.10.2 + - @backstage/core-components@0.13.9 + - @backstage/cli@0.25.0 + - @backstage/theme@0.5.0 + - @backstage/plugin-scaffolder@1.17.0 + - @backstage/plugin-home@0.6.0 + - @backstage/plugin-catalog@1.16.0 + - @backstage/plugin-scaffolder-react@1.7.0 + - @backstage/plugin-kubernetes@0.11.3 + - @backstage/plugin-org@0.6.18 + - @backstage/plugin-github-actions@0.6.9 + - @backstage/plugin-azure-devops@0.3.10 + - @backstage/core-app-api@1.11.2 + - @backstage/plugin-lighthouse@0.4.13 + - @backstage/plugin-explore@0.4.14 + - @backstage/plugin-catalog-import@0.10.4 + - @backstage/plugin-user-settings@0.7.14 + - @backstage/plugin-tech-radar@0.6.11 + - @backstage/plugin-graphiql@0.3.1 + - @backstage/plugin-techdocs@1.9.2 + - @backstage/plugin-search@1.4.4 + - @backstage/plugin-search-react@1.7.4 + - @backstage/plugin-stack-overflow@0.1.23 + - @backstage/plugin-adr@0.6.11 + - @backstage/plugin-gcp-projects@0.3.44 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3 + - @backstage/plugin-pagerduty@0.7.0 + - @backstage/app-defaults@1.4.6 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-airbrake@0.3.28 + - @backstage/plugin-apache-airflow@0.2.18 + - @backstage/plugin-azure-sites@0.1.17 + - @backstage/plugin-badges@0.2.52 + - @backstage/plugin-catalog-graph@0.3.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6 + - @backstage/plugin-cloudbuild@0.3.28 + - @backstage/plugin-code-coverage@0.2.21 + - @backstage/plugin-cost-insights@0.12.17 + - @backstage/plugin-devtools@0.1.7 + - @backstage/plugin-dynatrace@8.0.2 + - @backstage/plugin-entity-feedback@0.2.11 + - @backstage/plugin-gcalendar@0.3.21 + - @backstage/plugin-gocd@0.1.34 + - @backstage/plugin-jenkins@0.9.3 + - @backstage/plugin-kafka@0.3.28 + - @backstage/plugin-kubernetes-cluster@0.0.4 + - @backstage/plugin-linguist@0.1.13 + - @backstage/plugin-microsoft-calendar@0.1.10 + - @backstage/plugin-newrelic@0.3.43 + - @backstage/plugin-newrelic-dashboard@0.3.3 + - @backstage/plugin-nomad@0.1.9 + - @backstage/plugin-octopus-deploy@0.2.10 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/plugin-playlist@0.2.2 + - @backstage/plugin-puppetdb@0.1.11 + - @backstage/plugin-rollbar@0.4.28 + - @backstage/plugin-sentry@0.5.13 + - @backstage/plugin-shortcuts@0.3.17 + - @backstage/plugin-stackstorm@0.1.9 + - @backstage/plugin-tech-insights@0.3.20 + - @backstage/plugin-techdocs-react@1.1.14 + - @backstage/plugin-todo@0.2.32 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## example-app-next@0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/frontend-app-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/plugin-api-docs@0.10.2 + - @backstage/core-components@0.13.9 + - @backstage/cli@0.25.0 + - @backstage/theme@0.5.0 + - @backstage/plugin-scaffolder@1.17.0 + - @backstage/plugin-home@0.6.0 + - @backstage/plugin-catalog@1.16.0 + - @backstage/plugin-scaffolder-react@1.7.0 + - @backstage/plugin-kubernetes@0.11.3 + - @backstage/plugin-org@0.6.18 + - @backstage/plugin-github-actions@0.6.9 + - @backstage/plugin-azure-devops@0.3.10 + - @backstage/core-app-api@1.11.2 + - @backstage/plugin-lighthouse@0.4.13 + - @backstage/plugin-explore@0.4.14 + - @backstage/plugin-catalog-import@0.10.4 + - @backstage/plugin-user-settings@0.7.14 + - @backstage/plugin-tech-radar@0.6.11 + - @backstage/plugin-graphiql@0.3.1 + - @backstage/plugin-techdocs@1.9.2 + - @backstage/plugin-search@1.4.4 + - @backstage/plugin-search-react@1.7.4 + - @backstage/plugin-adr@0.6.11 + - @backstage/plugin-gcp-projects@0.3.44 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3 + - @backstage/plugin-pagerduty@0.7.0 + - @backstage/app-defaults@1.4.6 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-airbrake@0.3.28 + - @backstage/plugin-apache-airflow@0.2.18 + - @backstage/plugin-azure-sites@0.1.17 + - @backstage/plugin-badges@0.2.52 + - @backstage/plugin-catalog-graph@0.3.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6 + - @backstage/plugin-cloudbuild@0.3.28 + - @backstage/plugin-code-coverage@0.2.21 + - @backstage/plugin-cost-insights@0.12.17 + - @backstage/plugin-devtools@0.1.7 + - @backstage/plugin-dynatrace@8.0.2 + - @backstage/plugin-entity-feedback@0.2.11 + - @backstage/plugin-gcalendar@0.3.21 + - @backstage/plugin-gocd@0.1.34 + - @backstage/plugin-jenkins@0.9.3 + - @backstage/plugin-kafka@0.3.28 + - @backstage/plugin-linguist@0.1.13 + - @backstage/plugin-microsoft-calendar@0.1.10 + - @backstage/plugin-newrelic@0.3.43 + - @backstage/plugin-newrelic-dashboard@0.3.3 + - @backstage/plugin-octopus-deploy@0.2.10 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/plugin-playlist@0.2.2 + - @backstage/plugin-puppetdb@0.1.11 + - @backstage/plugin-rollbar@0.4.28 + - @backstage/plugin-sentry@0.5.13 + - @backstage/plugin-shortcuts@0.3.17 + - @backstage/plugin-stackstorm@0.1.9 + - @backstage/plugin-tech-insights@0.3.20 + - @backstage/plugin-techdocs-react@1.1.14 + - @backstage/plugin-todo@0.2.32 + - @backstage/plugin-visualizer@0.0.1 + - app-next-example-plugin@0.0.4 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## app-next-example-plugin@0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-components@0.13.9 + +## example-backend@0.2.90 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.1 + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-techdocs-backend@1.9.1 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-azure-devops-backend@0.5.0 + - @backstage/plugin-scaffolder-backend@1.19.2 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-lighthouse-backend@0.4.0 + - @backstage/plugin-kubernetes-backend@0.14.0 + - @backstage/integration@1.8.0 + - @backstage/plugin-azure-sites-backend@0.1.18 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-backend@0.5.31 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-playlist-backend@0.3.12 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/plugin-search-backend@1.4.8 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5 + - @backstage/plugin-search-backend-module-techdocs@0.1.12 + - @backstage/plugin-search-backend-module-catalog@0.1.12 + - @backstage/plugin-search-backend-module-explore@0.1.12 + - @backstage/plugin-search-backend-module-pg@0.5.17 + - @backstage/plugin-events-backend@0.2.17 + - example-app@0.2.90 + - @backstage/plugin-adr-backend@0.4.5 + - @backstage/plugin-app-backend@0.3.56 + - @backstage/plugin-badges-backend@0.3.5 + - @backstage/plugin-code-coverage-backend@0.2.22 + - @backstage/plugin-devtools-backend@0.2.5 + - @backstage/plugin-entity-feedback-backend@0.2.5 + - @backstage/plugin-explore-backend@0.0.18 + - @backstage/plugin-jenkins-backend@0.3.2 + - @backstage/plugin-kafka-backend@0.3.6 + - @backstage/plugin-linguist-backend@0.5.5 + - @backstage/plugin-nomad-backend@0.1.10 + - @backstage/plugin-proxy-backend@0.4.6 + - @backstage/plugin-rollbar-backend@0.1.53 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/plugin-tech-insights-backend@0.5.22 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40 + - @backstage/plugin-tech-insights-node@0.4.14 + - @backstage/plugin-todo-backend@0.3.6 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17 + - @backstage/plugin-search-common@1.2.9 + +## example-backend-next@0.0.18 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1 + - @backstage/plugin-techdocs-backend@1.9.1 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/plugin-azure-devops-backend@0.5.0 + - @backstage/plugin-scaffolder-backend@1.19.2 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-lighthouse-backend@0.4.0 + - @backstage/plugin-kubernetes-backend@0.14.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-backend@0.5.31 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-playlist-backend@0.3.12 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/plugin-search-backend@1.4.8 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5 + - @backstage/plugin-search-backend-module-techdocs@0.1.12 + - @backstage/plugin-search-backend-module-catalog@0.1.12 + - @backstage/plugin-search-backend-module-explore@0.1.12 + - @backstage/backend-defaults@0.2.8 + - @backstage/plugin-adr-backend@0.4.5 + - @backstage/plugin-app-backend@0.3.56 + - @backstage/plugin-badges-backend@0.3.5 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25 + - @backstage/plugin-devtools-backend@0.2.5 + - @backstage/plugin-entity-feedback-backend@0.2.5 + - @backstage/plugin-jenkins-backend@0.3.2 + - @backstage/plugin-linguist-backend@0.5.5 + - @backstage/plugin-nomad-backend@0.1.10 + - @backstage/plugin-proxy-backend@0.4.6 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/plugin-sonarqube-backend@0.2.10 + - @backstage/plugin-todo-backend@0.3.6 + - @backstage/backend-plugin-api@0.6.8 + +## @backstage/backend-plugin-manager@0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/cli-node@0.2.1 + - @backstage/plugin-events-backend@0.2.17 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.17 + - @backstage/plugin-search-common@1.2.9 + +## e2e-test@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.8 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## techdocs-cli-embedded-app@0.2.89 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/cli@0.25.0 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog@1.16.0 + - @backstage/core-app-api@1.11.2 + - @backstage/test-utils@1.4.6 + - @backstage/plugin-techdocs@1.9.2 + - @backstage/app-defaults@1.4.6 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-techdocs-react@1.1.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## @internal/plugin-todo-list@1.0.20 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## @internal/plugin-todo-list-backend@1.0.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @internal/plugin-todo-list-common@1.0.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-visualizer@0.0.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 diff --git a/docs/releases/v1.21.0-next.2-changelog.md b/docs/releases/v1.21.0-next.2-changelog.md new file mode 100644 index 0000000000..ba5c3c007d --- /dev/null +++ b/docs/releases/v1.21.0-next.2-changelog.md @@ -0,0 +1,3088 @@ +# Release v1.21.0-next.2 + +## @backstage/catalog-client@1.5.0-next.0 + +### Minor Changes + +- 38340678c3: The internals of `CatalogClient` are now auto-generated using the `backstage-repo-tools schema openapi generate-client` command. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/cli@0.25.0-next.1 + +### Minor Changes + +- 38340678c3: Updates the ESLint config to ignore issues created by generated files in `**/src/generated/**`. + +### Patch Changes + +- 0ffee55010: Toned down the warning message when git is not found +- c6f3743172: Added a warning when starting a standalone backend plugin that hasn't been updated to the new backend system. +- 3e358b0dff: Added deprecation warning for React Router v6 beta, please make sure you have migrated your apps to use React Router v6 stable as support for the beta version will be removed. See the [migration tutorial](https://backstage.io/docs/tutorials/react-router-stable-migration) for more information. +- 8056425e09: Updated dependency `@typescript-eslint/eslint-plugin` to `6.12.0`. +- 33e96e59e7: Switched the `@typescript-eslint/eslint-plugin` dependency back to using a `^` version range. +- Updated dependencies + - @backstage/eslint-plugin@0.1.4-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.5.3 + - @backstage/errors@1.2.3 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/frontend-app-api@0.4.0-next.1 + +### Minor Changes + +- e539735435: Updated core extension structure to make space for the sign-in page by adding `core.router`. + +### Patch Changes + +- 5eb6b8a7bc: Added the nav logo extension for customization of sidebar logo +- 1f12fb762c: Create a core components extension that allows adopters to override core app components such as `Progress`, `BootErrorPage`, `NotFoundErrorPage` and `ErrorBoundaryFallback`. +- 59709286b3: Collect and register feature flags from plugins and extension overrides. +- f27ee7d937: Migrate analytics route tracker component. +- a5a04739e1: Updates to provide `node` to extension factories instead of `id` and `source`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-plugin-api@0.4.0-next.1 + +### Minor Changes + +- a5a04739e1: The extension `factory` function now longer receives `id` or `source`, but instead now provides the extension's `AppNode` as `node`. The `ExtensionBoundary` component has also been updated to receive a `node` prop rather than `id` and `source`. + +### Patch Changes + +- 5eb6b8a7bc: Added the nav logo extension for customization of sidebar logo +- 1f12fb762c: Create factories for overriding default core components extensions. +- 59709286b3: Add feature flags to plugins and extension overrides. +- e539735435: Added `createSignInPageExtension`. +- f27ee7d937: Migrate analytics api and context files. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/repo-tools@0.5.0-next.0 + +### Minor Changes + +- aea8f8d329: **BREAKING**: API Reports generated for sub-path exports now place the name as a suffix rather than prefix, for example `api-report-alpha.md` instead of `alpha-api-report.md`. When upgrading to this version you'll need to re-create any such API reports and delete the old ones. +- 38340678c3: Adds a new command `schema openapi generate-client` that creates a Typescript client with Backstage flavor, including the discovery API and fetch API. This command doesn't currently generate a complete client and needs to be wrapped or exported manually by a separate Backstage plugin. See `@backstage/catalog-client/src/generated` for example output. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/errors@1.2.3 + +## @techdocs/cli@1.8.0-next.1 + +### Minor Changes + +- b2dccad7b3: Support passing additional `mkdocs-server` CLI parameters (`--dirtyreload`, `--strict` and `--clean`) when run in containerized mode. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.11.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.0-next.0 + +### Minor Changes + +- 271aa12c7c: Release of `oauth2-proxy-provider` plugin + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.0-next.0 + +### Minor Changes + +- ed02c69a3c: Add VMware Cloud auth backend module provider + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-azure-devops-backend@0.5.0-next.1 + +### Minor Changes + +- 844969cd97: **BREAKING** New `fromConfig` static method must be used now when creating an instance of the `AzureDevOpsApi` + + Added support for using the `AzureDevOpsCredentialsProvider` + +### Patch Changes + +- 043b724c56: Introduced new `AzureDevOpsAnnotatorProcessor` that adds the needed annotations automatically. Also, moved constants to common package so they can be shared more easily +- Updated dependencies + - @backstage/plugin-azure-devops-common@0.3.2-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog@1.16.0-next.2 + +### Minor Changes + +- e223f2264d: Properly support both function- and string-form visibility filter expressions in the new extensions exported via `/alpha`. + +### Patch Changes + +- 53600976bb: Ensure that passed-in icons are taken advantage of in the presentation API +- a5a04739e1: Internal refactor of alpha exports due to a change in how extension factories are defined. +- 78a10bb085: Adding in spec.type chip to search results for clarity +- fb8f3bdbc2: Updated alpha translation message keys to use nested format and camel case. +- 531e1a2a79: Updated alpha plugin to include the `unregisterRedirect` external route. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-scaffolder-common@1.4.3 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-home@0.6.0-next.1 + +### Minor Changes + +- 5a317f59c0: Added view of entities grouped by kind to make it easier to distinguish entities with different kind but same name + +### Patch Changes + +- 2b725913c1: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/plugin-home-react@0.1.6-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-pagerduty@0.7.0-next.1 + +### Minor Changes + +- 5fca16fdf6: This package has been deprecated, consider using [@pagerduty/backstage-plugin](https://github.com/pagerduty/backstage-plugin) instead. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-home-react@0.1.6-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/app-defaults@1.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/backend-app-api@0.5.9-next.1 + +### Patch Changes + +- 1da5f434f3: Ensure redaction of secrets that have accidental extra whitespace around them +- 9f8f266ff4: Add redacting for secrets in stack traces of logs +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.5.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-node@0.7.19-next.1 + +## @backstage/backend-common@0.20.0-next.1 + +### Patch Changes + +- 2666675457: Updated dependency `@google-cloud/storage` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-app-api@0.5.9-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-dev-utils@0.1.2 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.5.3 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.9-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + +## @backstage/backend-openapi-utils@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/backend-plugin-api@0.6.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/backend-tasks@0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.9-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/core-app-api@1.11.2-next.1 + +### Patch Changes + +- 3e358b0dff: Added deprecation warning for React Router v6 beta, please make sure you have migrated your apps to use React Router v6 stable as support for the beta version will be removed. See the [migration tutorial](https://backstage.io/docs/tutorials/react-router-stable-migration) for more information. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/core-compat-api@0.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/core-app-api@1.11.2-next.1 + +## @backstage/core-components@0.13.9-next.1 + +### Patch Changes + +- e8f2acef80: Added a new `/testUtils` sub-path that initially exports a `mockBreakpoint` helper. +- 07dfdf3702: Updated dependency `linkifyjs` to `4.1.3`. +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- f291757e70: Update `linkify-react` to version `4.1.3` +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/version-bridge@1.0.7 + +## @backstage/core-plugin-api@1.8.1-next.1 + +### Patch Changes + +- 0c93dc37b2: The `createTranslationRef` function from the `/alpha` subpath can now also accept a nested object structure of default translation messages, which will be flatted using `.` separators. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/create-app@0.5.8-next.2 + +### Patch Changes + +- 375b6f7d68: CircelCI plugin moved permanently +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## @backstage/dev-utils@1.0.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/app-defaults@1.4.6-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/eslint-plugin@0.1.4-next.0 + +### Patch Changes + +- 107dc46ab1: The `no-undeclared-imports` rule will now prefer using version queries that already exist en the repo for the same dependency type when installing new packages. + +## @backstage/frontend-test-utils@0.1.0-next.1 + +### Patch Changes + +- e539735435: Updates for `core.router` addition. +- c21c9cf07b: Re-export mock API implementations as well as `TestApiProvider`, `TestApiRegistry`, `withLogCollector`, and `setupRequestMockHandlers` from `@backstage/test-utils`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/frontend-app-api@0.4.0-next.1 + - @backstage/test-utils@1.4.6-next.1 + - @backstage/types@1.1.1 + +## @backstage/integration@1.8.0-next.1 + +### Patch Changes + +- 99fb54183b: Updated dependency `@azure/identity` to `^4.0.0`. +- Updated dependencies + - @backstage/config@1.1.1 + +## @backstage/integration-react@1.1.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/config@1.1.1 + +## @backstage/test-utils@1.4.6-next.1 + +### Patch Changes + +- e8f2acef80: Deprecated `mockBreakpoint`, as it is now available from `@backstage/core-components/testUtils` instead. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/plugin-adr@0.6.11-next.1 + +### Patch Changes + +- fb8f3bdbc2: Updated alpha translation message keys to use nested format and camel case. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-adr-common@0.2.18-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-adr-backend@0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-adr-common@0.2.18-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-adr-common@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-airbrake@0.3.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/test-utils@1.4.6-next.1 + - @backstage/dev-utils@1.0.25-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-airbrake-backend@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + +## @backstage/plugin-allure@0.1.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-analytics-module-ga@0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-analytics-module-ga4@0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-analytics-module-newrelic-browser@0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + +## @backstage/plugin-apache-airflow@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-api-docs@0.10.2-next.2 + +### Patch Changes + +- e16e7ce6a5: Updated dependency `@asyncapi/react-component` to `1.2.2`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-catalog@1.16.0-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-apollo-explorer@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-app-backend@0.3.56-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.5.3 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.8-next.1 + +## @backstage/plugin-app-node@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + +## @backstage/plugin-auth-backend@0.20.1-next.1 + +### Patch Changes + +- 7ac25759a5: `oauth2-proxy` auth implementation has been moved to `@backstage/plugin-auth-backend-module-oauth2-proxy-provider` +- bcbbf8e042: Updated dependency `@google-cloud/firestore` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.0-next.0 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.5-next.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.5-next.1 + - @backstage/plugin-auth-backend-module-google-provider@0.1.5-next.1 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.5-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.1-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.2-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-auth-node@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-azure-devops@0.3.10-next.1 + +### Patch Changes + +- 043b724c56: Introduced new `AzureDevOpsAnnotatorProcessor` that adds the needed annotations automatically. Also, moved constants to common package so they can be shared more easily +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-azure-devops-common@0.3.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-azure-devops-common@0.3.2-next.0 + +### Patch Changes + +- 043b724c56: Introduced new `AzureDevOpsAnnotatorProcessor` that adds the needed annotations automatically. Also, moved constants to common package so they can be shared more easily + +## @backstage/plugin-azure-sites@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-azure-sites-backend@0.1.18-next.1 + +### Patch Changes + +- 99fb54183b: Updated dependency `@azure/identity` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-badges@0.2.52-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-badges-backend@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-bazaar@0.2.20-next.2 + +### Patch Changes + +- 5d796829bb: Internalize 'AboutField' to break catalog dependency +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-bazaar-backend@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-bitrise@0.1.55-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-catalog-backend@1.15.1-next.1 + +### Patch Changes + +- 38340678c3: Update the OpenAPI spec to support the use of `openapi-generator`. +- 7123c58b3d: Updated dependency `@types/glob` to `^8.0.0`. +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-openapi-utils@0.1.1-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-azure@0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.1 + +### Patch Changes + +- eb44e92898: Support authenticated backends +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-openapi-utils@0.1.1-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-backend-module-github@0.4.6-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.12-next.1 + +### Patch Changes + +- 43b2eb8f70: Ensure that cursors always come back as JSON on sqlite too +- Updated dependencies + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.15-next.1 + +### Patch Changes + +- 99fb54183b: Updated dependency `@azure/identity` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-catalog-graph@0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.10.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.18 + +## @backstage/plugin-catalog-node@1.5.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + +## @backstage/plugin-catalog-react@1.9.2-next.1 + +### Patch Changes + +- 53600976bb: Ensure that passed-in icons are taken advantage of in the presentation API + +- 08d9e67199: Add default icon for kind resource. + +- a5a04739e1: Internal refactor of alpha exports due to a change in how extension factories are defined. + +- e223f2264d: Breaking alpha-API change to entity visibility filter functions to accept a bare entity as their first argument, instead of an object with an entity property. + + Functions that accept such filters now also support the string expression form of filters. + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-cicd-statistics@0.1.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-cicd-statistics@0.1.30-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-circleci@0.3.28-next.1 + +### Patch Changes + +- 375b6f7d68: CircelCI plugin moved permanently +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-cloudbuild@0.3.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-code-climate@0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-code-coverage@0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-code-coverage-backend@0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-codescene@0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-config-schema@0.1.48-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-cost-insights@0.12.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-cost-insights-common@0.1.2 + +## @backstage/plugin-devtools@0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.6 + +## @backstage/plugin-devtools-backend@0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.5.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-devtools-common@0.1.6 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + +## @backstage/plugin-dynatrace@8.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-entity-feedback@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-feedback-backend@0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-validation@0.1.13-next.1 + +### Patch Changes + +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-catalog-common@1.0.18 + +## @backstage/plugin-events-backend@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.1 + +## @backstage/plugin-events-backend-module-azure@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + +## @backstage/plugin-events-backend-module-github@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.1 + +## @backstage/plugin-events-backend-module-gitlab@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.1 + +## @backstage/plugin-events-backend-test-utils@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.17-next.1 + +## @backstage/plugin-events-node@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + +## @backstage/plugin-explore@0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/plugin-explore-react@0.0.34-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-explore-backend@0.0.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-explore-react@0.0.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-explore-common@0.0.2 + +## @backstage/plugin-firehydrant@0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-fossa@0.2.60-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-gcalendar@0.3.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-gcp-projects@0.3.44-next.1 + +### Patch Changes + +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-git-release-manager@0.3.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-github-actions@0.6.9-next.1 + +### Patch Changes + +- 08d7e4676a: Github Workflow Runs UI is modified to show in optional Card view instead of table, with branch selection option +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-github-deployments@0.1.59-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-github-issues@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-github-pull-requests-board@0.1.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-gitops-profiles@0.3.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-gocd@0.1.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-graphiql@0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-graphql-voyager@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-home-react@0.1.6-next.1 + +### Patch Changes + +- 2b725913c1: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-ilert@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-jenkins@0.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-jenkins-common@0.1.21 + +## @backstage/plugin-jenkins-backend@0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-jenkins-common@0.1.21 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + +## @backstage/plugin-kafka@0.3.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-kafka-backend@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-kubernetes@0.11.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/plugin-kubernetes-react@0.1.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-backend@0.14.0-next.1 + +### Patch Changes + +- ae94d3ce6f: Updated dependency `@aws-crypto/sha256-js` to `^5.0.0`. +- 99fb54183b: Updated dependency `@azure/identity` to `^4.0.0`. +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-kubernetes-node@0.1.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + +## @backstage/plugin-kubernetes-cluster@0.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/plugin-kubernetes-react@0.1.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-kubernetes-common@0.7.2-next.1 + +### Patch Changes + +- 5d796829bb: Remove unused dependency +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/plugin-kubernetes-node@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-kubernetes-react@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-lighthouse@0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-lighthouse-backend@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-linguist@0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-linguist-backend@0.5.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-microsoft-calendar@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-newrelic@0.3.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-newrelic-dashboard@0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-nomad@0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-nomad-backend@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-octopus-deploy@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-opencost@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-org@0.6.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-org-react@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-periskop@0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-periskop-backend@0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + +## @backstage/plugin-permission-backend@0.5.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + +## @backstage/plugin-permission-node@0.7.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/plugin-permission-react@0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/plugin-playlist@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-playlist-common@0.1.12 + +## @backstage/plugin-playlist-backend@0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-playlist-common@0.1.12 + +## @backstage/plugin-proxy-backend@0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + +## @backstage/plugin-puppetdb@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-rollbar@0.4.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-rollbar-backend@0.1.53-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + +## @backstage/plugin-scaffolder@1.16.2-next.1 + +### Patch Changes + +- 2b725913c1: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- b5fa6918dc: Fixing `headerOptions` not being passed through the `TemplatePage` component +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/plugin-scaffolder-react@1.6.2-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-scaffolder-backend@1.19.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-scaffolder-common@1.4.3 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.16-next.1 + +### Patch Changes + +- 7f8a801e6d: Added examples for `sentry:project:create` scaffolder action and unit tests. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + +## @backstage/plugin-scaffolder-node@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-scaffolder-react@1.6.2-next.1 + +### Patch Changes + +- fa66d1b5b3: Fixed bug in `ReviewState` where `enum` value was displayed in step review instead of the corresponding label when using `enumNames` +- 2aee53bbeb: Add horizontal slider if stepper overflows +- 2b725913c1: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-search@1.4.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend@1.4.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-openapi-utils@0.1.1-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-catalog@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-explore@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-pg@0.5.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-techdocs@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/plugin-techdocs-node@1.11.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-node@1.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-react@1.7.4-next.1 + +### Patch Changes + +- a5a04739e1: Internal refactor of alpha exports due to a change in how extension factories are defined. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-sentry@0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-shortcuts@0.3.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-sonarqube@0.7.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-sonarqube-react@0.1.11-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-sonarqube-backend@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-sonarqube-react@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-splunk-on-call@0.4.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-stack-overflow@0.1.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/plugin-home-react@0.1.6-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-stack-overflow-backend@0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.1-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-stackstorm@0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-tech-insights@0.3.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend@0.5.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + - @backstage/plugin-tech-insights-node@0.4.14-next.1 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-tech-insights-common@0.2.12 + - @backstage/plugin-tech-insights-node@0.4.14-next.1 + +## @backstage/plugin-tech-insights-node@0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-radar@0.6.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-techdocs@1.9.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.25-next.2 + +### Patch Changes + +- 5d796829bb: Remove unnecessary catalog dependency +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-catalog@1.16.0-next.2 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/test-utils@1.4.6-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/plugin-techdocs@1.9.2-next.2 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-techdocs-backend@1.9.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-techdocs-node@1.11.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.1 + +### Patch Changes + +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.1 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-techdocs-node@1.11.0-next.1 + +### Patch Changes + +- 99fb54183b: Updated dependency `@azure/identity` to `^4.0.0`. +- 2666675457: Updated dependency `@google-cloud/storage` to `^7.0.0`. +- 4f773c15f6: Bumped the default TechDocs docker image version to the latest which was released several month ago +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-techdocs-react@1.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/plugin-todo@0.2.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-todo-backend@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-openapi-utils@0.1.1-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.5.1-next.1 + +## @backstage/plugin-user-settings@0.7.14-next.2 + +### Patch Changes + +- fb8f3bdbc2: Updated alpha translation message keys to use nested format and camel case. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-user-settings-backend@0.2.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + +## @backstage/plugin-vault@0.1.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## @backstage/plugin-vault-backend@0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-vault-node@0.1.1-next.1 + +## @backstage/plugin-vault-node@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + +## @backstage/plugin-xcmetrics@0.2.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + +## example-app@0.2.90-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/cli@0.25.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-catalog@1.16.0-next.2 + - @backstage/plugin-scaffolder-react@1.6.2-next.1 + - @backstage/plugin-home@0.6.0-next.1 + - @backstage/plugin-github-actions@0.6.9-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/plugin-azure-devops@0.3.10-next.1 + - @backstage/plugin-scaffolder@1.16.2-next.1 + - @backstage/plugin-api-docs@0.10.2-next.2 + - @backstage/plugin-gcp-projects@0.3.44-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.1 + - @backstage/plugin-user-settings@0.7.14-next.2 + - @backstage/plugin-adr@0.6.11-next.1 + - @backstage/plugin-pagerduty@0.7.0-next.1 + - @backstage/plugin-catalog-import@0.10.4-next.2 + - @backstage/plugin-explore@0.4.14-next.1 + - @backstage/plugin-graphiql@0.3.1-next.2 + - @backstage/plugin-search@1.4.4-next.2 + - @backstage/plugin-stack-overflow@0.1.23-next.1 + - @backstage/plugin-tech-radar@0.6.11-next.2 + - @backstage/plugin-techdocs@1.9.2-next.2 + - @backstage/app-defaults@1.4.6-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-airbrake@0.3.28-next.1 + - @backstage/plugin-apache-airflow@0.2.18-next.1 + - @backstage/plugin-azure-sites@0.1.17-next.1 + - @backstage/plugin-badges@0.2.52-next.1 + - @backstage/plugin-catalog-graph@0.3.2-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.1 + - @backstage/plugin-cloudbuild@0.3.28-next.1 + - @backstage/plugin-code-coverage@0.2.21-next.1 + - @backstage/plugin-cost-insights@0.12.17-next.1 + - @backstage/plugin-devtools@0.1.7-next.1 + - @backstage/plugin-dynatrace@8.0.2-next.1 + - @backstage/plugin-entity-feedback@0.2.11-next.1 + - @backstage/plugin-gcalendar@0.3.21-next.1 + - @backstage/plugin-gocd@0.1.34-next.1 + - @backstage/plugin-jenkins@0.9.3-next.1 + - @backstage/plugin-kafka@0.3.28-next.1 + - @backstage/plugin-kubernetes@0.11.3-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.4-next.1 + - @backstage/plugin-lighthouse@0.4.13-next.1 + - @backstage/plugin-linguist@0.1.13-next.1 + - @backstage/plugin-microsoft-calendar@0.1.10-next.1 + - @backstage/plugin-newrelic@0.3.43-next.1 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.1 + - @backstage/plugin-nomad@0.1.9-next.1 + - @backstage/plugin-octopus-deploy@0.2.10-next.1 + - @backstage/plugin-org@0.6.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.1 + - @backstage/plugin-puppetdb@0.1.11-next.1 + - @backstage/plugin-rollbar@0.4.28-next.1 + - @backstage/plugin-sentry@0.5.13-next.1 + - @backstage/plugin-shortcuts@0.3.17-next.1 + - @backstage/plugin-stackstorm@0.1.9-next.1 + - @backstage/plugin-tech-insights@0.3.20-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.1 + - @backstage/plugin-todo@0.2.32-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.8 + +## example-app-next@0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/frontend-app-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/cli@0.25.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-catalog@1.16.0-next.2 + - @backstage/plugin-scaffolder-react@1.6.2-next.1 + - @backstage/plugin-home@0.6.0-next.1 + - @backstage/plugin-github-actions@0.6.9-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/plugin-azure-devops@0.3.10-next.1 + - @backstage/plugin-scaffolder@1.16.2-next.1 + - @backstage/plugin-api-docs@0.10.2-next.2 + - @backstage/plugin-gcp-projects@0.3.44-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.1 + - @backstage/plugin-user-settings@0.7.14-next.2 + - @backstage/plugin-adr@0.6.11-next.1 + - @backstage/plugin-pagerduty@0.7.0-next.1 + - app-next-example-plugin@0.0.4-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/plugin-catalog-import@0.10.4-next.2 + - @backstage/plugin-explore@0.4.14-next.1 + - @backstage/plugin-graphiql@0.3.1-next.2 + - @backstage/plugin-search@1.4.4-next.2 + - @backstage/plugin-tech-radar@0.6.11-next.2 + - @backstage/plugin-techdocs@1.9.2-next.2 + - @backstage/app-defaults@1.4.6-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-airbrake@0.3.28-next.1 + - @backstage/plugin-apache-airflow@0.2.18-next.1 + - @backstage/plugin-azure-sites@0.1.17-next.1 + - @backstage/plugin-badges@0.2.52-next.1 + - @backstage/plugin-catalog-graph@0.3.2-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.1 + - @backstage/plugin-cloudbuild@0.3.28-next.1 + - @backstage/plugin-code-coverage@0.2.21-next.1 + - @backstage/plugin-cost-insights@0.12.17-next.1 + - @backstage/plugin-devtools@0.1.7-next.1 + - @backstage/plugin-dynatrace@8.0.2-next.1 + - @backstage/plugin-entity-feedback@0.2.11-next.1 + - @backstage/plugin-gcalendar@0.3.21-next.1 + - @backstage/plugin-gocd@0.1.34-next.1 + - @backstage/plugin-jenkins@0.9.3-next.1 + - @backstage/plugin-kafka@0.3.28-next.1 + - @backstage/plugin-kubernetes@0.11.3-next.1 + - @backstage/plugin-lighthouse@0.4.13-next.1 + - @backstage/plugin-linguist@0.1.13-next.1 + - @backstage/plugin-microsoft-calendar@0.1.10-next.1 + - @backstage/plugin-newrelic@0.3.43-next.1 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.1 + - @backstage/plugin-octopus-deploy@0.2.10-next.1 + - @backstage/plugin-org@0.6.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.1 + - @backstage/plugin-puppetdb@0.1.11-next.1 + - @backstage/plugin-rollbar@0.4.28-next.1 + - @backstage/plugin-sentry@0.5.13-next.1 + - @backstage/plugin-shortcuts@0.3.17-next.1 + - @backstage/plugin-stackstorm@0.1.9-next.1 + - @backstage/plugin-tech-insights@0.3.20-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.1 + - @backstage/plugin-todo@0.2.32-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.8 + +## app-next-example-plugin@0.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + +## example-backend@0.2.90-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.1-next.1 + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/plugin-azure-devops-backend@0.5.0-next.1 + - @backstage/plugin-kubernetes-backend@0.14.0-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-azure-sites-backend@0.1.18-next.1 + - @backstage/backend-common@0.20.0-next.1 + - example-app@0.2.90-next.2 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-adr-backend@0.4.5-next.1 + - @backstage/plugin-app-backend@0.3.56-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-badges-backend@0.3.5-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-code-coverage-backend@0.2.22-next.1 + - @backstage/plugin-devtools-backend@0.2.5-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.1 + - @backstage/plugin-events-backend@0.2.17-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + - @backstage/plugin-explore-backend@0.0.18-next.1 + - @backstage/plugin-jenkins-backend@0.3.2-next.1 + - @backstage/plugin-kafka-backend@0.3.6-next.1 + - @backstage/plugin-lighthouse-backend@0.3.5-next.1 + - @backstage/plugin-linguist-backend@0.5.5-next.1 + - @backstage/plugin-nomad-backend@0.1.10-next.1 + - @backstage/plugin-permission-backend@0.5.31-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-playlist-backend@0.3.12-next.1 + - @backstage/plugin-proxy-backend@0.4.6-next.1 + - @backstage/plugin-rollbar-backend@0.1.53-next.1 + - @backstage/plugin-scaffolder-backend@1.19.2-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.1 + - @backstage/plugin-search-backend@1.4.8-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.17-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.1 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-tech-insights-backend@0.5.22-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.1 + - @backstage/plugin-tech-insights-node@0.4.14-next.1 + - @backstage/plugin-techdocs-backend@1.9.1-next.1 + - @backstage/plugin-todo-backend@0.3.6-next.1 + +## example-backend-next@0.0.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.1 + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/plugin-azure-devops-backend@0.5.0-next.1 + - @backstage/plugin-kubernetes-backend@0.14.0-next.1 + - @backstage/backend-defaults@0.2.8-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/plugin-adr-backend@0.4.5-next.1 + - @backstage/plugin-app-backend@0.3.56-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-badges-backend@0.3.5-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.1 + - @backstage/plugin-devtools-backend@0.2.5-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.1 + - @backstage/plugin-jenkins-backend@0.3.2-next.1 + - @backstage/plugin-lighthouse-backend@0.3.5-next.1 + - @backstage/plugin-linguist-backend@0.5.5-next.1 + - @backstage/plugin-nomad-backend@0.1.10-next.1 + - @backstage/plugin-permission-backend@0.5.31-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-playlist-backend@0.3.12-next.1 + - @backstage/plugin-proxy-backend@0.4.6-next.1 + - @backstage/plugin-scaffolder-backend@1.19.2-next.1 + - @backstage/plugin-search-backend@1.4.8-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.1 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-sonarqube-backend@0.2.10-next.1 + - @backstage/plugin-techdocs-backend@1.9.1-next.1 + - @backstage/plugin-todo-backend@0.3.6-next.1 + +## @backstage/backend-plugin-manager@0.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-events-backend@0.2.17-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 + +## e2e-test@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.8-next.2 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## techdocs-cli-embedded-app@0.2.89-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/cli@0.25.0-next.1 + - @backstage/plugin-catalog@1.16.0-next.2 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/test-utils@1.4.6-next.1 + - @backstage/plugin-techdocs@1.9.2-next.2 + - @backstage/app-defaults@1.4.6-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + +## @internal/plugin-todo-list@1.0.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + +## @internal/plugin-todo-list-backend@1.0.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 diff --git a/docs/releases/v1.21.0-next.3-changelog.md b/docs/releases/v1.21.0-next.3-changelog.md new file mode 100644 index 0000000000..a45c6d6df0 --- /dev/null +++ b/docs/releases/v1.21.0-next.3-changelog.md @@ -0,0 +1,3025 @@ +# Release v1.21.0-next.3 + +## @backstage/config-loader@1.6.0-next.0 + +### Minor Changes + +- 24f5a85: Add "path" to `TransformFunc` context + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/core-compat-api@0.1.0-next.2 + +### Minor Changes + +- cf5cc4c: Discover plugins and routes recursively beneath the root routes in `collectLecacyRoutes` + +### Patch Changes + +- 8226442: Added `compatWrapper`, which can be used to wrap any React element to provide bi-directional interoperability between the `@backstage/core-*-api` and `@backstage/frontend-*-api` APIs. +- 8f5d6c1: Updates to match the new extension input wrapping. +- b7adf24: Delete alpha DI compatibility helper for components, migrating components should be simple without a helper. +- 046e443: Updates for compatibility with the new extension IDs. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-app-api@0.4.0-next.2 + +### Minor Changes + +- ea06590: The app no longer provides the `AppContext` from `@backstage/core-plugin-api`. Components that require this context to be available should use the `compatWrapper` helper from `@backstage/core-compat-api`. + +### Patch Changes + +- aeb8008: Add support for translation extensions. +- b7adf24: Use the new plugin type for error boundary components. +- 8f5d6c1: Updates to match the new extension input wrapping. +- cb4197a: Forward ` node`` instead of `extensionId\` to resolved extension inputs. +- 8837a96: Updates to match the introduction of `ExtensionDefinition` and new extension ID naming patterns. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-plugin-api@0.4.0-next.2 + +### Minor Changes + +- 8f5d6c1: Extension inputs are now wrapped into an additional object when passed to the extension factory, with the previous values being available at the `output` property. The `ExtensionInputValues` type has also been replaced by `ResolvedExtensionInputs`. +- 8837a96: **BREAKING**: This version changes how extensions are created and how their IDs are determined. The `createExtension` function now accepts `kind`, `namespace` and `name` instead of `id`. All of the new options are optional, and are used to construct the final extension ID. By convention extension creators should set the `kind` to match their own name, for example `createNavItemExtension` sets the kind `nav-item`. + + The `createExtension` function as well as all extension creators now also return an `ExtensionDefinition` rather than an `Extension`, which in turn needs to be passed to `createPlugin` or `createExtensionOverrides` to be used. + +### Patch Changes + +- b7adf24: Update alpha component ref type to be more specific than any, delete boot page component and use new plugin type for error boundary component extensions. +- 73246ec: Added translation APIs as well as `createTranslationExtension`. +- cb4197a: Forward ` node`` instead of `extensionId\` to resolved extension inputs. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/plugin-catalog-backend@1.16.0-next.2 + +### Minor Changes + +- 7804597: Permission rules can now be added for the Catalog plugin through the `CatalogPermissionExtensionPoint` interface. + +### Patch Changes + +- 50ee804: Wrap single `pipelineLoop` of TaskPipeline in a span for better traces +- a168507: Deprecated `EntitiesSearchFilter` and `EntityFilter`, which can now be imported from `@backstage/plugin-catalog-node` instead +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.2 + - @backstage/backend-openapi-utils@0.1.1-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-events-node@0.2.17-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## @backstage/plugin-catalog-node@1.6.0-next.2 + +### Minor Changes + +- a168507: Added `EntitiesSearchFilter` and `EntityFilter` from `@backstage/plugin-catalog-backend`, for reuse +- 7804597: Permission rules can now be added for the Catalog plugin through the `CatalogPermissionExtensionPoint` interface. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## @backstage/plugin-kubernetes-react@0.2.0-next.2 + +### Minor Changes + +- 899d71a: Change `formatClusterLink` to be an API and make it async for further customization possibilities. + + **BREAKING** + If you have a custom k8s page and used `formatClusterLink` directly, you need to migrate to new `kubernetesClusterLinkFormatterApiRef` + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## @backstage/plugin-lighthouse-backend@0.4.0-next.2 + +### Minor Changes + +- 7f0dbfd: Fixed crashes faced with custom schedule configuration. The configuration schema has been update to leverage the TaskScheduleDefinition interface. It is highly recommended to move the `lighthouse.shedule` and `lighthouse.timeout` respectively to `lighthouse.schedule.frequency` and `lighthouse.schedule.timeout`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/app-defaults@1.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## @backstage/backend-app-api@0.5.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.0-next.0 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## @backstage/backend-common@0.20.0-next.2 + +### Patch Changes + +- bc67498: Updated dependency `archiver` to `^6.0.0`. + Updated dependency `@types/archiver` to `^6.0.0`. +- Updated dependencies + - @backstage/config-loader@1.6.0-next.0 + - @backstage/backend-app-api@0.5.9-next.2 + - @backstage/backend-dev-utils@0.1.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-app-api@0.5.9-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## @backstage/backend-openapi-utils@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/backend-plugin-api@0.6.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/backend-tasks@0.5.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.2.9-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-app-api@0.5.9-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/catalog-client@1.5.0-next.1 + +### Patch Changes + +- 82fa88b: Fixes a bug where some query parameters were double URL encoded. +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/cli@0.25.0-next.2 + +### Patch Changes + +- 32018ff: Enable the `tsx` loader to work on Node 18.19 and up +- a3edc18: Updated dependency `vite-plugin-node-polyfills` to `^0.17.0`. +- 627554e: Updated dependency `@rollup/plugin-node-resolve` to `^15.0.0`. +- c07cee5: Updated dependency `@rollup/plugin-json` to `^6.0.0`. +- 2565cc8: Updated dependency `@rollup/plugin-commonjs` to `^25.0.0`. +- Updated dependencies + - @backstage/config-loader@1.6.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/eslint-plugin@0.1.4-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/core-components@0.13.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/version-bridge@1.0.7 + +## @backstage/create-app@0.5.8-next.3 + +### Patch Changes + +- a96c2d4: Include the `` for group entities by default +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## @backstage/dev-utils@1.0.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/app-defaults@1.4.6-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + +## @backstage/frontend-test-utils@0.1.0-next.2 + +### Patch Changes + +- 818eea4: Updates for compatibility with the new extension IDs. +- b9aa6e4: Migrate `renderInTestApp` to `@backstage/frontend-test-utils` for testing individual React components in an app. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/frontend-app-api@0.4.0-next.2 + - @backstage/test-utils@1.4.6-next.2 + - @backstage/types@1.1.1 + +## @backstage/repo-tools@0.5.0-next.1 + +### Patch Changes + +- f909e9d: Includes templates in @backstage/repo-tools package and use them in the CLI + +- da3c4db: Updates the `schema openapi generate-client` command to export all generated types from the generated directory. + +- 7959f23: The `api-reports` command now checks for api report files that no longer apply. + If it finds such files, it's treated basically the same as report errors do, and + the check fails. + + For example, if you had an `api-report-alpha.md` but then removed the alpha + export, the reports generator would now report that this file needs to be + deleted. + +- f49e237: Fixed a bug where `schema openapi init` created an invalid test command. + +- f91be2c: Updated dependency `@stoplight/types` to `^14.0.0`. + +- 45bfb20: Execute `openapi-generator-cli` from `@backstage/repo-tools` directory to force it to use our openapitools.json config file. + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/errors@1.2.3 + +## @techdocs/cli@1.8.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.11.0-next.2 + +## @backstage/test-utils@1.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## @backstage/theme@0.5.0-next.1 + +### Patch Changes + +- cd0dd4c: Align Material UI v5 `Paper` component background color in dark mode to v4. + +## @backstage/plugin-adr@0.6.11-next.2 + +### Patch Changes + +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-adr-common@0.2.18-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-adr-backend@0.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-adr-common@0.2.18-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-airbrake@0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/dev-utils@1.0.25-next.2 + - @backstage/test-utils@1.4.6-next.2 + +## @backstage/plugin-airbrake-backend@0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + +## @backstage/plugin-allure@0.1.44-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-analytics-module-ga@0.1.36-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-analytics-module-ga4@0.1.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-analytics-module-newrelic-browser@0.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-apache-airflow@0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-api-docs@0.10.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog@1.16.0-next.3 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-apollo-explorer@0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-app-backend@0.3.56-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.0-next.0 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.8-next.2 + +## @backstage/plugin-app-node@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + +## @backstage/plugin-auth-backend@0.20.1-next.2 + +### Patch Changes + +- 783797a: fix static token issuer not being able to initialize +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.0-next.1 + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.0-next.2 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.5-next.2 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.5-next.2 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.1-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.2-next.2 + - @backstage/plugin-auth-backend-module-google-provider@0.1.5-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.5-next.2 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.1.0-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.2-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.5-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.5-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.3-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.5-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.0-next.1 + +### Patch Changes + +- a6be465: Exported the provider as default so it gets discovered when using `featureDiscoveryServiceFactory()` +- 510dab4: Change provider id from `oauth2ProxyProvider` to `oauth2Proxy` +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.1-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.2-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-auth-node@0.4.2-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-azure-devops@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-azure-devops-common@0.3.2-next.0 + +## @backstage/plugin-azure-devops-backend@0.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-azure-devops-common@0.3.2-next.0 + - @backstage/plugin-catalog-common@1.0.18 + +## @backstage/plugin-azure-sites@0.1.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-azure-sites-backend@0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-badges@0.2.52-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-badges-backend@0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bazaar@0.2.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bazaar-backend@0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bitrise@0.1.55-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-catalog@1.16.0-next.3 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-scaffolder-common@1.4.3 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-catalog-backend-module-aws@0.3.2-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## @backstage/plugin-catalog-backend-module-azure@0.1.27-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-openapi-utils@0.1.1-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.23-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-events-node@0.2.17-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.21-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.8-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.24-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.4.6-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-events-node@0.2.17-next.2 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.2-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-catalog-backend-module-github@0.4.6-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/config@1.1.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.5-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.12-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-events-node@0.2.17-next.2 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.13-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-catalog-graph@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.10.4-next.3 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-catalog-common@1.0.18 + +## @backstage/plugin-catalog-react@1.9.2-next.2 + +### Patch Changes + +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-cicd-statistics@0.1.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-cicd-statistics@0.1.30-next.2 + +## @backstage/plugin-circleci@0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-cloudbuild@0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-code-climate@0.1.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-code-coverage@0.2.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-code-coverage-backend@0.2.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-codescene@0.1.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-config-schema@0.1.48-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-cost-insights@0.12.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-cost-insights-common@0.1.2 + +## @backstage/plugin-devtools@0.1.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.6 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## @backstage/plugin-devtools-backend@0.2.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.0-next.0 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.6 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## @backstage/plugin-dynatrace@8.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-entity-feedback@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-feedback-backend@0.2.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-validation@0.1.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.18 + +## @backstage/plugin-events-backend@0.2.17-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.2 + +## @backstage/plugin-events-backend-module-aws-sqs@0.2.11-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.2 + +## @backstage/plugin-events-backend-module-azure@0.1.18-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/plugin-events-node@0.2.17-next.2 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.1.18-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/plugin-events-node@0.2.17-next.2 + +## @backstage/plugin-events-backend-module-gerrit@0.1.18-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/plugin-events-node@0.2.17-next.2 + +## @backstage/plugin-events-backend-module-github@0.1.18-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.2 + +## @backstage/plugin-events-backend-module-gitlab@0.1.18-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.2 + +## @backstage/plugin-events-backend-test-utils@0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.17-next.2 + +## @backstage/plugin-events-node@0.2.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + +## @backstage/plugin-explore@0.4.14-next.2 + +### Patch Changes + +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-explore-react@0.0.34-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-explore-backend@0.0.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.2 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-firehydrant@0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-fossa@0.2.60-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-gcalendar@0.3.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-gcp-projects@0.3.44-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-git-release-manager@0.3.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-github-actions@0.6.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + +## @backstage/plugin-github-deployments@0.1.59-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + +## @backstage/plugin-github-issues@0.2.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-github-pull-requests-board@0.1.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-gitops-profiles@0.3.43-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-gocd@0.1.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-graphiql@0.3.1-next.3 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-graphql-voyager@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-home@0.6.0-next.2 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-home-react@0.1.6-next.2 + +## @backstage/plugin-home-react@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-ilert@0.2.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-jenkins@0.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.21 + +## @backstage/plugin-jenkins-backend@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.21 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## @backstage/plugin-kafka@0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-kafka-backend@0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-kubernetes@0.11.3-next.2 + +### Patch Changes + +- 899d71a: Change `formatClusterLink` to be an API and make it async for further customization possibilities. + + **BREAKING** + If you have a custom k8s page and used `formatClusterLink` directly, you need to migrate to new `kubernetesClusterLinkFormatterApiRef` + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-kubernetes-react@0.2.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## @backstage/plugin-kubernetes-backend@0.14.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/plugin-kubernetes-node@0.1.2-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## @backstage/plugin-kubernetes-cluster@0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-kubernetes-react@0.2.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## @backstage/plugin-kubernetes-node@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## @backstage/plugin-lighthouse@0.4.13-next.2 + +### Patch Changes + +- ffbf656: Updated README +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-linguist@0.1.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-linguist-backend@0.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-microsoft-calendar@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-newrelic@0.3.43-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-newrelic-dashboard@0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-nomad@0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-nomad-backend@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-octopus-deploy@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-opencost@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-org@0.6.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-org-react@0.1.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-pagerduty@0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-home-react@0.1.6-next.2 + +## @backstage/plugin-periskop@0.1.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-periskop-backend@0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + +## @backstage/plugin-permission-backend@0.5.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## @backstage/plugin-permission-node@0.7.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/plugin-playlist@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist-common@0.1.12 + +## @backstage/plugin-playlist-backend@0.3.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-playlist-common@0.1.12 + +## @backstage/plugin-proxy-backend@0.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + +## @backstage/plugin-puppetdb@0.1.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-rollbar@0.4.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-rollbar-backend@0.1.53-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + +## @backstage/plugin-scaffolder@1.16.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/plugin-scaffolder-react@1.6.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-scaffolder-backend@1.19.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-scaffolder-common@1.4.3 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## @backstage/plugin-scaffolder-node@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-scaffolder-react@1.6.2-next.2 + +### Patch Changes + +- 5bb5240: Fixed issue for showing undefined for hidden form items +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-search@1.4.4-next.3 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend@1.4.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-openapi-utils@0.1.1-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-catalog@0.1.12-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-explore@0.1.12-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/config@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-pg@0.5.17-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.1-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-techdocs@0.1.12-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-techdocs-node@1.11.0-next.2 + +## @backstage/plugin-search-backend-node@1.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-react@1.7.4-next.2 + +### Patch Changes + +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-sentry@0.5.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-shortcuts@0.3.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-sonarqube@0.7.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-sonarqube-react@0.1.11-next.1 + +## @backstage/plugin-sonarqube-backend@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-splunk-on-call@0.4.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-stack-overflow@0.1.23-next.2 + +### Patch Changes + +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-home-react@0.1.6-next.2 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-stack-overflow-backend@0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.1-next.2 + - @backstage/config@1.1.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-stackstorm@0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-tech-insights@0.3.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend@0.5.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + - @backstage/plugin-tech-insights-node@0.4.14-next.2 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-tech-insights-common@0.2.12 + - @backstage/plugin-tech-insights-node@0.4.14-next.2 + +## @backstage/plugin-tech-insights-node@0.4.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-radar@0.6.11-next.3 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-techdocs@1.9.2-next.3 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-techdocs-react@1.1.14-next.2 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.25-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-techdocs@1.9.2-next.3 + - @backstage/plugin-catalog@1.16.0-next.3 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/test-utils@1.4.6-next.2 + - @backstage/plugin-techdocs-react@1.1.14-next.2 + +## @backstage/plugin-techdocs-backend@1.9.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-techdocs-node@1.11.0-next.2 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.2 + +## @backstage/plugin-techdocs-node@1.11.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-techdocs-react@1.1.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/plugin-todo@0.2.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-todo-backend@0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-openapi-utils@0.1.1-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-user-settings@0.7.14-next.3 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-user-settings-backend@0.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-vault@0.1.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-vault-backend@0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-vault-node@0.1.1-next.2 + +## @backstage/plugin-vault-node@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + +## @backstage/plugin-xcmetrics@0.2.46-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## example-app@0.2.90-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.4.0-next.2 + - @backstage/cli@0.25.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-kubernetes@0.11.3-next.2 + - @backstage/plugin-lighthouse@0.4.13-next.2 + - @backstage/plugin-catalog-import@0.10.4-next.3 + - @backstage/plugin-user-settings@0.7.14-next.3 + - @backstage/plugin-tech-radar@0.6.11-next.3 + - @backstage/plugin-graphiql@0.3.1-next.3 + - @backstage/plugin-techdocs@1.9.2-next.3 + - @backstage/plugin-catalog@1.16.0-next.3 + - @backstage/plugin-search@1.4.4-next.3 + - @backstage/plugin-home@0.6.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-stack-overflow@0.1.23-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/plugin-explore@0.4.14-next.2 + - @backstage/plugin-adr@0.6.11-next.2 + - @backstage/plugin-scaffolder-react@1.6.2-next.2 + - @backstage/app-defaults@1.4.6-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-airbrake@0.3.28-next.2 + - @backstage/plugin-apache-airflow@0.2.18-next.2 + - @backstage/plugin-api-docs@0.10.2-next.3 + - @backstage/plugin-azure-devops@0.3.10-next.2 + - @backstage/plugin-azure-sites@0.1.17-next.2 + - @backstage/plugin-badges@0.2.52-next.2 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-graph@0.3.2-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.2 + - @backstage/plugin-cloudbuild@0.3.28-next.2 + - @backstage/plugin-code-coverage@0.2.21-next.2 + - @backstage/plugin-cost-insights@0.12.17-next.2 + - @backstage/plugin-devtools@0.1.7-next.2 + - @backstage/plugin-dynatrace@8.0.2-next.2 + - @backstage/plugin-entity-feedback@0.2.11-next.2 + - @backstage/plugin-gcalendar@0.3.21-next.2 + - @backstage/plugin-gcp-projects@0.3.44-next.2 + - @backstage/plugin-github-actions@0.6.9-next.2 + - @backstage/plugin-gocd@0.1.34-next.2 + - @backstage/plugin-jenkins@0.9.3-next.2 + - @backstage/plugin-kafka@0.3.28-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.4-next.2 + - @backstage/plugin-linguist@0.1.13-next.2 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.10-next.2 + - @backstage/plugin-newrelic@0.3.43-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.2 + - @backstage/plugin-nomad@0.1.9-next.2 + - @backstage/plugin-octopus-deploy@0.2.10-next.2 + - @backstage/plugin-org@0.6.18-next.2 + - @backstage/plugin-pagerduty@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.2 + - @backstage/plugin-puppetdb@0.1.11-next.2 + - @backstage/plugin-rollbar@0.4.28-next.2 + - @backstage/plugin-scaffolder@1.16.2-next.2 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-sentry@0.5.13-next.2 + - @backstage/plugin-shortcuts@0.3.17-next.2 + - @backstage/plugin-stackstorm@0.1.9-next.2 + - @backstage/plugin-tech-insights@0.3.20-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.2 + - @backstage/plugin-techdocs-react@1.1.14-next.2 + - @backstage/plugin-todo@0.2.32-next.2 + +## example-app-next@0.0.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/frontend-app-api@0.4.0-next.2 + - @backstage/cli@0.25.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-kubernetes@0.11.3-next.2 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/plugin-lighthouse@0.4.13-next.2 + - @backstage/plugin-catalog-import@0.10.4-next.3 + - @backstage/plugin-user-settings@0.7.14-next.3 + - @backstage/plugin-tech-radar@0.6.11-next.3 + - @backstage/plugin-graphiql@0.3.1-next.3 + - @backstage/plugin-techdocs@1.9.2-next.3 + - @backstage/plugin-catalog@1.16.0-next.3 + - @backstage/plugin-search@1.4.4-next.3 + - @backstage/plugin-home@0.6.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/plugin-explore@0.4.14-next.2 + - @backstage/plugin-adr@0.6.11-next.2 + - @backstage/plugin-scaffolder-react@1.6.2-next.2 + - app-next-example-plugin@0.0.4-next.2 + - @backstage/app-defaults@1.4.6-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-airbrake@0.3.28-next.2 + - @backstage/plugin-apache-airflow@0.2.18-next.2 + - @backstage/plugin-api-docs@0.10.2-next.3 + - @backstage/plugin-azure-devops@0.3.10-next.2 + - @backstage/plugin-azure-sites@0.1.17-next.2 + - @backstage/plugin-badges@0.2.52-next.2 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-graph@0.3.2-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.2 + - @backstage/plugin-cloudbuild@0.3.28-next.2 + - @backstage/plugin-code-coverage@0.2.21-next.2 + - @backstage/plugin-cost-insights@0.12.17-next.2 + - @backstage/plugin-devtools@0.1.7-next.2 + - @backstage/plugin-dynatrace@8.0.2-next.2 + - @backstage/plugin-entity-feedback@0.2.11-next.2 + - @backstage/plugin-gcalendar@0.3.21-next.2 + - @backstage/plugin-gcp-projects@0.3.44-next.2 + - @backstage/plugin-github-actions@0.6.9-next.2 + - @backstage/plugin-gocd@0.1.34-next.2 + - @backstage/plugin-jenkins@0.9.3-next.2 + - @backstage/plugin-kafka@0.3.28-next.2 + - @backstage/plugin-linguist@0.1.13-next.2 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.10-next.2 + - @backstage/plugin-newrelic@0.3.43-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.2 + - @backstage/plugin-octopus-deploy@0.2.10-next.2 + - @backstage/plugin-org@0.6.18-next.2 + - @backstage/plugin-pagerduty@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.2 + - @backstage/plugin-puppetdb@0.1.11-next.2 + - @backstage/plugin-rollbar@0.4.28-next.2 + - @backstage/plugin-scaffolder@1.16.2-next.2 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-sentry@0.5.13-next.2 + - @backstage/plugin-shortcuts@0.3.17-next.2 + - @backstage/plugin-stackstorm@0.1.9-next.2 + - @backstage/plugin-tech-insights@0.3.20-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.2 + - @backstage/plugin-techdocs-react@1.1.14-next.2 + - @backstage/plugin-todo@0.2.32-next.2 + +## app-next-example-plugin@0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/core-components@0.13.9-next.2 + +## example-backend@0.2.90-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/plugin-auth-backend@0.20.1-next.2 + - @backstage/plugin-lighthouse-backend@0.4.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.17-next.2 + - @backstage/plugin-events-backend@0.2.17-next.2 + - example-app@0.2.90-next.3 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-adr-backend@0.4.5-next.2 + - @backstage/plugin-app-backend@0.3.56-next.2 + - @backstage/plugin-azure-devops-backend@0.5.0-next.2 + - @backstage/plugin-azure-sites-backend@0.1.18-next.2 + - @backstage/plugin-badges-backend@0.3.5-next.2 + - @backstage/plugin-code-coverage-backend@0.2.22-next.2 + - @backstage/plugin-devtools-backend@0.2.5-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.2 + - @backstage/plugin-events-node@0.2.17-next.2 + - @backstage/plugin-explore-backend@0.0.18-next.2 + - @backstage/plugin-jenkins-backend@0.3.2-next.2 + - @backstage/plugin-kafka-backend@0.3.6-next.2 + - @backstage/plugin-kubernetes-backend@0.14.0-next.2 + - @backstage/plugin-linguist-backend@0.5.5-next.2 + - @backstage/plugin-nomad-backend@0.1.10-next.2 + - @backstage/plugin-permission-backend@0.5.31-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-playlist-backend@0.3.12-next.2 + - @backstage/plugin-proxy-backend@0.4.6-next.2 + - @backstage/plugin-rollbar-backend@0.1.53-next.2 + - @backstage/plugin-scaffolder-backend@1.19.2-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.2 + - @backstage/plugin-search-backend@1.4.8-next.2 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-tech-insights-backend@0.5.22-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.2 + - @backstage/plugin-tech-insights-node@0.4.14-next.2 + - @backstage/plugin-techdocs-backend@1.9.1-next.2 + - @backstage/plugin-todo-backend@0.3.6-next.2 + +## example-backend-next@0.0.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/plugin-lighthouse-backend@0.4.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.2 + - @backstage/backend-defaults@0.2.8-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/plugin-adr-backend@0.4.5-next.2 + - @backstage/plugin-app-backend@0.3.56-next.2 + - @backstage/plugin-azure-devops-backend@0.5.0-next.2 + - @backstage/plugin-badges-backend@0.3.5-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.2 + - @backstage/plugin-devtools-backend@0.2.5-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.2 + - @backstage/plugin-jenkins-backend@0.3.2-next.2 + - @backstage/plugin-kubernetes-backend@0.14.0-next.2 + - @backstage/plugin-linguist-backend@0.5.5-next.2 + - @backstage/plugin-nomad-backend@0.1.10-next.2 + - @backstage/plugin-permission-backend@0.5.31-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-playlist-backend@0.3.12-next.2 + - @backstage/plugin-proxy-backend@0.4.6-next.2 + - @backstage/plugin-scaffolder-backend@1.19.2-next.2 + - @backstage/plugin-search-backend@1.4.8-next.2 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-sonarqube-backend@0.2.10-next.2 + - @backstage/plugin-techdocs-backend@1.9.1-next.2 + - @backstage/plugin-todo-backend@0.3.6-next.2 + +## @backstage/backend-plugin-manager@0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/plugin-events-backend@0.2.17-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + +## e2e-test@0.2.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.8-next.3 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## techdocs-cli-embedded-app@0.2.89-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.25.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-techdocs@1.9.2-next.3 + - @backstage/plugin-catalog@1.16.0-next.3 + - @backstage/app-defaults@1.4.6-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/test-utils@1.4.6-next.2 + - @backstage/plugin-techdocs-react@1.1.14-next.2 + +## @internal/plugin-todo-list@1.0.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @internal/plugin-todo-list-backend@1.0.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 diff --git a/docs/releases/v1.21.0-next.4-changelog.md b/docs/releases/v1.21.0-next.4-changelog.md new file mode 100644 index 0000000000..d8ac20508c --- /dev/null +++ b/docs/releases/v1.21.0-next.4-changelog.md @@ -0,0 +1,2935 @@ +# Release v1.21.0-next.4 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.0-next.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.0-next.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.0-next.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.1.0-next.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## @backstage/app-defaults@1.4.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## @backstage/backend-app-api@0.5.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## @backstage/backend-common@0.20.0-next.3 + +### Patch Changes + +- d86a007: Fixed the AwsS3UrlReader host regex and host to allow the S3 reading for CN AWS domain +- Updated dependencies + - @backstage/backend-app-api@0.5.9-next.3 + - @backstage/backend-dev-utils@0.1.2 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0-next.0 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.2.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-app-api@0.5.9-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + +## @backstage/backend-openapi-utils@0.1.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/backend-plugin-api@0.6.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/backend-tasks@0.5.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-app-api@0.5.9-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/cli@0.25.0-next.3 + +### Patch Changes + +- 219d7f0: Updating template generation for scaffolder module +- bd586a5: Updated dependency `bfj` to `^8.0.0`. +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0-next.0 + - @backstage/errors@1.2.3 + - @backstage/eslint-plugin@0.1.4-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/core-compat-api@0.1.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/version-bridge@1.0.7 + +## @backstage/core-components@0.13.9-next.3 + +### Patch Changes + +- 175d86b: Fixed an issue where the `onChange` prop within `HeaderTabs` was triggering twice upon tab-switching. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/create-app@0.5.8-next.4 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## @backstage/dev-utils@1.0.25-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/app-defaults@1.4.6-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/frontend-app-api@0.4.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-plugin-api@0.4.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-test-utils@0.1.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.4.0-next.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/test-utils@1.4.6-next.2 + - @backstage/types@1.1.1 + +## @backstage/repo-tools@0.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/errors@1.2.3 + +## @techdocs/cli@1.8.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.11.0-next.3 + +## @backstage/plugin-adr@0.6.11-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-adr-common@0.2.18-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + +## @backstage/plugin-adr-backend@0.4.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-adr-common@0.2.18-next.1 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-airbrake@0.3.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/dev-utils@1.0.25-next.3 + - @backstage/test-utils@1.4.6-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-airbrake-backend@0.3.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-allure@0.1.44-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-analytics-module-ga@0.1.36-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-analytics-module-ga4@0.1.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-analytics-module-newrelic-browser@0.0.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-apache-airflow@0.2.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-api-docs@0.10.2-next.4 + +### Patch Changes + +- 82fb18b: Updated dependency `@asyncapi/react-component` to `1.2.6`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog@1.16.0-next.4 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-apollo-explorer@0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-app-backend@0.3.56-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.8-next.3 + +## @backstage/plugin-app-node@0.1.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + +## @backstage/plugin-auth-backend@0.20.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.0-next.3 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.2-next.3 + - @backstage/plugin-auth-backend-module-github-provider@0.1.5-next.3 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.5-next.3 + - @backstage/plugin-auth-backend-module-google-provider@0.1.5-next.3 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.5-next.3 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.0-next.2 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.1-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.1.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-auth-node@0.4.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-azure-devops@0.3.10-next.3 + +### Patch Changes + +- c70e4f5: Added multi-org support +- 7c9af0b: Added support for annotations that use a subpath for the host. Also validated that the annotations have the correct number of slashes. +- Updated dependencies + - @backstage/plugin-azure-devops-common@0.3.2-next.1 + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-azure-devops-backend@0.5.0-next.3 + +### Patch Changes + +- c70e4f5: Added multi-org support +- 646db72: Updated encoding of Org to use `encodeURIComponent` when building URL used to get credentials from credential provider +- Updated dependencies + - @backstage/plugin-azure-devops-common@0.3.2-next.1 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-azure-devops-common@0.3.2-next.1 + +### Patch Changes + +- c70e4f5: Added multi-org support + +## @backstage/plugin-azure-sites@0.1.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-azure-sites-common@0.1.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-azure-sites-backend@0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-badges@0.2.52-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-badges-backend@0.3.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-bazaar@0.2.20-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-bazaar-backend@0.3.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-bitrise@0.1.55-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-catalog@1.16.0-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-scaffolder-common@1.4.3 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + +## @backstage/plugin-catalog-backend@1.16.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-openapi-utils@0.1.1-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.3 + +## @backstage/plugin-catalog-backend-module-aws@0.3.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## @backstage/plugin-catalog-backend-module-azure@0.1.27-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-openapi-utils@0.1.1-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.23-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.23-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.21-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.24-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-catalog-backend-module-github@0.4.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-backend-module-github@0.4.6-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.12-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.23-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.15-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-catalog-graph@0.3.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-catalog-import@0.10.4-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-catalog-node@1.6.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## @backstage/plugin-catalog-react@1.9.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-cicd-statistics@0.1.30-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.24-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-cicd-statistics@0.1.30-next.3 + +## @backstage/plugin-circleci@0.3.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-cloudbuild@0.3.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-code-climate@0.1.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-code-coverage@0.2.21-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-code-coverage-backend@0.2.22-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-codescene@0.1.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-config-schema@0.1.48-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-cost-insights@0.12.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-cost-insights-common@0.1.2 + +## @backstage/plugin-devtools@0.1.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.6 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## @backstage/plugin-devtools-backend@0.2.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-devtools-common@0.1.6 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## @backstage/plugin-dynatrace@8.0.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-entity-feedback@0.2.11-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-feedback-backend@0.2.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-validation@0.1.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-events-backend@0.2.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.3 + +## @backstage/plugin-events-backend-module-aws-sqs@0.2.11-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.3 + +## @backstage/plugin-events-backend-module-azure@0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + +## @backstage/plugin-events-backend-module-gerrit@0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + +## @backstage/plugin-events-backend-module-github@0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.3 + +## @backstage/plugin-events-backend-module-gitlab@0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.3 + +## @backstage/plugin-events-backend-test-utils@0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.17-next.3 + +## @backstage/plugin-events-node@0.2.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + +## @backstage/plugin-explore@0.4.14-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-explore-react@0.0.34-next.1 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + +## @backstage/plugin-explore-backend@0.0.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.3 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-firehydrant@0.2.12-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-fossa@0.2.60-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-gcalendar@0.3.21-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-gcp-projects@0.3.44-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-git-release-manager@0.3.40-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-github-actions@0.6.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-github-deployments@0.1.59-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-github-issues@0.2.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-github-pull-requests-board@0.1.22-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-gitops-profiles@0.3.43-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-gocd@0.1.34-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-graphiql@0.3.1-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-graphql-voyager@0.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-home@0.6.0-next.3 + +### Patch Changes + +- 2633d64: Change user settings backend plugin id and fix when using user setting backend home page first will cause edit page loop render +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- 54cef27: StarredEntities component calls `getEntitiesByRefs` instead of `getEntities` to improve performance since we have the `entityRefs` +- c8908d4: Use new option from RJSF 5.15 +- Updated dependencies + - @backstage/plugin-home-react@0.1.6-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-home-react@0.1.6-next.3 + +### Patch Changes + +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + +## @backstage/plugin-ilert@0.2.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-jenkins@0.9.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-jenkins-common@0.1.21 + +## @backstage/plugin-jenkins-backend@0.3.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-jenkins-common@0.1.21 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## @backstage/plugin-kafka@0.3.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-kafka-backend@0.3.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-kubernetes@0.11.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/plugin-kubernetes-react@0.2.0-next.3 + +## @backstage/plugin-kubernetes-backend@0.14.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/plugin-kubernetes-node@0.1.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## @backstage/plugin-kubernetes-cluster@0.0.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/plugin-kubernetes-react@0.2.0-next.3 + +## @backstage/plugin-kubernetes-node@0.1.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## @backstage/plugin-kubernetes-react@0.2.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## @backstage/plugin-lighthouse@0.4.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-lighthouse-backend@0.4.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-linguist@0.1.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-linguist-backend@0.5.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-microsoft-calendar@0.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-newrelic@0.3.43-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-newrelic-dashboard@0.3.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-nomad@0.1.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-nomad-backend@0.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-octopus-deploy@0.2.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-opencost@0.2.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-org@0.6.18-next.3 + +### Patch Changes + +- 59c24b9: Fix issue where members inside of `` would be rendered as squished when the card itself was shrunk down. +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-org-react@0.1.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-pagerduty@0.7.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home-react@0.1.6-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-periskop@0.1.26-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-periskop-backend@0.2.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-permission-backend@0.5.31-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## @backstage/plugin-permission-node@0.7.19-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + +## @backstage/plugin-playlist@0.2.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist-common@0.1.12 + - @backstage/plugin-search-react@1.7.4-next.3 + +## @backstage/plugin-playlist-backend@0.3.12-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-playlist-common@0.1.12 + +## @backstage/plugin-proxy-backend@0.4.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-puppetdb@0.1.11-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-rollbar@0.4.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-rollbar-backend@0.1.53-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-scaffolder@1.16.2-next.3 + +### Patch Changes + +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- c8908d4: Use new option from RJSF 5.15 +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.6.2-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-scaffolder-backend@1.19.2-next.3 + +### Patch Changes + +- 219d7f0: Refactor some methods to `-node` instead and use the new external modules +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.11-next.3 + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.0-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.1.0-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.0-next.0 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.32-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.2.11-next.3 + +### Patch Changes + +- 219d7f0: Extract some more actions to this library +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.16-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.29-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node@0.2.9-next.3 + +### Patch Changes + +- 219d7f0: Refactor some methods to `-node` instead and use the new external modules +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-scaffolder-react@1.6.2-next.3 + +### Patch Changes + +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- c8908d4: Use new option from RJSF 5.15 +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-scaffolder-common@1.4.3 + +## @backstage/plugin-search@1.4.4-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + +## @backstage/plugin-search-backend@1.4.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-openapi-utils@0.1.1-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-catalog@0.1.12-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-explore@0.1.12-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-pg@0.5.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-backend-module-techdocs@0.1.12-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-techdocs-node@1.11.0-next.3 + +## @backstage/plugin-search-backend-node@1.2.12-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-search-react@1.7.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-sentry@0.5.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-shortcuts@0.3.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-sonarqube@0.7.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-sonarqube-react@0.1.11-next.1 + +## @backstage/plugin-sonarqube-backend@0.2.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-splunk-on-call@0.4.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-stack-overflow@0.1.23-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home-react@0.1.6-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + +## @backstage/plugin-stack-overflow-backend@0.2.12-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.1-next.3 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-stackstorm@0.1.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-tech-insights@0.3.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend@0.5.22-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + - @backstage/plugin-tech-insights-node@0.4.14-next.3 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-tech-insights-common@0.2.12 + - @backstage/plugin-tech-insights-node@0.4.14-next.3 + +## @backstage/plugin-tech-insights-node@0.4.14-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-radar@0.6.11-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + +## @backstage/plugin-techdocs@1.9.2-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + - @backstage/plugin-techdocs-react@1.1.14-next.3 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.25-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/test-utils@1.4.6-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog@1.16.0-next.4 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-search-react@1.7.4-next.3 + - @backstage/plugin-techdocs@1.9.2-next.4 + - @backstage/plugin-techdocs-react@1.1.14-next.3 + +## @backstage/plugin-techdocs-backend@1.9.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.3 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-techdocs-node@1.11.0-next.3 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.3 + +## @backstage/plugin-techdocs-node@1.11.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-common@1.2.8 + +## @backstage/plugin-techdocs-react@1.1.14-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/plugin-todo@0.2.32-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-todo-backend@0.3.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-openapi-utils@0.1.1-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## @backstage/plugin-user-settings@0.7.14-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-user-settings-backend@0.2.7-next.3 + +### Patch Changes + +- 2633d64: Change user settings backend plugin id and fix when using user setting backend home page first will cause edit page loop render +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## @backstage/plugin-vault@0.1.23-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## @backstage/plugin-vault-backend@0.4.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-vault-node@0.1.1-next.3 + +## @backstage/plugin-vault-node@0.1.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + +## @backstage/plugin-xcmetrics@0.2.46-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + +## example-app@0.2.90-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.6.0-next.3 + - @backstage/plugin-org@0.6.18-next.3 + - @backstage/plugin-azure-devops@0.3.10-next.3 + - @backstage/cli@0.25.0-next.3 + - @backstage/plugin-api-docs@0.10.2-next.4 + - @backstage/plugin-scaffolder-react@1.6.2-next.3 + - @backstage/plugin-scaffolder@1.16.2-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/app-defaults@1.4.6-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-app-api@0.4.0-next.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-adr@0.6.11-next.3 + - @backstage/plugin-airbrake@0.3.28-next.3 + - @backstage/plugin-apache-airflow@0.2.18-next.3 + - @backstage/plugin-azure-sites@0.1.17-next.3 + - @backstage/plugin-badges@0.2.52-next.3 + - @backstage/plugin-catalog@1.16.0-next.4 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-graph@0.3.2-next.3 + - @backstage/plugin-catalog-import@0.10.4-next.4 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.3 + - @backstage/plugin-cloudbuild@0.3.28-next.3 + - @backstage/plugin-code-coverage@0.2.21-next.3 + - @backstage/plugin-cost-insights@0.12.17-next.3 + - @backstage/plugin-devtools@0.1.7-next.3 + - @backstage/plugin-dynatrace@8.0.2-next.3 + - @backstage/plugin-entity-feedback@0.2.11-next.3 + - @backstage/plugin-explore@0.4.14-next.3 + - @backstage/plugin-gcalendar@0.3.21-next.3 + - @backstage/plugin-gcp-projects@0.3.44-next.3 + - @backstage/plugin-github-actions@0.6.9-next.3 + - @backstage/plugin-gocd@0.1.34-next.3 + - @backstage/plugin-graphiql@0.3.1-next.4 + - @backstage/plugin-jenkins@0.9.3-next.3 + - @backstage/plugin-kafka@0.3.28-next.3 + - @backstage/plugin-kubernetes@0.11.3-next.3 + - @backstage/plugin-kubernetes-cluster@0.0.4-next.3 + - @backstage/plugin-lighthouse@0.4.13-next.3 + - @backstage/plugin-linguist@0.1.13-next.3 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.10-next.3 + - @backstage/plugin-newrelic@0.3.43-next.3 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.3 + - @backstage/plugin-nomad@0.1.9-next.3 + - @backstage/plugin-octopus-deploy@0.2.10-next.3 + - @backstage/plugin-pagerduty@0.7.0-next.3 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.3 + - @backstage/plugin-puppetdb@0.1.11-next.3 + - @backstage/plugin-rollbar@0.4.28-next.3 + - @backstage/plugin-search@1.4.4-next.4 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + - @backstage/plugin-sentry@0.5.13-next.3 + - @backstage/plugin-shortcuts@0.3.17-next.3 + - @backstage/plugin-stack-overflow@0.1.23-next.3 + - @backstage/plugin-stackstorm@0.1.9-next.3 + - @backstage/plugin-tech-insights@0.3.20-next.3 + - @backstage/plugin-tech-radar@0.6.11-next.4 + - @backstage/plugin-techdocs@1.9.2-next.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.3 + - @backstage/plugin-techdocs-react@1.1.14-next.3 + - @backstage/plugin-todo@0.2.32-next.3 + - @backstage/plugin-user-settings@0.7.14-next.4 + +## example-app-next@0.0.4-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.6.0-next.3 + - @backstage/plugin-org@0.6.18-next.3 + - @backstage/plugin-azure-devops@0.3.10-next.3 + - @backstage/cli@0.25.0-next.3 + - @backstage/plugin-api-docs@0.10.2-next.4 + - @backstage/plugin-scaffolder-react@1.6.2-next.3 + - @backstage/plugin-scaffolder@1.16.2-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/app-defaults@1.4.6-next.3 + - app-next-example-plugin@0.0.4-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-app-api@0.4.0-next.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-adr@0.6.11-next.3 + - @backstage/plugin-airbrake@0.3.28-next.3 + - @backstage/plugin-apache-airflow@0.2.18-next.3 + - @backstage/plugin-azure-sites@0.1.17-next.3 + - @backstage/plugin-badges@0.2.52-next.3 + - @backstage/plugin-catalog@1.16.0-next.4 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-graph@0.3.2-next.3 + - @backstage/plugin-catalog-import@0.10.4-next.4 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.3 + - @backstage/plugin-cloudbuild@0.3.28-next.3 + - @backstage/plugin-code-coverage@0.2.21-next.3 + - @backstage/plugin-cost-insights@0.12.17-next.3 + - @backstage/plugin-devtools@0.1.7-next.3 + - @backstage/plugin-dynatrace@8.0.2-next.3 + - @backstage/plugin-entity-feedback@0.2.11-next.3 + - @backstage/plugin-explore@0.4.14-next.3 + - @backstage/plugin-gcalendar@0.3.21-next.3 + - @backstage/plugin-gcp-projects@0.3.44-next.3 + - @backstage/plugin-github-actions@0.6.9-next.3 + - @backstage/plugin-gocd@0.1.34-next.3 + - @backstage/plugin-graphiql@0.3.1-next.4 + - @backstage/plugin-jenkins@0.9.3-next.3 + - @backstage/plugin-kafka@0.3.28-next.3 + - @backstage/plugin-kubernetes@0.11.3-next.3 + - @backstage/plugin-lighthouse@0.4.13-next.3 + - @backstage/plugin-linguist@0.1.13-next.3 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.10-next.3 + - @backstage/plugin-newrelic@0.3.43-next.3 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.3 + - @backstage/plugin-octopus-deploy@0.2.10-next.3 + - @backstage/plugin-pagerduty@0.7.0-next.3 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.3 + - @backstage/plugin-puppetdb@0.1.11-next.3 + - @backstage/plugin-rollbar@0.4.28-next.3 + - @backstage/plugin-search@1.4.4-next.4 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + - @backstage/plugin-sentry@0.5.13-next.3 + - @backstage/plugin-shortcuts@0.3.17-next.3 + - @backstage/plugin-stackstorm@0.1.9-next.3 + - @backstage/plugin-tech-insights@0.3.20-next.3 + - @backstage/plugin-tech-radar@0.6.11-next.4 + - @backstage/plugin-techdocs@1.9.2-next.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.3 + - @backstage/plugin-techdocs-react@1.1.14-next.3 + - @backstage/plugin-todo@0.2.32-next.3 + - @backstage/plugin-user-settings@0.7.14-next.4 + +## app-next-example-plugin@0.0.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + +## example-backend@0.2.90-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops-backend@0.5.0-next.3 + - @backstage/plugin-scaffolder-backend@1.19.2-next.3 + - @backstage/backend-common@0.20.0-next.3 + - example-app@0.2.90-next.4 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-adr-backend@0.4.5-next.3 + - @backstage/plugin-app-backend@0.3.56-next.3 + - @backstage/plugin-auth-backend@0.20.1-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-azure-sites-backend@0.1.18-next.3 + - @backstage/plugin-badges-backend@0.3.5-next.3 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-code-coverage-backend@0.2.22-next.3 + - @backstage/plugin-devtools-backend@0.2.5-next.3 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.3 + - @backstage/plugin-events-backend@0.2.17-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + - @backstage/plugin-explore-backend@0.0.18-next.3 + - @backstage/plugin-jenkins-backend@0.3.2-next.3 + - @backstage/plugin-kafka-backend@0.3.6-next.3 + - @backstage/plugin-kubernetes-backend@0.14.0-next.3 + - @backstage/plugin-lighthouse-backend@0.4.0-next.3 + - @backstage/plugin-linguist-backend@0.5.5-next.3 + - @backstage/plugin-nomad-backend@0.1.10-next.3 + - @backstage/plugin-permission-backend@0.5.31-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-playlist-backend@0.3.12-next.3 + - @backstage/plugin-proxy-backend@0.4.6-next.3 + - @backstage/plugin-rollbar-backend@0.1.53-next.3 + - @backstage/plugin-search-backend@1.4.8-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.3 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.3 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.3 + - @backstage/plugin-search-backend-module-pg@0.5.17-next.3 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.3 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-tech-insights-backend@0.5.22-next.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.3 + - @backstage/plugin-tech-insights-node@0.4.14-next.3 + - @backstage/plugin-techdocs-backend@1.9.1-next.3 + - @backstage/plugin-todo-backend@0.3.6-next.3 + +## example-backend-next@0.0.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops-backend@0.5.0-next.3 + - @backstage/plugin-scaffolder-backend@1.19.2-next.3 + - @backstage/backend-defaults@0.2.8-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/plugin-adr-backend@0.4.5-next.3 + - @backstage/plugin-app-backend@0.3.56-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-badges-backend@0.3.5-next.3 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.3 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.3 + - @backstage/plugin-devtools-backend@0.2.5-next.3 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.3 + - @backstage/plugin-jenkins-backend@0.3.2-next.3 + - @backstage/plugin-kubernetes-backend@0.14.0-next.3 + - @backstage/plugin-lighthouse-backend@0.4.0-next.3 + - @backstage/plugin-linguist-backend@0.5.5-next.3 + - @backstage/plugin-nomad-backend@0.1.10-next.3 + - @backstage/plugin-permission-backend@0.5.31-next.3 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-playlist-backend@0.3.12-next.3 + - @backstage/plugin-proxy-backend@0.4.6-next.3 + - @backstage/plugin-search-backend@1.4.8-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.3 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.3 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.3 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-sonarqube-backend@0.2.10-next.3 + - @backstage/plugin-techdocs-backend@1.9.1-next.3 + - @backstage/plugin-todo-backend@0.3.6-next.3 + +## @backstage/backend-plugin-manager@0.0.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-events-backend@0.2.17-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + +## e2e-test@0.2.10-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.8-next.4 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## techdocs-cli-embedded-app@0.2.89-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.25.0-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/app-defaults@1.4.6-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/test-utils@1.4.6-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog@1.16.0-next.4 + - @backstage/plugin-techdocs@1.9.2-next.4 + - @backstage/plugin-techdocs-react@1.1.14-next.3 + +## @internal/plugin-todo-list@1.0.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## @internal/plugin-todo-list-backend@1.0.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 diff --git a/docs/releases/v1.21.0.md b/docs/releases/v1.21.0.md new file mode 100644 index 0000000000..53e77f0021 --- /dev/null +++ b/docs/releases/v1.21.0.md @@ -0,0 +1,135 @@ +--- +id: v1.21.0 +title: v1.21.0 +description: Backstage Release v1.21.0 +--- + +These are the release notes for the v1.21.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### New Frontend System Alpha + +This release marks the alpha release of the new frontend system, which has been in an experimental state since implementation began in the middle of 2023. This new system brings declarative integration of plugins, which is the ability to integrate new features into a Backstage app without writing any TypeScript code. Through this capability it also paves the way for supporting dynamic plugin installation at runtime. + +The alpha release is a point of increased stability following the earlier experimental phase. There is now a complete system that lets you build out a full application, supported by [documentation](https://backstage.io/docs/frontend-system/). From this point on any breaking changes will also be clearly marked in the changelog. + +There is still a long road ahead to a stable release, and this is not the time to migrate existing applications. There are only a few plugins that support this system so far, and if you want to add to a plugin that you own, please do so under an `/alpha` sub-path export. + +Still, we encourage you to explore this new system to see whether you are confident in this path forward. If you have feedback or want to know more you can reach out in the #declarative-integration channel on Discord, or join an [Adoption SIG](https://github.com/backstage/community/blob/main/sigs/sig-adoption/README.md) meeting where the new system is frequently discussed. You can also check out our [maintainer talk at KubeCon NA 2023](https://youtu.be/ONMBYnhxnNU?t=436), where we talk about this new system and show a couple of demos. + +### React Router Beta deprecation + +This release of Backstage officially deprecates, but does _not_ immediately remove, support for old beta versions of [`react-router` 6](https://reactrouter.com/). Actual support for beta versions will be removed entirely in an upcoming release of Backstage. Please upgrade your own Backstage project as soon as possible to a stable version of `react-router`, by [following this guide](https://backstage.io/docs/tutorials/react-router-stable-migration/). + +### New PostgreSQL versioning policy + +The Backstage project has now settled on [a clearer policy](https://backstage.io/docs/overview/versioning-policy/#postgresql-releases) for what versions of PostgreSQL that it supports. In short, we support the last five [released major versions](https://www.postgresql.org/support/versioning/), and actively test against the first and last of those five, in a rolling window over time. + +As part of this, the `TestDatabases` utility class now supports all of the last major versions of PostgreSQL in addition to the ones it supported before. You can also call `TestDatabases.setDefaults` inside your `setupTests.ts` file to configure the set of engines to test against, instead of enumerating them in every individual test. + +Contributed by [@awanlin](https://github.com/awanlin) in [#21510](https://github.com/backstage/backstage/pull/21510) + +### `UnifiedTheme` Now Supports Overrides + +You can now supply overrides for Backstage components when using `createUnifiedTheme`. We've updated the demo site’s Aperture theme to work with this and you can see the code for that [here](https://github.com/backstage/demo/blob/402cbb358cddacd59b339580bef0a4c5c2c7e013/packages/app/src/theme/aperture.ts#L85). + +If you are switching from the old way of defining a theme to `createUnifiedTheme`, note that it uses the MUI v5 overrides format. The style overrides are now nested in a `styleOverrides` key, and if you want access to the theme you’ll need to use a callback: + +```ts +BackstageHeaderTabs: { + styleOverrides: { + defaultTab: { + textTransform: 'none', + }, + }, +}, +MuiChip: { + styleOverrides: { + root: ({ theme }) => ({ + color: theme.palette.primary.dark, + }), + }, +}, +``` + +### Catalog pagination + +`CatalogIndexPage` now offers an optional pagination feature, designed to accommodate adopters managing extensive catalogs. This new capability allows for better handling of large amounts of data. + +To activate the pagination mode, simply update your `App.tsx` as follows: + +```diff + const routes = ( + + ... +- } /> ++ } /> + ... +``` + +In case you have a custom catalog page and you want to enable pagination, you need to pass the `pagination` prop to `EntityListProvider` instead. For now both column sorting and search filtering are still done locally, meaning they only apply to each individual page. This is something we will improve in the future and we still wanted to make this feature available early as it can greatly improve the performance of the catalog page. + +### Azure DevOps Multi-Org Support + +The Azure DevOps plugin now has multi-org support and there is a new processor to help with adding the needed annotations. Contributed by [@awanlin](https://github.com/awanlin) in [#19622](https://github.com/backstage/backstage/issues/19622) + +### New Authentication providers + +A new Atlassian authentication provider has been added to `@backstage/plugin-auth-backend`. Contributed by [@handsamtw](https://github.com/handsamtw) in [#21007](https://github.com/backstage/backstage/pull/21007) + +A new VMware Cloud authentication provider has been added to `@backstage/plugin-auth-backend`. Contributed by [@luchillo17](https://github.com/luchillo17) in [#21337](https://github.com/backstage/backstage/pull/21337) + +### Kubernetes single cluster selection + +You can now select a `single` kubernetes cluster that the entity is part of from all your defined kubernetes clusters, by providing the `backstage.io/kubernetes-cluster` annotation with the defined cluster name. + +If you do not specify the annotation then by default it fetches all defined kubernetes clusters. + +To apply, update your `catalog-info.yaml`as follows: + +```diff + metadata: + annotations: + 'backstage.io/kubernetes-id': dice-roller + 'backstage.io/kubernetes-namespace': dice-space ++ 'backstage.io/kubernetes-cluster': dice-cluster + 'backstage.io/kubernetes-label-selector': 'app=my-app,component=front-end' +``` + +Contributed by [@deepan10](https://github.com/deepan10) in [#20954](https://github.com/backstage/backstage/pull/20954) + +### BREAKING: Repo tools generated API Reports path changes + +API Reports generated for sub-path exports now place the name as a suffix rather than prefix, for example `api-report-alpha.md` instead of `alpha-api-report.md`. When upgrading to this version you'll need to re-create any such API reports and delete the old ones. + +### PagerDuty plugin changes home 🏡 + +The [PagerDuty](https://www.pagerduty.com/) plugin has been marked as deprecated in favor of [pagerduty/backstage-plugin](https://github.com/pagerduty/backstage-plugin) which is maintained by PagerDuty themselves! We encourage you to [migrate to @pagerduty/backstage-plugin](https://pagerduty.github.io/backstage-plugin-docs/migration/) in order to receive future updates. + +Congrats to the PagerDuty folks for taking ownership of the plugin 👏 + +Contributed by [@t1agob](https://github.com/t1agob) in [#21436](https://github.com/backstage/backstage/pull/21436) + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.21.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.22.0-changelog.md b/docs/releases/v1.22.0-changelog.md new file mode 100644 index 0000000000..399d7e7016 --- /dev/null +++ b/docs/releases/v1.22.0-changelog.md @@ -0,0 +1,3239 @@ +# Release v1.22.0 + +## @backstage/backend-dynamic-feature-service@0.1.0 + +### Minor Changes + +- eb81f42: New `backend-dynamic-feature-service` package, for the discovery of dynamic frontend and backend plugins (and modules) and the loading of the backend ones inside the backend application. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/cli-node@0.2.2 + - @backstage/plugin-events-backend@0.2.18 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-catalog-backend@1.16.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.18 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/frontend-app-api@0.5.0 + +### Minor Changes + +- d4149bf: **BREAKING**: Renamed the `app/router` extension to `app/root`. +- 074dfe3: Attaching extensions to an input that does not exist is now a warning rather than an error. + +### Patch Changes + +- 7d63b32: Accepts sub route refs on the new `createPlugin` routes map. +- 516fd3e: Updated README to reflect release status +- c97fa1c: Added `elements`, `wrappers`, and `router` inputs to `app/root`, that let you add things to the root of the React tree above the layout. You can use the `createAppRootElementExtension`, `createAppRootWrapperExtension`, and `createRouterExtension` extension creator, respectively, to conveniently create such extensions. These are all optional, and if you do not supply a router a default one will be used (`BrowserRouter` in regular runs, `MemoryRouter` in tests/CI). +- 5fe6600: add oauth dialog and alert display to the root elements +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-plugin-api@0.5.0 + +### Minor Changes + +- d4149bf: **BREAKING**: Renamed the `app/router` extension to `app/root`. + +### Patch Changes + +- b2d370e: Exposed `createComponentRef`, and ensured that produced refs and feature bits have a `toString` for easier debugging +- 7d63b32: Accepts sub route refs on the new `createPlugin` routes map. +- 516fd3e: Updated README to reflect release status +- 4016f21: Remove some unused dependencies +- c97fa1c: Added `elements`, `wrappers`, and `router` inputs to `app/root`, that let you add things to the root of the React tree above the layout. You can use the `createAppRootElementExtension`, `createAppRootWrapperExtension`, and `createRouterExtension` extension creator, respectively, to conveniently create such extensions. These are all optional, and if you do not supply a router a default one will be used (`BrowserRouter` in regular runs, `MemoryRouter` in tests/CI). +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/plugin-app-visualizer@0.1.0 + +### Minor Changes + +- e57cc9f: Initial release of the app visualizer plugin. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-scaffolder-backend@1.20.0 + +### Minor Changes + +- a694f71: The Scaffolder builtin actions now contains an action for running pipelines from Bitbucket Cloud Rest API +- 7c522c5: Add `gitlab:repo:push` scaffolder action to push files to arbitrary branch without creating a Merge Request + +### Patch Changes + +- e9ab1c4: Fixed an issue where not passing a `value` to any of the action's permission conditions caused an error. +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-backend-module-github@0.1.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.12 + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-user-settings@0.8.0 + +### Minor Changes + +- 56b2fb0: Updated the user settings selector to use a select component that displays native language names instead of language codes if possible. + +### Patch Changes + +- eea0849: add user-settings declarative integration core nav item +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/core-app-api@1.11.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + +## @backstage/app-defaults@1.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + +## @backstage/backend-app-api@0.5.10 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config-loader@1.6.1 + - @backstage/cli-node@0.2.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/backend-common@0.20.1 + +### Patch Changes + +- 3b24eae: Adding support for removing file from git index + +- 454d17c: Do not call fetch directly but rather use `fetchResponse` facility + +- b6b15b2: Use sha256 instead of md5 for hash key calculation in caches + + This can have a side effect of invalidating caches (when cache key was >250 characters) + This improves compliance with FIPS nodejs + +- Updated dependencies + - @backstage/config-loader@1.6.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-dev-utils@0.1.3 + - @backstage/backend-app-api@0.5.10 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.2.9 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-app-api@0.5.10 + +## @backstage/backend-dev-utils@0.1.3 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status + +## @backstage/backend-openapi-utils@0.1.2 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/errors@1.2.3 + +## @backstage/backend-plugin-api@0.6.9 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## @backstage/backend-tasks@0.5.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-app-api@0.5.10 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/catalog-client@1.5.2 + +### Patch Changes + +- 883782e: Fix a bug in `getLocationByRef` that led to invalid backend calls +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/cli@0.25.1 + +### Patch Changes + +- b6b15b2: Use sha256 instead of md5 in build script cache key calculation + + Makes it possible to build on FIPS nodejs. + +- Updated dependencies + - @backstage/config-loader@1.6.1 + - @backstage/cli-node@0.2.2 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/eslint-plugin@0.1.4 + - @backstage/integration@1.8.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/cli-node@0.2.2 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/config-loader@1.6.1 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/core-app-api@1.11.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/core-compat-api@0.1.1 + +### Patch Changes + +- 4c1f50c: Make `convertLegacyApp` wrap discovered routes with `compatWrapper`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-plugin-api@1.8.2 + - @backstage/core-app-api@1.11.3 + - @backstage/version-bridge@1.0.7 + +## @backstage/core-components@0.13.10 + +### Patch Changes + +- d625f66: Fixed bug in Link where it was possible to select and copy a hidden element into clipboard +- 6878b1d: Removed unnecessary `history` and `immer` dependencies. +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/version-bridge@1.0.7 + +## @backstage/core-plugin-api@1.8.2 + +### Patch Changes + +- 6878b1d: Removed unnecessary `i18next` dependency. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/create-app@0.5.9 + +### Patch Changes + +- c9f71fb: Bumped create-app version. +- ac277f3: Bumped create-app version. +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## @backstage/dev-utils@1.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/app-defaults@1.4.7 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + +## @backstage/frontend-test-utils@0.1.1 + +### Patch Changes + +- f7566f9: Updates to reflect the `app/router` extension having been renamed to `app/root`. +- 516fd3e: Updated README to reflect release status +- c97fa1c: Added `elements`, `wrappers`, and `router` inputs to `app/root`, that let you add things to the root of the React tree above the layout. You can use the `createAppRootElementExtension`, `createAppRootWrapperExtension`, and `createRouterExtension` extension creator, respectively, to conveniently create such extensions. These are all optional, and if you do not supply a router a default one will be used (`BrowserRouter` in regular runs, `MemoryRouter` in tests/CI). +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/frontend-app-api@0.5.0 + - @backstage/test-utils@1.4.7 + - @backstage/types@1.1.1 + +## @backstage/integration-react@1.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + +## @backstage/repo-tools@0.5.2 + +### Patch Changes + +- 883782e: Updated the OpenAPI template to export the `TypedResponse` interface so that client code can leverage it +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/cli-node@0.2.2 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## @techdocs/cli@1.8.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-techdocs-node@1.11.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## @backstage/test-utils@1.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-adr@0.6.12 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-search-react@1.7.5 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-adr-common@0.2.19 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-adr-backend@0.4.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-adr-common@0.2.19 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-adr-common@0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-airbrake@0.3.29 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/dev-utils@1.0.26 + - @backstage/catalog-model@1.4.3 + - @backstage/test-utils@1.4.7 + +## @backstage/plugin-airbrake-backend@0.3.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + +## @backstage/plugin-allure@0.1.45 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-analytics-module-ga@0.1.37 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + +## @backstage/plugin-analytics-module-ga4@0.1.8 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + +## @backstage/plugin-analytics-module-newrelic-browser@0.0.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + +## @backstage/plugin-apache-airflow@0.2.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-api-docs@0.10.3 + +### Patch Changes + +- 8a69cc9: Fix custom http resolvers for AsyncAPI widget. +- 062b8f2: Add permission check to Register Existing API button +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-catalog@1.16.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-apollo-explorer@0.1.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-app-backend@0.3.57 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config-loader@1.6.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.9 + +## @backstage/plugin-app-node@0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + +## @backstage/plugin-auth-backend@0.20.3 + +### Patch Changes + +- 004499c: Fixed an issue where some Okta's resolvers were missing +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.3 + - @backstage/plugin-auth-backend-module-github-provider@0.1.6 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.6 + - @backstage/plugin-auth-backend-module-google-provider@0.1.6 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.6 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.2 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.1.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.4 + +### Patch Changes + +- 928efbc: Deprecated the `authModuleMicrosoftProvider` export. A default export is now available and should be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-microsoft-provider'));` +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.2 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.3 + +### Patch Changes + +- 928efbc: Deprecated the `authModulePinnipedProvider` export. A default export is now available and should be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-pinniped-provider'));` +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-auth-node@0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-azure-devops@0.3.11 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-azure-devops-common@0.3.2 + +## @backstage/plugin-azure-devops-backend@0.5.1 + +### Patch Changes + +- d076ee4: Updated dependency `azure-devops-node-api` to `^12.0.0`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-azure-devops-common@0.3.2 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-azure-sites@0.1.18 + +### Patch Changes + +- a31f688: Show Azure site tags in `EntityAzureSitesOverviewWidget`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-azure-sites-backend@0.1.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-badges@0.2.53 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-badges-backend@0.3.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bazaar@0.2.21 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bazaar-backend@0.3.7 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-bitrise@0.1.56 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-catalog@1.16.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-search-react@1.7.5 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-catalog-backend@1.16.1 + +### Patch Changes + +- c3249d6: Parse the URL using a different method rather than `git-url-parse` to support wildcards for URLs which are not VCS providers +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-search-backend-module-catalog@0.1.13 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-openapi-utils@0.1.2 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-events-node@0.2.18 + +## @backstage/plugin-catalog-backend-module-aws@0.3.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- 22e88d0: Added status and e-mail as labels to the AWS Account Resource +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-backend-module-azure@0.1.28 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-openapi-utils@0.1.2 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.24 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-events-node@0.2.18 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.25 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-catalog-backend-module-github@0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-catalog-backend@1.16.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-events-node@0.2.18 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-catalog-backend-module-github@0.4.7 + - @backstage/config@1.1.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-catalog-backend@1.16.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-events-node@0.2.18 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.24 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.26 + +### Patch Changes + +- 4ebf99b: Add support for the new backend system. + + A new backend module for the catalog backend + was added and exported as `default`. + + You can use it with the new backend system like + + ```ts title="packages/backend/src/index.ts" + backend.add(import('@backstage/plugin-catalog-backend-module-openapi')); + ``` + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-catalog-backend@1.16.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-catalog-common@1.0.20 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-catalog-graph@0.3.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.10.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-node@1.6.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-react@1.9.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-unprocessed-entities@0.1.7 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-cicd-statistics@0.1.31 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.25 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-cicd-statistics@0.1.31 + +## @backstage/plugin-circleci@0.3.29 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-cloudbuild@0.3.29 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-code-climate@0.1.29 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-code-coverage@0.2.22 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-code-coverage-backend@0.2.23 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-codescene@0.1.21 + +### Patch Changes + +- d5eda61: Updated Readme document in codescene plugin +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-config-schema@0.1.49 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-cost-insights@0.12.18 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0 + - @backstage/plugin-cost-insights-common@0.1.2 + +## @backstage/plugin-devtools@0.1.8 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/errors@1.2.3 + - @backstage/plugin-devtools-common@0.1.8 + +## @backstage/plugin-devtools-backend@0.2.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config-loader@1.6.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.8 + +## @backstage/plugin-devtools-common@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/types@1.1.1 + +## @backstage/plugin-dynatrace@8.0.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-entity-feedback@0.2.12 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-feedback-backend@0.2.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-validation@0.1.14 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-events-backend@0.2.18 + +### Patch Changes + +- 92ea615: Update `README.md` +- d5ddc4e: Add documentation on how to install the plugins with the new backend system. +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.18 + +## @backstage/plugin-events-backend-module-aws-sqs@0.2.12 + +### Patch Changes + +- 7b8e551: Fix errors when deleting SQS messages: + + - If zero messages were received, skip deletion to avoid `EmptyBatchRequest` error from the SQS client. + - If zero failures were returned from the SQS client during deletion, skip error logging. + +- d5ddc4e: Add documentation on how to install the plugins with the new backend system. + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-tasks@0.5.14 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.18 + +## @backstage/plugin-events-backend-module-azure@0.1.19 + +### Patch Changes + +- af76a95: Add default exports for the new backend system and documentation. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-events-node@0.2.18 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.1.19 + +### Patch Changes + +- af76a95: Add default exports for the new backend system and documentation. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-events-node@0.2.18 + +## @backstage/plugin-events-backend-module-gerrit@0.1.19 + +### Patch Changes + +- af76a95: Add default exports for the new backend system and documentation. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-events-node@0.2.18 + +## @backstage/plugin-events-backend-module-github@0.1.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.18 + +## @backstage/plugin-events-backend-module-gitlab@0.1.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.18 + +## @backstage/plugin-events-backend-test-utils@0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.18 + +## @backstage/plugin-events-node@0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + +## @backstage/plugin-explore@0.4.15 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-explore-react@0.0.35 + - @backstage/plugin-search-react@1.7.5 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-explore-backend@0.0.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-search-backend-module-explore@0.1.13 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + +## @backstage/plugin-explore-react@0.0.35 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-explore-common@0.0.2 + +## @backstage/plugin-firehydrant@0.2.13 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-fossa@0.2.61 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-gcalendar@0.3.22 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-gcp-projects@0.3.45 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-git-release-manager@0.3.41 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration@1.8.0 + +## @backstage/plugin-github-actions@0.6.10 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-github-deployments@0.1.60 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-github-issues@0.2.18 + +### Patch Changes + +- bf92ae3: Updated dependency `octokit` to `^3.0.0`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-github-pull-requests-board@0.1.23 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-gitops-profiles@0.3.44 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-gocd@0.1.35 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-graphiql@0.3.2 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-graphql-voyager@0.1.11 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-home@0.6.1 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-home-react@0.1.7 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + +## @backstage/plugin-home-react@0.1.7 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-ilert@0.2.18 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-jenkins@0.9.4 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.23 + +## @backstage/plugin-jenkins-backend@0.3.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.23 + +## @backstage/plugin-jenkins-common@0.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-kafka@0.3.29 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-kafka-backend@0.3.7 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-kubernetes@0.11.4 + +### Patch Changes + +- d5d2c67: Add `authuser` search parameter to GKE cluster link formatter in k8s plugin + + Thanks to this, people with multiple simultaneously logged-in accounts in their GCP console will automatically view objects with the same email as the one signed in to the Google auth provider in Backstage. + +- 4016f21: Remove some unused dependencies + +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-kubernetes-react@0.2.1 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-kubernetes-backend@0.14.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-node@0.1.3 + +## @backstage/plugin-kubernetes-cluster@0.0.5 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-kubernetes-react@0.2.1 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-kubernetes-common@0.7.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-node@0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-react@0.2.1 + +### Patch Changes + +- d5d2c67: Add `authuser` search parameter to GKE cluster link formatter in k8s plugin + + Thanks to this, people with multiple simultaneously logged-in accounts in their GCP console will automatically view objects with the same email as the one signed in to the Google auth provider in Backstage. + +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-lighthouse@0.4.14 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-lighthouse-backend@0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-linguist@0.1.14 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- 4f42918: Added alpha support for the New Frontend System (Declarative Integration) +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-linguist-backend@0.5.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-microsoft-calendar@0.1.11 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-newrelic@0.3.44 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-newrelic-dashboard@0.3.4 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-nomad@0.1.10 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-nomad-backend@0.1.11 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-octopus-deploy@0.2.11 + +### Patch Changes + +- 7d96ba8: added install path and fixed import on plugin-octopus-deploy +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-opencost@0.2.4 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-org@0.6.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-org-react@0.1.18 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-pagerduty@0.7.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-home-react@0.1.7 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-periskop@0.1.27 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-periskop-backend@0.2.7 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + +## @backstage/plugin-permission-backend@0.5.32 + +### Patch Changes + +- b1acd9b: Updated README +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + +## @backstage/plugin-permission-common@0.7.12 + +### Patch Changes + +- b1acd9b: Updated README +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-permission-node@0.7.20 + +### Patch Changes + +- b1acd9b: Updated README +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-permission-react@0.4.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- b1acd9b: Updated README +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/config@1.1.1 + +## @backstage/plugin-playlist@0.2.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-react@1.7.5 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-playlist-common@0.1.14 + +## @backstage/plugin-playlist-backend@0.3.13 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-playlist-common@0.1.14 + +## @backstage/plugin-playlist-common@0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-proxy-backend@0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + +## @backstage/plugin-puppetdb@0.1.12 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-rollbar@0.4.29 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-rollbar-backend@0.1.54 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config@1.1.1 + +## @backstage/plugin-scaffolder@1.17.1 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- 4016f21: Remove some unused dependencies +- df4bc9d: Minor internal refactor +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.7.1 + - @backstage/core-components@0.13.10 + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.1 + +### Patch Changes + +- d076ee4: Updated dependency `azure-devops-node-api` to `^12.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.1 + +### Patch Changes + +- a694f71: The Scaffolder builtin actions now contains an action for running pipelines from Bitbucket Cloud Rest API +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.33 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.1.1 + +### Patch Changes + +- 5470300: Ensure `teamReviewers` list is unique before calling API +- bf92ae3: Updated dependency `octokit` to `^3.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.2.12 + +### Patch Changes + +- 604c9dd: Add Scaffolder custom action that creates GitLab issues called `gitlab:issues:create` +- 7c522c5: Add `gitlab:repo:push` scaffolder action to push files to arbitrary branch without creating a Merge Request +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.30 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-common@1.4.5 + +### Patch Changes + +- 178b8d8: Updated Template.v1beta3.schema.json, added a missing "presentation" field +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-react@1.7.1 + +### Patch Changes + +- c28f281: Scaffolder form now shows a list of errors at the top of the form. +- 0b9ce2b: Fix for a step with no properties +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- 4016f21: Remove some unused dependencies +- d16f85f: Show first scaffolder output text by default +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/plugin-search@1.4.5 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-search-react@1.7.5 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend@1.4.9 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-openapi-utils@0.1.2 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-module-catalog@0.1.13 + +### Patch Changes + +- 2e6c56b: Update wording to show that the backend system no longer is in alpha +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-module-elasticsearch@1.3.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/config@1.1.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-module-explore@0.1.13 + +### Patch Changes + +- 2e6c56b: Update wording to show that the backend system no longer is in alpha +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/config@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-module-pg@0.5.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/config@1.1.1 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.2 + +### Patch Changes + +- 2e6c56b: Update wording to show that the backend system no longer is in alpha +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/config@1.1.1 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-module-techdocs@0.1.13 + +### Patch Changes + +- 2e6c56b: Update wording to show that the backend system no longer is in alpha +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/plugin-techdocs-node@1.11.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-node@1.2.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/backend-tasks@0.5.14 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-common@1.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/types@1.1.1 + +## @backstage/plugin-search-react@1.7.5 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-sentry@0.5.14 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-shortcuts@0.3.18 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-sonarqube@0.7.11 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-sonarqube-react@0.1.12 + +## @backstage/plugin-sonarqube-backend@0.2.11 + +### Patch Changes + +- 53445cd: Updated README +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-sonarqube-react@0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-splunk-on-call@0.4.18 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-stack-overflow@0.1.24 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-home-react@0.1.7 + - @backstage/plugin-search-react@1.7.5 + - @backstage/config@1.1.1 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-stack-overflow-backend@0.2.13 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.2 + +## @backstage/plugin-stackstorm@0.1.10 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-tech-insights@0.3.21 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend@0.5.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-tech-insights-node@0.4.15 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-tech-insights-node@0.4.15 + - @backstage/errors@1.2.3 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-node@0.4.15 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-radar@0.6.12 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-techdocs@1.9.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-techdocs-react@1.1.15 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-search-react@1.7.5 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.26 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-techdocs-react@1.1.15 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-techdocs@1.9.3 + - @backstage/plugin-catalog@1.16.1 + - @backstage/plugin-search-react@1.7.5 + - @backstage/integration-react@1.1.23 + - @backstage/core-app-api@1.11.3 + - @backstage/test-utils@1.4.7 + +## @backstage/plugin-techdocs-backend@1.9.2 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.13 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-techdocs-node@1.11.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.4 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-techdocs-react@1.1.15 + - @backstage/integration-react@1.1.23 + - @backstage/integration@1.8.0 + +## @backstage/plugin-techdocs-node@1.11.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-techdocs-react@1.1.15 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/plugin-todo@0.2.33 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-todo-backend@0.3.7 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-openapi-utils@0.1.2 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-user-settings-backend@0.2.8 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-vault@0.1.24 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-vault-backend@0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-tasks@0.5.14 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-vault-node@0.1.2 + +## @backstage/plugin-vault-node@0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + +## @backstage/plugin-xcmetrics@0.2.47 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## example-app@0.2.91 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.10.3 + - @backstage/plugin-scaffolder-react@1.7.1 + - @backstage/core-components@0.13.10 + - @backstage/plugin-user-settings@0.8.0 + - @backstage/plugin-azure-sites@0.1.18 + - @backstage/cli@0.25.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-octopus-deploy@0.2.11 + - @backstage/frontend-app-api@0.5.0 + - @backstage/plugin-kubernetes@0.11.4 + - @backstage/plugin-home@0.6.1 + - @backstage/plugin-scaffolder@1.17.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7 + - @backstage/plugin-kubernetes-cluster@0.0.5 + - @backstage/plugin-microsoft-calendar@0.1.11 + - @backstage/plugin-newrelic-dashboard@0.3.4 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-entity-feedback@0.2.12 + - @backstage/plugin-apache-airflow@0.2.19 + - @backstage/plugin-github-actions@0.6.10 + - @backstage/plugin-stack-overflow@0.1.24 + - @backstage/plugin-techdocs-react@1.1.15 + - @backstage/plugin-catalog-graph@0.3.3 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-code-coverage@0.2.22 + - @backstage/plugin-cost-insights@0.12.18 + - @backstage/plugin-tech-insights@0.3.21 + - @backstage/plugin-azure-devops@0.3.11 + - @backstage/plugin-gcp-projects@0.3.45 + - @backstage/plugin-cloudbuild@0.3.29 + - @backstage/plugin-lighthouse@0.4.14 + - @backstage/plugin-stackstorm@0.1.10 + - @backstage/plugin-tech-radar@0.6.12 + - @backstage/plugin-dynatrace@8.0.3 + - @backstage/plugin-gcalendar@0.3.22 + - @backstage/plugin-pagerduty@0.7.1 + - @backstage/plugin-shortcuts@0.3.18 + - @backstage/plugin-airbrake@0.3.29 + - @backstage/plugin-devtools@0.1.8 + - @backstage/plugin-graphiql@0.3.2 + - @backstage/plugin-linguist@0.1.14 + - @backstage/plugin-newrelic@0.3.44 + - @backstage/plugin-playlist@0.2.3 + - @backstage/plugin-puppetdb@0.1.12 + - @backstage/plugin-techdocs@1.9.3 + - @backstage/plugin-catalog@1.16.1 + - @backstage/plugin-explore@0.4.15 + - @backstage/plugin-jenkins@0.9.4 + - @backstage/plugin-rollbar@0.4.29 + - @backstage/plugin-badges@0.2.53 + - @backstage/plugin-search@1.4.5 + - @backstage/plugin-sentry@0.5.14 + - @backstage/plugin-kafka@0.3.29 + - @backstage/plugin-nomad@0.1.10 + - @backstage/plugin-gocd@0.1.35 + - @backstage/plugin-todo@0.2.33 + - @backstage/plugin-adr@0.6.12 + - @backstage/plugin-org@0.6.19 + - @backstage/plugin-catalog-import@0.10.5 + - @backstage/plugin-search-react@1.7.5 + - @backstage/app-defaults@1.4.7 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.10 + +## example-app-next@0.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.10.3 + - @backstage/core-compat-api@0.1.1 + - @backstage/plugin-scaffolder-react@1.7.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-user-settings@0.8.0 + - @backstage/plugin-azure-sites@0.1.18 + - @backstage/cli@0.25.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-octopus-deploy@0.2.11 + - @backstage/frontend-app-api@0.5.0 + - @backstage/plugin-kubernetes@0.11.4 + - @backstage/plugin-home@0.6.1 + - @backstage/plugin-scaffolder@1.17.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7 + - @backstage/plugin-microsoft-calendar@0.1.11 + - @backstage/plugin-newrelic-dashboard@0.3.4 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-entity-feedback@0.2.12 + - @backstage/plugin-apache-airflow@0.2.19 + - @backstage/plugin-github-actions@0.6.10 + - @backstage/plugin-techdocs-react@1.1.15 + - @backstage/plugin-catalog-graph@0.3.3 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-code-coverage@0.2.22 + - @backstage/plugin-cost-insights@0.12.18 + - @backstage/plugin-tech-insights@0.3.21 + - @backstage/plugin-azure-devops@0.3.11 + - @backstage/plugin-gcp-projects@0.3.45 + - @backstage/plugin-cloudbuild@0.3.29 + - @backstage/plugin-lighthouse@0.4.14 + - @backstage/plugin-stackstorm@0.1.10 + - @backstage/plugin-tech-radar@0.6.12 + - @backstage/plugin-dynatrace@8.0.3 + - @backstage/plugin-gcalendar@0.3.22 + - @backstage/plugin-pagerduty@0.7.1 + - @backstage/plugin-shortcuts@0.3.18 + - @backstage/plugin-airbrake@0.3.29 + - @backstage/plugin-devtools@0.1.8 + - @backstage/plugin-graphiql@0.3.2 + - @backstage/plugin-linguist@0.1.14 + - @backstage/plugin-newrelic@0.3.44 + - @backstage/plugin-playlist@0.2.3 + - @backstage/plugin-puppetdb@0.1.12 + - @backstage/plugin-techdocs@1.9.3 + - @backstage/plugin-catalog@1.16.1 + - @backstage/plugin-explore@0.4.15 + - @backstage/plugin-jenkins@0.9.4 + - @backstage/plugin-rollbar@0.4.29 + - @backstage/plugin-badges@0.2.53 + - @backstage/plugin-search@1.4.5 + - @backstage/plugin-sentry@0.5.14 + - @backstage/plugin-kafka@0.3.29 + - @backstage/plugin-gocd@0.1.35 + - @backstage/plugin-todo@0.2.33 + - @backstage/plugin-adr@0.6.12 + - @backstage/plugin-org@0.6.19 + - @backstage/plugin-app-visualizer@0.1.0 + - @backstage/plugin-catalog-import@0.10.5 + - app-next-example-plugin@0.0.5 + - @backstage/plugin-search-react@1.7.5 + - @backstage/app-defaults@1.4.7 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.10 + +## app-next-example-plugin@0.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + +## example-backend@0.2.91 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.3 + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-backend@1.20.0 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10 + - @backstage/plugin-events-backend@0.2.18 + - @backstage/plugin-search-backend-module-techdocs@0.1.13 + - @backstage/plugin-search-backend-module-catalog@0.1.13 + - @backstage/plugin-search-backend-module-explore@0.1.13 + - @backstage/plugin-azure-devops-backend@0.5.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41 + - @backstage/plugin-entity-feedback-backend@0.2.6 + - @backstage/plugin-code-coverage-backend@0.2.23 + - @backstage/plugin-azure-sites-backend@0.1.19 + - @backstage/plugin-tech-insights-node@0.4.15 + - @backstage/plugin-devtools-backend@0.2.6 + - @backstage/plugin-linguist-backend@0.5.6 + - @backstage/plugin-playlist-backend@0.3.13 + - @backstage/plugin-techdocs-backend@1.9.2 + - @backstage/plugin-explore-backend@0.0.19 + - @backstage/plugin-jenkins-backend@0.3.3 + - @backstage/plugin-badges-backend@0.3.6 + - @backstage/plugin-search-backend@1.4.9 + - @backstage/plugin-kafka-backend@0.3.7 + - @backstage/plugin-nomad-backend@0.1.11 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-todo-backend@0.3.7 + - @backstage/plugin-adr-backend@0.4.6 + - @backstage/plugin-app-backend@0.3.57 + - @backstage/plugin-permission-backend@0.5.32 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-catalog-backend@1.16.1 + - example-app@0.2.91 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/plugin-kubernetes-backend@0.14.1 + - @backstage/plugin-lighthouse-backend@0.4.1 + - @backstage/plugin-proxy-backend@0.4.7 + - @backstage/plugin-rollbar-backend@0.1.54 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.26 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.12 + - @backstage/plugin-search-backend-module-pg@0.5.18 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/plugin-tech-insights-backend@0.5.23 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6 + - @backstage/plugin-events-node@0.2.18 + - @backstage/plugin-search-common@1.2.10 + +## example-backend-next@0.0.19 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-sonarqube-backend@0.2.11 + - @backstage/plugin-scaffolder-backend@1.20.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.26 + - @backstage/plugin-search-backend-module-techdocs@0.1.13 + - @backstage/plugin-search-backend-module-catalog@0.1.13 + - @backstage/plugin-search-backend-module-explore@0.1.13 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-defaults@0.2.9 + - @backstage/plugin-azure-devops-backend@0.5.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2 + - @backstage/plugin-entity-feedback-backend@0.2.6 + - @backstage/plugin-devtools-backend@0.2.6 + - @backstage/plugin-linguist-backend@0.5.6 + - @backstage/plugin-playlist-backend@0.3.13 + - @backstage/plugin-techdocs-backend@1.9.2 + - @backstage/plugin-jenkins-backend@0.3.3 + - @backstage/plugin-badges-backend@0.3.6 + - @backstage/plugin-search-backend@1.4.9 + - @backstage/plugin-nomad-backend@0.1.11 + - @backstage/plugin-todo-backend@0.3.7 + - @backstage/plugin-adr-backend@0.4.6 + - @backstage/plugin-app-backend@0.3.57 + - @backstage/plugin-permission-backend@0.5.32 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-catalog-backend@1.16.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/plugin-kubernetes-backend@0.14.1 + - @backstage/plugin-lighthouse-backend@0.4.1 + - @backstage/plugin-proxy-backend@0.4.7 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6 + +## e2e-test@0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.9 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## techdocs-cli-embedded-app@0.2.90 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/cli@0.25.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-techdocs-react@1.1.15 + - @backstage/plugin-techdocs@1.9.3 + - @backstage/plugin-catalog@1.16.1 + - @backstage/app-defaults@1.4.7 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/test-utils@1.4.7 + - @backstage/theme@0.5.0 + +## @internal/plugin-todo-list@1.0.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @internal/plugin-todo-list-backend@1.0.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/errors@1.2.3 + +## @internal/plugin-todo-list-common@1.0.17 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 diff --git a/docs/releases/v1.22.0-next.0-changelog.md b/docs/releases/v1.22.0-next.0-changelog.md new file mode 100644 index 0000000000..2505b29d9d --- /dev/null +++ b/docs/releases/v1.22.0-next.0-changelog.md @@ -0,0 +1,2974 @@ +# Release v1.22.0-next.0 + +## @backstage/app-defaults@1.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + +## @backstage/backend-app-api@0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.1 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## @backstage/backend-common@0.20.1-next.0 + +### Patch Changes + +- b6b15b2: Use sha256 instead of md5 for hash key calculation in caches + + This can have a side effect of invalidating caches (when cache key was >250 characters) + This improves compliance with FIPS nodejs + +- Updated dependencies + - @backstage/backend-app-api@0.5.10-next.0 + - @backstage/backend-dev-utils@0.1.2 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-app-api@0.5.10-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + +## @backstage/backend-openapi-utils@0.1.2-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/errors@1.2.3 + +## @backstage/backend-plugin-api@0.6.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/backend-tasks@0.5.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-app-api@0.5.10-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/catalog-client@1.5.2-next.0 + +### Patch Changes + +- 883782e: Fix a bug in `getLocationByRef` that led to invalid backend calls +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/cli@0.25.1-next.0 + +### Patch Changes + +- b6b15b2: Use sha256 instead of md5 in build script cache key calculation + + Makes it possible to build on FIPS nodejs. + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.1 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0 + - @backstage/errors@1.2.3 + - @backstage/eslint-plugin@0.1.4 + - @backstage/integration@1.8.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/core-compat-api@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/core-components@0.13.10-next.0 + +### Patch Changes + +- d625f66: Fixed bug in Link where it was possible to select and copy a hidden element into clipboard +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/version-bridge@1.0.7 + +## @backstage/create-app@0.5.9-next.0 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## @backstage/dev-utils@1.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/app-defaults@1.4.7-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + +## @backstage/frontend-app-api@0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-plugin-api@0.4.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-test-utils@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/frontend-app-api@0.4.1-next.0 + - @backstage/test-utils@1.4.7-next.0 + - @backstage/types@1.1.1 + +## @backstage/repo-tools@0.5.2-next.0 + +### Patch Changes + +- 883782e: Updated the OpenAPI template to export the `TypedResponse` interface so that client code can leverage it +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.1 + - @backstage/errors@1.2.3 + +## @techdocs/cli@1.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.11.1-next.0 + +## @backstage/test-utils@1.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-adr@0.6.12-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-adr-common@0.2.18 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-adr-backend@0.4.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-adr-common@0.2.18 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-airbrake@0.3.29-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/dev-utils@1.0.26-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/test-utils@1.4.7-next.0 + +## @backstage/plugin-airbrake-backend@0.3.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-allure@0.1.45-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-analytics-module-ga@0.1.37-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-analytics-module-ga4@0.1.8-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-analytics-module-newrelic-browser@0.0.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-apache-airflow@0.2.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-api-docs@0.10.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-catalog@1.16.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-apollo-explorer@0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-app-backend@0.3.57-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.9-next.0 + +## @backstage/plugin-app-node@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + +## @backstage/plugin-auth-backend@0.20.3-next.0 + +### Patch Changes + +- 004499c: Fixed an issue where some Okta's resolvers were missing +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.1-next.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.1-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.3-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.6-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.6-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.6-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.6-next.0 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.1.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.4-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.2-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-auth-node@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-azure-devops@0.3.11-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-azure-devops-common@0.3.2 + +## @backstage/plugin-azure-devops-backend@0.5.1-next.0 + +### Patch Changes + +- d076ee4: Updated dependency `azure-devops-node-api` to `^12.0.0`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-azure-devops-common@0.3.2 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-azure-sites@0.1.18-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-azure-sites-backend@0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-badges@0.2.53-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-badges-backend@0.3.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-bazaar@0.2.21-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bazaar-backend@0.3.7-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-bitrise@0.1.56-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-catalog@1.16.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-catalog-backend@1.16.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.0 + - @backstage/backend-openapi-utils@0.1.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-events-node@0.2.18-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.3.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-backend-module-azure@0.1.28-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-openapi-utils@0.1.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.24-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-events-node@0.2.18-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.25-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-catalog-backend-module-github@0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-events-node@0.2.18-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-backend-module-github@0.4.7-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-catalog-graph@0.3.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.10.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-node@1.6.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## @backstage/plugin-catalog-react@1.9.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-cicd-statistics@0.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-cicd-statistics@0.1.31-next.0 + +## @backstage/plugin-circleci@0.3.29-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-cloudbuild@0.3.29-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-code-climate@0.1.29-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-code-coverage@0.2.22-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-code-coverage-backend@0.2.23-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-codescene@0.1.21-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-config-schema@0.1.49-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-cost-insights@0.12.18-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/plugin-cost-insights-common@0.1.2 + +## @backstage/plugin-devtools@0.1.8-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-devtools-common@0.1.7 + +## @backstage/plugin-devtools-backend@0.2.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-devtools-common@0.1.7 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## @backstage/plugin-dynatrace@8.0.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-entity-feedback@0.2.12-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-feedback-backend@0.2.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-validation@0.1.14-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-events-backend@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.18-next.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.18-next.0 + +## @backstage/plugin-events-backend-module-azure@0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + +## @backstage/plugin-events-backend-module-github@0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.18-next.0 + +## @backstage/plugin-events-backend-module-gitlab@0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.18-next.0 + +## @backstage/plugin-events-backend-test-utils@0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.18-next.0 + +## @backstage/plugin-events-node@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + +## @backstage/plugin-explore@0.4.15-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-explore-react@0.0.35-next.0 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-explore-backend@0.0.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + +## @backstage/plugin-explore-react@0.0.35-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-explore-common@0.0.2 + +## @backstage/plugin-firehydrant@0.2.13-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-fossa@0.2.61-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-gcalendar@0.3.22-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-gcp-projects@0.3.45-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-git-release-manager@0.3.41-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/integration@1.8.0 + +## @backstage/plugin-github-actions@0.6.10-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/integration@1.8.0 + +## @backstage/plugin-github-deployments@0.1.60-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-github-issues@0.2.18-next.0 + +### Patch Changes + +- bf92ae3: Updated dependency `octokit` to `^3.0.0`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-github-pull-requests-board@0.1.23-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/integration@1.8.0 + +## @backstage/plugin-gitops-profiles@0.3.44-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-gocd@0.1.35-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-graphiql@0.3.2-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-graphql-voyager@0.1.11-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-home@0.6.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-home-react@0.1.7-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.2 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + +## @backstage/plugin-home-react@0.1.7-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-ilert@0.2.18-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-jenkins@0.9.4-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.22 + +## @backstage/plugin-jenkins-backend@0.3.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-jenkins-common@0.1.22 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## @backstage/plugin-kafka@0.3.29-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-kafka-backend@0.3.7-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-kubernetes@0.11.4-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-kubernetes-react@0.2.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-kubernetes-backend@0.14.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-kubernetes-node@0.1.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.5-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-kubernetes-react@0.2.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-kubernetes-common@0.7.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-kubernetes-node@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-react@0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-lighthouse@0.4.14-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-lighthouse-backend@0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-linguist@0.1.14-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-linguist-backend@0.5.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-microsoft-calendar@0.1.11-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-newrelic@0.3.44-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-newrelic-dashboard@0.3.4-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-nomad@0.1.10-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-nomad-backend@0.1.11-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-octopus-deploy@0.2.11-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-opencost@0.2.4-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-org@0.6.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-org-react@0.1.18-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-pagerduty@0.7.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-home-react@0.1.7-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-periskop@0.1.27-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-periskop-backend@0.2.7-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-permission-backend@0.5.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## @backstage/plugin-permission-node@0.7.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-permission-react@0.4.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-playlist@0.2.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-playlist-common@0.1.13 + +## @backstage/plugin-playlist-backend@0.3.13-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-playlist-common@0.1.13 + +## @backstage/plugin-proxy-backend@0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-puppetdb@0.1.12-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-rollbar@0.4.29-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-rollbar-backend@0.1.54-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-scaffolder@1.17.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.7.1-next.0 + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-scaffolder-backend@1.19.3-next.0 + +### Patch Changes + +- e9ab1c4: Fixed an issue where not passing a `value` to any of the action's permission conditions caused an error. +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.1.1-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.1-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.1-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.1-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.12-next.0 + - @backstage/plugin-scaffolder-common@1.4.4 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.1-next.0 + +### Patch Changes + +- d076ee4: Updated dependency `azure-devops-node-api` to `^12.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.33-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.1.1-next.0 + +### Patch Changes + +- bf92ae3: Updated dependency `octokit` to `^3.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.30-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## @backstage/plugin-scaffolder-node@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-scaffolder-react@1.7.1-next.0 + +### Patch Changes + +- c28f281: Scaffolder form now shows a list of errors at the top of the form. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-search@1.4.5-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend@1.4.9-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-openapi-utils@0.1.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-catalog@0.1.13-next.0 + +### Patch Changes + +- 2e6c56b: Update wording to show that the backend system no longer is in alpha +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-explore@0.1.13-next.0 + +### Patch Changes + +- 2e6c56b: Update wording to show that the backend system no longer is in alpha +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-pg@0.5.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.2-next.0 + +### Patch Changes + +- 2e6c56b: Update wording to show that the backend system no longer is in alpha +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-techdocs@0.1.13-next.0 + +### Patch Changes + +- 2e6c56b: Update wording to show that the backend system no longer is in alpha +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + - @backstage/plugin-techdocs-node@1.11.1-next.0 + +## @backstage/plugin-search-backend-node@1.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-react@1.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-sentry@0.5.14-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-shortcuts@0.3.18-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-sonarqube@0.7.11-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-sonarqube-react@0.1.11 + +## @backstage/plugin-sonarqube-backend@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-splunk-on-call@0.4.18-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-stack-overflow@0.1.24-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-home-react@0.1.7-next.0 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-stack-overflow-backend@0.2.13-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.2-next.0 + +## @backstage/plugin-stackstorm@0.1.10-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-tech-insights@0.3.21-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend@0.5.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-tech-insights-node@0.4.15-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-tech-insights-node@0.4.15-next.0 + - @backstage/errors@1.2.3 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-node@0.4.15-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-radar@0.6.12-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + +## @backstage/plugin-techdocs@1.9.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-techdocs-react@1.1.15-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.26-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-techdocs-react@1.1.15-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-techdocs@1.9.3-next.0 + - @backstage/plugin-catalog@1.16.1-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/test-utils@1.4.7-next.0 + +## @backstage/plugin-techdocs-backend@1.9.2-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-techdocs-node@1.11.1-next.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-techdocs-react@1.1.15-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/core-plugin-api@1.8.1 + - @backstage/integration@1.8.0 + +## @backstage/plugin-techdocs-node@1.11.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-techdocs-react@1.1.15-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/plugin-todo@0.2.33-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-todo-backend@0.3.7-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-openapi-utils@0.1.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-user-settings@0.7.15-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/core-app-api@1.11.2 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-user-settings-backend@0.2.8-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-vault@0.1.24-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-vault-backend@0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-vault-node@0.1.2-next.0 + +## @backstage/plugin-vault-node@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + +## @backstage/plugin-xcmetrics@0.2.47-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## example-app@0.2.91-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.7.1-next.0 + - @backstage/core-components@0.13.10-next.0 + - @backstage/cli@0.25.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.5-next.0 + - @backstage/plugin-microsoft-calendar@0.1.11-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/plugin-entity-feedback@0.2.12-next.0 + - @backstage/plugin-apache-airflow@0.2.19-next.0 + - @backstage/plugin-github-actions@0.6.10-next.0 + - @backstage/plugin-octopus-deploy@0.2.11-next.0 + - @backstage/plugin-stack-overflow@0.1.24-next.0 + - @backstage/plugin-techdocs-react@1.1.15-next.0 + - @backstage/plugin-catalog-graph@0.3.3-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-code-coverage@0.2.22-next.0 + - @backstage/plugin-cost-insights@0.12.18-next.0 + - @backstage/plugin-tech-insights@0.3.21-next.0 + - @backstage/plugin-user-settings@0.7.15-next.0 + - @backstage/plugin-azure-devops@0.3.11-next.0 + - @backstage/plugin-gcp-projects@0.3.45-next.0 + - @backstage/plugin-azure-sites@0.1.18-next.0 + - @backstage/plugin-cloudbuild@0.3.29-next.0 + - @backstage/plugin-kubernetes@0.11.4-next.0 + - @backstage/plugin-lighthouse@0.4.14-next.0 + - @backstage/plugin-scaffolder@1.17.1-next.0 + - @backstage/plugin-stackstorm@0.1.10-next.0 + - @backstage/plugin-tech-radar@0.6.12-next.0 + - @backstage/plugin-dynatrace@8.0.3-next.0 + - @backstage/plugin-gcalendar@0.3.22-next.0 + - @backstage/plugin-pagerduty@0.7.1-next.0 + - @backstage/plugin-shortcuts@0.3.18-next.0 + - @backstage/plugin-airbrake@0.3.29-next.0 + - @backstage/plugin-api-docs@0.10.3-next.0 + - @backstage/plugin-devtools@0.1.8-next.0 + - @backstage/plugin-graphiql@0.3.2-next.0 + - @backstage/plugin-linguist@0.1.14-next.0 + - @backstage/plugin-newrelic@0.3.44-next.0 + - @backstage/plugin-playlist@0.2.3-next.0 + - @backstage/plugin-puppetdb@0.1.12-next.0 + - @backstage/plugin-techdocs@1.9.3-next.0 + - @backstage/plugin-catalog@1.16.1-next.0 + - @backstage/plugin-explore@0.4.15-next.0 + - @backstage/plugin-jenkins@0.9.4-next.0 + - @backstage/plugin-rollbar@0.4.29-next.0 + - @backstage/plugin-badges@0.2.53-next.0 + - @backstage/plugin-search@1.4.5-next.0 + - @backstage/plugin-sentry@0.5.14-next.0 + - @backstage/plugin-kafka@0.3.29-next.0 + - @backstage/plugin-nomad@0.1.10-next.0 + - @backstage/plugin-gocd@0.1.35-next.0 + - @backstage/plugin-home@0.6.1-next.0 + - @backstage/plugin-todo@0.2.33-next.0 + - @backstage/plugin-adr@0.6.12-next.0 + - @backstage/plugin-org@0.6.19-next.0 + - @backstage/app-defaults@1.4.7-next.0 + - @backstage/frontend-app-api@0.4.1-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-catalog-import@0.10.5-next.0 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## example-app-next@0.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.7.1-next.0 + - @backstage/core-components@0.13.10-next.0 + - @backstage/cli@0.25.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-microsoft-calendar@0.1.11-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/plugin-entity-feedback@0.2.12-next.0 + - @backstage/plugin-apache-airflow@0.2.19-next.0 + - @backstage/plugin-github-actions@0.6.10-next.0 + - @backstage/plugin-octopus-deploy@0.2.11-next.0 + - @backstage/plugin-techdocs-react@1.1.15-next.0 + - @backstage/plugin-catalog-graph@0.3.3-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-code-coverage@0.2.22-next.0 + - @backstage/plugin-cost-insights@0.12.18-next.0 + - @backstage/plugin-tech-insights@0.3.21-next.0 + - @backstage/plugin-user-settings@0.7.15-next.0 + - @backstage/plugin-azure-devops@0.3.11-next.0 + - @backstage/plugin-gcp-projects@0.3.45-next.0 + - @backstage/plugin-azure-sites@0.1.18-next.0 + - @backstage/plugin-cloudbuild@0.3.29-next.0 + - @backstage/plugin-kubernetes@0.11.4-next.0 + - @backstage/plugin-lighthouse@0.4.14-next.0 + - @backstage/plugin-scaffolder@1.17.1-next.0 + - @backstage/plugin-stackstorm@0.1.10-next.0 + - @backstage/plugin-tech-radar@0.6.12-next.0 + - @backstage/plugin-dynatrace@8.0.3-next.0 + - @backstage/plugin-gcalendar@0.3.22-next.0 + - @backstage/plugin-pagerduty@0.7.1-next.0 + - @backstage/plugin-shortcuts@0.3.18-next.0 + - @backstage/plugin-airbrake@0.3.29-next.0 + - @backstage/plugin-api-docs@0.10.3-next.0 + - @backstage/plugin-devtools@0.1.8-next.0 + - @backstage/plugin-graphiql@0.3.2-next.0 + - @backstage/plugin-linguist@0.1.14-next.0 + - @backstage/plugin-newrelic@0.3.44-next.0 + - @backstage/plugin-playlist@0.2.3-next.0 + - @backstage/plugin-puppetdb@0.1.12-next.0 + - @backstage/plugin-techdocs@1.9.3-next.0 + - @backstage/plugin-catalog@1.16.1-next.0 + - @backstage/plugin-explore@0.4.15-next.0 + - @backstage/plugin-jenkins@0.9.4-next.0 + - @backstage/plugin-rollbar@0.4.29-next.0 + - @backstage/plugin-badges@0.2.53-next.0 + - @backstage/plugin-search@1.4.5-next.0 + - @backstage/plugin-sentry@0.5.14-next.0 + - @backstage/plugin-kafka@0.3.29-next.0 + - @backstage/plugin-gocd@0.1.35-next.0 + - @backstage/plugin-home@0.6.1-next.0 + - @backstage/plugin-todo@0.2.33-next.0 + - @backstage/plugin-adr@0.6.12-next.0 + - @backstage/plugin-org@0.6.19-next.0 + - @backstage/app-defaults@1.4.7-next.0 + - app-next-example-plugin@0.0.5-next.0 + - @backstage/frontend-app-api@0.4.1-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-catalog-import@0.10.5-next.0 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/plugin-visualizer@0.0.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.2 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## app-next-example-plugin@0.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + +## example-backend@0.2.91-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.3-next.0 + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-scaffolder-backend@1.19.3-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.0 + - @backstage/plugin-azure-devops-backend@0.5.1-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.0 + - @backstage/plugin-code-coverage-backend@0.2.23-next.0 + - @backstage/plugin-azure-sites-backend@0.1.19-next.0 + - @backstage/plugin-tech-insights-node@0.4.15-next.0 + - @backstage/plugin-devtools-backend@0.2.6-next.0 + - @backstage/plugin-linguist-backend@0.5.6-next.0 + - @backstage/plugin-playlist-backend@0.3.13-next.0 + - @backstage/plugin-techdocs-backend@1.9.2-next.0 + - @backstage/plugin-explore-backend@0.0.19-next.0 + - @backstage/plugin-jenkins-backend@0.3.3-next.0 + - @backstage/plugin-badges-backend@0.3.6-next.0 + - @backstage/plugin-search-backend@1.4.9-next.0 + - @backstage/plugin-kafka-backend@0.3.7-next.0 + - @backstage/plugin-nomad-backend@0.1.11-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/plugin-todo-backend@0.3.7-next.0 + - @backstage/plugin-adr-backend@0.4.6-next.0 + - @backstage/plugin-app-backend@0.3.57-next.0 + - example-app@0.2.91-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.0 + - @backstage/plugin-events-backend@0.2.18-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + - @backstage/plugin-kubernetes-backend@0.14.1-next.0 + - @backstage/plugin-lighthouse-backend@0.4.1-next.0 + - @backstage/plugin-permission-backend@0.5.32-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-proxy-backend@0.4.7-next.0 + - @backstage/plugin-rollbar-backend@0.1.54-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.18-next.0 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + - @backstage/plugin-tech-insights-backend@0.5.23-next.0 + +## example-backend-next@0.0.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.19.3-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.0 + - @backstage/plugin-azure-devops-backend@0.5.1-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.0 + - @backstage/plugin-devtools-backend@0.2.6-next.0 + - @backstage/plugin-linguist-backend@0.5.6-next.0 + - @backstage/plugin-playlist-backend@0.3.13-next.0 + - @backstage/plugin-techdocs-backend@1.9.2-next.0 + - @backstage/plugin-jenkins-backend@0.3.3-next.0 + - @backstage/plugin-badges-backend@0.3.6-next.0 + - @backstage/plugin-search-backend@1.4.9-next.0 + - @backstage/plugin-nomad-backend@0.1.11-next.0 + - @backstage/plugin-todo-backend@0.3.7-next.0 + - @backstage/plugin-adr-backend@0.4.6-next.0 + - @backstage/plugin-app-backend@0.3.57-next.0 + - @backstage/backend-defaults@0.2.9-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.0 + - @backstage/plugin-kubernetes-backend@0.14.1-next.0 + - @backstage/plugin-lighthouse-backend@0.4.1-next.0 + - @backstage/plugin-permission-backend@0.5.32-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-proxy-backend@0.4.7-next.0 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-sonarqube-backend@0.2.11-next.0 + +## @backstage/backend-plugin-manager@0.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-events-backend@0.2.18-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + +## e2e-test@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.9-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## techdocs-cli-embedded-app@0.2.90-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/cli@0.25.1-next.0 + - @backstage/plugin-techdocs-react@1.1.15-next.0 + - @backstage/plugin-techdocs@1.9.3-next.0 + - @backstage/plugin-catalog@1.16.1-next.0 + - @backstage/app-defaults@1.4.7-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/test-utils@1.4.7-next.0 + - @backstage/theme@0.5.0 + +## @internal/plugin-todo-list@1.0.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## @internal/plugin-todo-list-backend@1.0.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## @backstage/plugin-visualizer@0.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/core-plugin-api@1.8.1 diff --git a/docs/releases/v1.22.0-next.1-changelog.md b/docs/releases/v1.22.0-next.1-changelog.md new file mode 100644 index 0000000000..88d8c7c9cb --- /dev/null +++ b/docs/releases/v1.22.0-next.1-changelog.md @@ -0,0 +1,2868 @@ +# Release v1.22.0-next.1 + +## @backstage/plugin-user-settings@0.8.0-next.1 + +### Minor Changes + +- 56b2fb0: Updated the user settings selector to use a select component that displays native language names instead of language codes if possible. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + +## @backstage/app-defaults@1.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/theme@0.5.0 + +## @backstage/backend-app-api@0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.1-next.0 + - @backstage/cli-node@0.2.2-next.0 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/backend-common@0.20.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.1-next.0 + - @backstage/backend-app-api@0.5.10-next.1 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/config@1.1.1 + - @backstage/backend-dev-utils@0.1.2 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.10-next.1 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/backend-openapi-utils@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + +## @backstage/backend-plugin-api@0.6.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/backend-tasks@0.5.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.10-next.1 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/cli@0.25.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.1-next.0 + - @backstage/cli-node@0.2.2-next.0 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/release-manifests@0.0.11 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/eslint-plugin@0.1.4 + - @backstage/types@1.1.1 + +## @backstage/cli-node@0.2.2-next.0 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/config-loader@1.6.1-next.0 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/core-app-api@1.11.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/core-compat-api@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/core-components@0.13.10-next.1 + +### Patch Changes + +- 6878b1d: Removed unnecessary `history` and `immer` dependencies. +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/version-bridge@1.0.7 + +## @backstage/core-plugin-api@1.8.2-next.0 + +### Patch Changes + +- 6878b1d: Removed unnecessary `i18next` dependency. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/create-app@0.5.9-next.1 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## @backstage/dev-utils@1.0.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/app-defaults@1.4.7-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + +## @backstage/frontend-app-api@0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-plugin-api@0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-test-utils@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.4.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/test-utils@1.4.7-next.1 + - @backstage/types@1.1.1 + +## @backstage/integration-react@1.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + +## @backstage/repo-tools@0.5.2-next.1 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-node@0.2.2-next.0 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## @techdocs/cli@1.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.11.1-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + +## @backstage/test-utils@1.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-adr@0.6.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-adr-common@0.2.18 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-adr-backend@0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-adr-common@0.2.18 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-airbrake@0.3.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/dev-utils@1.0.26-next.1 + - @backstage/test-utils@1.4.7-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-airbrake-backend@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-allure@0.1.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-analytics-module-ga@0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/config@1.1.1 + +## @backstage/plugin-analytics-module-ga4@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/config@1.1.1 + +## @backstage/plugin-analytics-module-newrelic-browser@0.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/config@1.1.1 + +## @backstage/plugin-apache-airflow@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## @backstage/plugin-api-docs@0.10.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog@1.16.1-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-apollo-explorer@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## @backstage/plugin-app-backend@0.3.57-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.1-next.0 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.9-next.1 + +## @backstage/plugin-app-node@0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-auth-backend@0.20.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.1-next.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.3-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.6-next.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.6-next.1 + - @backstage/plugin-auth-backend-module-google-provider@0.1.6-next.1 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.6-next.1 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.1-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.2-next.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-auth-node@0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-azure-devops@0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-azure-devops-common@0.3.2 + +## @backstage/plugin-azure-devops-backend@0.5.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-azure-devops-common@0.3.2 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-azure-sites@0.1.18-next.1 + +### Patch Changes + +- a31f688: Show Azure site tags in `EntityAzureSitesOverviewWidget`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-azure-sites-backend@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-badges@0.2.53-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-badges-backend@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bazaar@0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bazaar-backend@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-bitrise@0.1.56-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-catalog@1.16.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-catalog-backend@1.16.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.1 + - @backstage/backend-openapi-utils@0.1.2-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-catalog-backend-module-aws@0.3.3-next.1 + +### Patch Changes + +- 22e88d0: Added status and e-mail as labels to the AWS Account Resource +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-openapi-utils@0.1.2-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-catalog-backend-module-github@0.4.7-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-catalog-graph@0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.10.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-catalog-node@1.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-catalog-react@1.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-cicd-statistics@0.1.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-cicd-statistics@0.1.31-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-circleci@0.3.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-cloudbuild@0.3.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-code-climate@0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-code-coverage@0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-code-coverage-backend@0.2.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-codescene@0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-config-schema@0.1.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-cost-insights@0.12.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-cost-insights-common@0.1.2 + +## @backstage/plugin-devtools@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-devtools-common@0.1.7 + +## @backstage/plugin-devtools-backend@0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.1-next.0 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.7 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-dynatrace@8.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-entity-feedback@0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-feedback-backend@0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-validation@0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-events-backend@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## @backstage/plugin-events-backend-module-azure@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## @backstage/plugin-events-backend-module-github@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## @backstage/plugin-events-backend-module-gitlab@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## @backstage/plugin-events-backend-test-utils@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.18-next.1 + +## @backstage/plugin-events-node@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-explore@0.4.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-explore-react@0.0.35-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-explore-backend@0.0.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + +## @backstage/plugin-explore-react@0.0.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-explore-common@0.0.2 + +## @backstage/plugin-firehydrant@0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-fossa@0.2.61-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-gcalendar@0.3.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-gcp-projects@0.3.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## @backstage/plugin-git-release-manager@0.3.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/integration@1.8.0 + +## @backstage/plugin-github-actions@0.6.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-github-deployments@0.1.60-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-github-issues@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-github-pull-requests-board@0.1.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-gitops-profiles@0.3.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## @backstage/plugin-gocd@0.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-graphiql@0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + +## @backstage/plugin-graphql-voyager@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## @backstage/plugin-home@0.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-home-react@0.1.7-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + +## @backstage/plugin-home-react@0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## @backstage/plugin-ilert@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-jenkins@0.9.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.22 + +## @backstage/plugin-jenkins-backend@0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-jenkins-common@0.1.22 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-kafka@0.3.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-kafka-backend@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-kubernetes@0.11.4-next.1 + +### Patch Changes + +- d5d2c67: Add `authuser` search parameter to GKE cluster link formatter in k8s plugin + + Thanks to this, people with multiple simultaneously logged-in accounts in their GCP console will automatically view objects with the same email as the one signed in to the Google auth provider in Backstage. + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-kubernetes-react@0.2.1-next.1 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + +## @backstage/plugin-kubernetes-backend@0.14.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/plugin-kubernetes-node@0.1.3-next.1 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-kubernetes-cluster@0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-kubernetes-react@0.2.1-next.1 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + +## @backstage/plugin-kubernetes-node@0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + +## @backstage/plugin-kubernetes-react@0.2.1-next.1 + +### Patch Changes + +- d5d2c67: Add `authuser` search parameter to GKE cluster link formatter in k8s plugin + + Thanks to this, people with multiple simultaneously logged-in accounts in their GCP console will automatically view objects with the same email as the one signed in to the Google auth provider in Backstage. + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + +## @backstage/plugin-lighthouse@0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-lighthouse-backend@0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-linguist@0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-linguist-backend@0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-microsoft-calendar@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-newrelic@0.3.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## @backstage/plugin-newrelic-dashboard@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-nomad@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-nomad-backend@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-octopus-deploy@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-opencost@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## @backstage/plugin-org@0.6.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + +## @backstage/plugin-org-react@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-pagerduty@0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-home-react@0.1.7-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-periskop@0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-periskop-backend@0.2.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-permission-backend@0.5.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-permission-node@0.7.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-permission-react@0.4.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-playlist@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-playlist-common@0.1.13 + +## @backstage/plugin-playlist-backend@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-playlist-common@0.1.13 + +## @backstage/plugin-proxy-backend@0.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-puppetdb@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-rollbar@0.4.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-rollbar-backend@0.1.54-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + +## @backstage/plugin-scaffolder@1.17.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/plugin-scaffolder-react@1.7.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-scaffolder-backend@1.19.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.12-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.1-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.1-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.1-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.1.1-next.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.1 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-github@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-scaffolder-react@1.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.4 + +## @backstage/plugin-search@1.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend@1.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/backend-openapi-utils@0.1.2-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-catalog@0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-explore@0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-pg@0.5.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-module-techdocs@0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/plugin-techdocs-node@1.11.1-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-backend-node@1.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-search-react@1.7.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-sentry@0.5.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-shortcuts@0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-sonarqube@0.7.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-sonarqube-react@0.1.12-next.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-sonarqube-backend@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-sonarqube-react@0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-splunk-on-call@0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-stack-overflow@0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/plugin-home-react@0.1.7-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-stack-overflow-backend@0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.2-next.1 + +## @backstage/plugin-stackstorm@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-tech-insights@0.3.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend@0.5.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-tech-insights-node@0.4.15-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/plugin-tech-insights-node@0.4.15-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-node@0.4.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-radar@0.6.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + +## @backstage/plugin-techdocs@1.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/plugin-techdocs-react@1.1.15-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/test-utils@1.4.7-next.1 + - @backstage/plugin-catalog@1.16.1-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/plugin-techdocs@1.9.3-next.1 + - @backstage/plugin-techdocs-react@1.1.15-next.1 + +## @backstage/plugin-techdocs-backend@1.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.1 + - @backstage/plugin-techdocs-node@1.11.1-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-techdocs-react@1.1.15-next.1 + - @backstage/integration@1.8.0 + +## @backstage/plugin-techdocs-node@1.11.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-search-common@1.2.9 + +## @backstage/plugin-techdocs-react@1.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.3 + - @backstage/version-bridge@1.0.7 + +## @backstage/plugin-todo@0.2.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-todo-backend@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-openapi-utils@0.1.2-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## @backstage/plugin-user-settings-backend@0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-vault@0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/plugin-vault-backend@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-vault-node@0.1.2-next.1 + +## @backstage/plugin-vault-node@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + +## @backstage/plugin-xcmetrics@0.2.47-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/errors@1.2.3 + +## example-app@0.2.91-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-sites@0.1.18-next.1 + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-user-settings@0.8.0-next.1 + - @backstage/plugin-kubernetes@0.11.4-next.1 + - @backstage/core-components@0.13.10-next.1 + - @backstage/app-defaults@1.4.7-next.1 + - @backstage/cli@0.25.1-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/frontend-app-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-adr@0.6.12-next.1 + - @backstage/plugin-airbrake@0.3.29-next.1 + - @backstage/plugin-apache-airflow@0.2.19-next.1 + - @backstage/plugin-api-docs@0.10.3-next.1 + - @backstage/plugin-azure-devops@0.3.11-next.1 + - @backstage/plugin-badges@0.2.53-next.1 + - @backstage/plugin-catalog@1.16.1-next.1 + - @backstage/plugin-catalog-graph@0.3.3-next.1 + - @backstage/plugin-catalog-import@0.10.5-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.1 + - @backstage/plugin-cloudbuild@0.3.29-next.1 + - @backstage/plugin-code-coverage@0.2.22-next.1 + - @backstage/plugin-cost-insights@0.12.18-next.1 + - @backstage/plugin-devtools@0.1.8-next.1 + - @backstage/plugin-dynatrace@8.0.3-next.1 + - @backstage/plugin-entity-feedback@0.2.12-next.1 + - @backstage/plugin-explore@0.4.15-next.1 + - @backstage/plugin-gcalendar@0.3.22-next.1 + - @backstage/plugin-gcp-projects@0.3.45-next.1 + - @backstage/plugin-github-actions@0.6.10-next.1 + - @backstage/plugin-gocd@0.1.35-next.1 + - @backstage/plugin-graphiql@0.3.2-next.1 + - @backstage/plugin-home@0.6.1-next.1 + - @backstage/plugin-jenkins@0.9.4-next.1 + - @backstage/plugin-kafka@0.3.29-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.5-next.1 + - @backstage/plugin-lighthouse@0.4.14-next.1 + - @backstage/plugin-linguist@0.1.14-next.1 + - @backstage/plugin-microsoft-calendar@0.1.11-next.1 + - @backstage/plugin-newrelic@0.3.44-next.1 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.1 + - @backstage/plugin-nomad@0.1.10-next.1 + - @backstage/plugin-octopus-deploy@0.2.11-next.1 + - @backstage/plugin-org@0.6.19-next.1 + - @backstage/plugin-pagerduty@0.7.1-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/plugin-playlist@0.2.3-next.1 + - @backstage/plugin-puppetdb@0.1.12-next.1 + - @backstage/plugin-rollbar@0.4.29-next.1 + - @backstage/plugin-scaffolder@1.17.1-next.1 + - @backstage/plugin-scaffolder-react@1.7.1-next.1 + - @backstage/plugin-search@1.4.5-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/plugin-sentry@0.5.14-next.1 + - @backstage/plugin-shortcuts@0.3.18-next.1 + - @backstage/plugin-stack-overflow@0.1.24-next.1 + - @backstage/plugin-stackstorm@0.1.10-next.1 + - @backstage/plugin-tech-insights@0.3.21-next.1 + - @backstage/plugin-tech-radar@0.6.12-next.1 + - @backstage/plugin-techdocs@1.9.3-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.1 + - @backstage/plugin-techdocs-react@1.1.15-next.1 + - @backstage/plugin-todo@0.2.33-next.1 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## example-app-next@0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-sites@0.1.18-next.1 + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-user-settings@0.8.0-next.1 + - @backstage/plugin-kubernetes@0.11.4-next.1 + - @backstage/core-components@0.13.10-next.1 + - @backstage/app-defaults@1.4.7-next.1 + - @backstage/cli@0.25.1-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-app-api@0.4.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-adr@0.6.12-next.1 + - @backstage/plugin-airbrake@0.3.29-next.1 + - @backstage/plugin-apache-airflow@0.2.19-next.1 + - @backstage/plugin-api-docs@0.10.3-next.1 + - @backstage/plugin-azure-devops@0.3.11-next.1 + - @backstage/plugin-badges@0.2.53-next.1 + - @backstage/plugin-catalog@1.16.1-next.1 + - @backstage/plugin-catalog-graph@0.3.3-next.1 + - @backstage/plugin-catalog-import@0.10.5-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.1 + - @backstage/plugin-cloudbuild@0.3.29-next.1 + - @backstage/plugin-code-coverage@0.2.22-next.1 + - @backstage/plugin-cost-insights@0.12.18-next.1 + - @backstage/plugin-devtools@0.1.8-next.1 + - @backstage/plugin-dynatrace@8.0.3-next.1 + - @backstage/plugin-entity-feedback@0.2.12-next.1 + - @backstage/plugin-explore@0.4.15-next.1 + - @backstage/plugin-gcalendar@0.3.22-next.1 + - @backstage/plugin-gcp-projects@0.3.45-next.1 + - @backstage/plugin-github-actions@0.6.10-next.1 + - @backstage/plugin-gocd@0.1.35-next.1 + - @backstage/plugin-graphiql@0.3.2-next.1 + - @backstage/plugin-home@0.6.1-next.1 + - @backstage/plugin-jenkins@0.9.4-next.1 + - @backstage/plugin-kafka@0.3.29-next.1 + - @backstage/plugin-lighthouse@0.4.14-next.1 + - @backstage/plugin-linguist@0.1.14-next.1 + - @backstage/plugin-microsoft-calendar@0.1.11-next.1 + - @backstage/plugin-newrelic@0.3.44-next.1 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.1 + - @backstage/plugin-octopus-deploy@0.2.11-next.1 + - @backstage/plugin-org@0.6.19-next.1 + - @backstage/plugin-pagerduty@0.7.1-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/plugin-playlist@0.2.3-next.1 + - @backstage/plugin-puppetdb@0.1.12-next.1 + - @backstage/plugin-rollbar@0.4.29-next.1 + - @backstage/plugin-scaffolder@1.17.1-next.1 + - @backstage/plugin-scaffolder-react@1.7.1-next.1 + - @backstage/plugin-search@1.4.5-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/plugin-sentry@0.5.14-next.1 + - @backstage/plugin-shortcuts@0.3.18-next.1 + - @backstage/plugin-stackstorm@0.1.10-next.1 + - @backstage/plugin-tech-insights@0.3.21-next.1 + - @backstage/plugin-tech-radar@0.6.12-next.1 + - @backstage/plugin-techdocs@1.9.3-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.1 + - @backstage/plugin-techdocs-react@1.1.15-next.1 + - @backstage/plugin-todo@0.2.33-next.1 + - @backstage/plugin-visualizer@0.0.2-next.1 + - app-next-example-plugin@0.0.5-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## app-next-example-plugin@0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + +## example-backend@0.2.91-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.1 + - example-app@0.2.91-next.1 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-app-backend@0.3.57-next.1 + - @backstage/plugin-devtools-backend@0.2.6-next.1 + - @backstage/plugin-proxy-backend@0.4.7-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-kubernetes-backend@0.14.1-next.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-adr-backend@0.4.6-next.1 + - @backstage/plugin-auth-backend@0.20.3-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-azure-devops-backend@0.5.1-next.1 + - @backstage/plugin-azure-sites-backend@0.1.19-next.1 + - @backstage/plugin-badges-backend@0.3.6-next.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/plugin-code-coverage-backend@0.2.23-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.1 + - @backstage/plugin-events-backend@0.2.18-next.1 + - @backstage/plugin-explore-backend@0.0.19-next.1 + - @backstage/plugin-jenkins-backend@0.3.3-next.1 + - @backstage/plugin-kafka-backend@0.3.7-next.1 + - @backstage/plugin-lighthouse-backend@0.4.1-next.1 + - @backstage/plugin-linguist-backend@0.5.6-next.1 + - @backstage/plugin-nomad-backend@0.1.11-next.1 + - @backstage/plugin-permission-backend@0.5.32-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-playlist-backend@0.3.13-next.1 + - @backstage/plugin-rollbar-backend@0.1.54-next.1 + - @backstage/plugin-scaffolder-backend@1.19.3-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.1 + - @backstage/plugin-search-backend@1.4.9-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.18-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/plugin-tech-insights-backend@0.5.23-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.1 + - @backstage/plugin-tech-insights-node@0.4.15-next.1 + - @backstage/plugin-techdocs-backend@1.9.2-next.1 + - @backstage/plugin-todo-backend@0.3.7-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-common@1.2.9 + +## example-backend-next@0.0.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app-backend@0.3.57-next.1 + - @backstage/plugin-devtools-backend@0.2.6-next.1 + - @backstage/plugin-proxy-backend@0.4.7-next.1 + - @backstage/backend-defaults@0.2.9-next.1 + - @backstage/plugin-kubernetes-backend@0.14.1-next.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-adr-backend@0.4.6-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-azure-devops-backend@0.5.1-next.1 + - @backstage/plugin-badges-backend@0.3.6-next.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.1 + - @backstage/plugin-jenkins-backend@0.3.3-next.1 + - @backstage/plugin-lighthouse-backend@0.4.1-next.1 + - @backstage/plugin-linguist-backend@0.5.6-next.1 + - @backstage/plugin-nomad-backend@0.1.11-next.1 + - @backstage/plugin-permission-backend@0.5.32-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-playlist-backend@0.3.13-next.1 + - @backstage/plugin-scaffolder-backend@1.19.3-next.1 + - @backstage/plugin-search-backend@1.4.9-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/plugin-sonarqube-backend@0.2.11-next.1 + - @backstage/plugin-techdocs-backend@1.9.2-next.1 + - @backstage/plugin-todo-backend@0.3.7-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.1 + - @backstage/plugin-permission-common@0.7.11 + +## @backstage/backend-plugin-manager@0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.2.2-next.0 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/plugin-events-backend@0.2.18-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.18-next.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-common@1.2.9 + +## e2e-test@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.9-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## techdocs-cli-embedded-app@0.2.90-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/app-defaults@1.4.7-next.1 + - @backstage/cli@0.25.1-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/test-utils@1.4.7-next.1 + - @backstage/plugin-catalog@1.16.1-next.1 + - @backstage/plugin-techdocs@1.9.3-next.1 + - @backstage/plugin-techdocs-react@1.1.15-next.1 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + +## @internal/plugin-todo-list@1.0.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## @internal/plugin-todo-list-backend@1.0.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-visualizer@0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 diff --git a/docs/releases/v1.22.0-next.2-changelog.md b/docs/releases/v1.22.0-next.2-changelog.md new file mode 100644 index 0000000000..c9dcfeff98 --- /dev/null +++ b/docs/releases/v1.22.0-next.2-changelog.md @@ -0,0 +1,1835 @@ +# Release v1.22.0-next.2 + +## @backstage/backend-app-api@0.5.10-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/cli-node@0.2.2-next.0 + - @backstage/config-loader@1.6.1-next.0 + +## @backstage/backend-common@0.20.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-dev-utils@0.1.3-next.0 + - @backstage/backend-app-api@0.5.10-next.2 + - @backstage/config-loader@1.6.1-next.0 + +## @backstage/backend-defaults@0.2.9-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-app-api@0.5.10-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/backend-dev-utils@0.1.3-next.0 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status + +## @backstage/backend-openapi-utils@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + +## @backstage/backend-plugin-api@0.6.9-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/backend-tasks@0.5.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/backend-test-utils@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-app-api@0.5.10-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/core-compat-api@0.1.1-next.2 + +### Patch Changes + +- 4c1f50c: Make `convertLegacyApp` wrap discovered routes with `compatWrapper`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + +## @backstage/create-app@0.5.9-next.2 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/dev-utils@1.0.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## @backstage/frontend-app-api@0.4.1-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + +## @backstage/frontend-plugin-api@0.4.1-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status + +## @backstage/frontend-test-utils@0.1.1-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/frontend-app-api@0.4.1-next.2 + +## @backstage/repo-tools@0.5.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/cli-node@0.2.2-next.0 + +## @techdocs/cli@1.8.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-techdocs-node@1.11.1-next.2 + +## @backstage/plugin-adr@0.6.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## @backstage/plugin-adr-backend@0.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-airbrake@0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/dev-utils@1.0.26-next.2 + +## @backstage/plugin-airbrake-backend@0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-allure@0.1.45-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-api-docs@0.10.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.16.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-app-backend@0.3.57-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-app-node@0.1.9-next.2 + - @backstage/config-loader@1.6.1-next.0 + +## @backstage/plugin-app-node@0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + +## @backstage/plugin-auth-backend@0.20.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.1-next.2 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.3-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.1.6-next.2 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.6-next.2 + - @backstage/plugin-auth-backend-module-google-provider@0.1.6-next.2 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.6-next.2 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.1-next.2 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.2-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-auth-node@0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-azure-devops@0.3.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-azure-devops-backend@0.5.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + +## @backstage/plugin-azure-sites@0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-azure-sites-backend@0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-badges@0.2.53-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-badges-backend@0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-bazaar@0.2.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-bazaar-backend@0.3.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-bitrise@0.1.56-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-catalog@1.16.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## @backstage/plugin-catalog-backend@1.16.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/backend-openapi-utils@0.1.2-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-aws@0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-azure@0.1.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/backend-openapi-utils@0.1.2-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-github@0.4.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-backend-module-github@0.4.7-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-catalog-graph@0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-catalog-import@0.10.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## @backstage/plugin-catalog-node@1.6.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## @backstage/plugin-catalog-react@1.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## @backstage/plugin-cicd-statistics@0.1.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cicd-statistics@0.1.31-next.2 + +## @backstage/plugin-circleci@0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-cloudbuild@0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-code-climate@0.1.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-code-coverage@0.2.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-code-coverage-backend@0.2.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-cost-insights@0.12.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-devtools-backend@0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/config-loader@1.6.1-next.0 + +## @backstage/plugin-dynatrace@8.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-entity-feedback@0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-entity-feedback-backend@0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-entity-validation@0.1.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-events-backend@0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + +## @backstage/plugin-events-backend-module-aws-sqs@0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-events-backend-module-azure@0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + +## @backstage/plugin-events-backend-module-gerrit@0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + +## @backstage/plugin-events-backend-module-github@0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + +## @backstage/plugin-events-backend-module-gitlab@0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + +## @backstage/plugin-events-backend-test-utils@0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.18-next.2 + +## @backstage/plugin-events-node@0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + +## @backstage/plugin-explore@0.4.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + +## @backstage/plugin-explore-backend@0.0.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.2 + +## @backstage/plugin-firehydrant@0.2.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-fossa@0.2.61-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-github-actions@0.6.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## @backstage/plugin-github-deployments@0.1.60-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## @backstage/plugin-github-issues@0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-github-pull-requests-board@0.1.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-gocd@0.1.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-graphiql@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + +## @backstage/plugin-home@0.6.1-next.2 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-home-react@0.1.7-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-home-react@0.1.7-next.2 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. + +## @backstage/plugin-ilert@0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-jenkins@0.9.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-jenkins-backend@0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## @backstage/plugin-kafka@0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-kafka-backend@0.3.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-kubernetes@0.11.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-kubernetes-backend@0.14.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-kubernetes-node@0.1.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## @backstage/plugin-kubernetes-cluster@0.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-kubernetes-node@0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + +## @backstage/plugin-lighthouse@0.4.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-lighthouse-backend@0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-linguist@0.1.14-next.2 + +### Patch Changes + +- 4f42918: Added alpha support for the New Frontend System (Declarative Integration) +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-linguist-backend@0.5.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-newrelic-dashboard@0.3.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-nomad@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-nomad-backend@0.1.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-octopus-deploy@0.2.11-next.2 + +### Patch Changes + +- 7d96ba8: added install path and fixed import on plugin-octopus-deploy +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-org@0.6.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-org-react@0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-pagerduty@0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home-react@0.1.7-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-periskop@0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-periskop-backend@0.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-permission-backend@0.5.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## @backstage/plugin-permission-node@0.7.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-playlist@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + +## @backstage/plugin-playlist-backend@0.3.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## @backstage/plugin-proxy-backend@0.4.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-puppetdb@0.1.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-rollbar@0.4.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-rollbar-backend@0.1.54-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-scaffolder@1.17.1-next.2 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.7.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## @backstage/plugin-scaffolder-backend@1.19.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.1-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.1-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.1-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.1.1-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.12-next.2 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## @backstage/plugin-scaffolder-backend-module-github@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## @backstage/plugin-scaffolder-node@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-scaffolder-react@1.7.1-next.2 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-search@1.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + +## @backstage/plugin-search-backend@1.4.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/backend-openapi-utils@0.1.2-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + +## @backstage/plugin-search-backend-module-catalog@0.1.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + +## @backstage/plugin-search-backend-module-explore@0.1.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-search-backend-module-pg@0.5.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-search-backend-module-techdocs@0.1.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + - @backstage/plugin-techdocs-node@1.11.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-search-backend-node@1.2.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-search-react@1.7.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + +## @backstage/plugin-sentry@0.5.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-sonarqube@0.7.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-sonarqube-backend@0.2.11-next.2 + +### Patch Changes + +- 53445cd: Updated README +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-splunk-on-call@0.4.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-stack-overflow@0.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-home-react@0.1.7-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + +## @backstage/plugin-stack-overflow-backend@0.2.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.2-next.2 + +## @backstage/plugin-tech-insights@0.3.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-tech-insights-backend@0.5.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/plugin-tech-insights-node@0.4.15-next.2 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-tech-insights-node@0.4.15-next.2 + +## @backstage/plugin-tech-insights-node@0.4.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-tech-radar@0.6.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + +## @backstage/plugin-techdocs@1.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.16.1-next.2 + - @backstage/plugin-techdocs@1.9.3-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## @backstage/plugin-techdocs-backend@1.9.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.2 + - @backstage/plugin-techdocs-node@1.11.1-next.2 + +## @backstage/plugin-techdocs-node@1.11.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## @backstage/plugin-todo@0.2.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-todo-backend@0.3.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/backend-openapi-utils@0.1.2-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + +## @backstage/plugin-user-settings@0.8.0-next.2 + +### Patch Changes + +- eea0849: add user-settings declarative integration core nav item +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-user-settings-backend@0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-vault@0.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## @backstage/plugin-vault-backend@0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-vault-node@0.1.2-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## @backstage/plugin-vault-node@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + +## example-app@0.2.91-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-user-settings@0.8.0-next.2 + - @backstage/plugin-octopus-deploy@0.2.11-next.2 + - @backstage/frontend-app-api@0.4.1-next.2 + - @backstage/plugin-home@0.6.1-next.2 + - @backstage/plugin-scaffolder-react@1.7.1-next.2 + - @backstage/plugin-scaffolder@1.17.1-next.2 + - @backstage/plugin-linguist@0.1.14-next.2 + - @backstage/plugin-catalog@1.16.1-next.2 + - @backstage/plugin-catalog-import@0.10.5-next.2 + - @backstage/plugin-graphiql@0.3.2-next.2 + - @backstage/plugin-search@1.4.5-next.2 + - @backstage/plugin-tech-radar@0.6.12-next.2 + - @backstage/plugin-techdocs@1.9.3-next.2 + - @backstage/plugin-adr@0.6.12-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-explore@0.4.15-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + - @backstage/plugin-stack-overflow@0.1.24-next.2 + - @backstage/cli@0.25.1-next.1 + - @backstage/plugin-pagerduty@0.7.1-next.2 + - @backstage/plugin-api-docs@0.10.3-next.2 + - @backstage/plugin-catalog-graph@0.3.3-next.2 + - @backstage/plugin-org@0.6.19-next.2 + - @backstage/plugin-airbrake@0.3.29-next.2 + - @backstage/plugin-azure-devops@0.3.11-next.2 + - @backstage/plugin-azure-sites@0.1.18-next.2 + - @backstage/plugin-badges@0.2.53-next.2 + - @backstage/plugin-cloudbuild@0.3.29-next.2 + - @backstage/plugin-code-coverage@0.2.22-next.2 + - @backstage/plugin-cost-insights@0.12.18-next.2 + - @backstage/plugin-dynatrace@8.0.3-next.2 + - @backstage/plugin-entity-feedback@0.2.12-next.2 + - @backstage/plugin-github-actions@0.6.10-next.2 + - @backstage/plugin-gocd@0.1.35-next.2 + - @backstage/plugin-jenkins@0.9.4-next.2 + - @backstage/plugin-kafka@0.3.29-next.2 + - @backstage/plugin-kubernetes@0.11.4-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.5-next.2 + - @backstage/plugin-lighthouse@0.4.14-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.2 + - @backstage/plugin-nomad@0.1.10-next.2 + - @backstage/plugin-playlist@0.2.3-next.2 + - @backstage/plugin-puppetdb@0.1.12-next.2 + - @backstage/plugin-rollbar@0.4.29-next.2 + - @backstage/plugin-sentry@0.5.14-next.2 + - @backstage/plugin-tech-insights@0.3.21-next.2 + - @backstage/plugin-todo@0.2.33-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-apache-airflow@0.2.19-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.1 + - @backstage/plugin-devtools@0.1.8-next.1 + - @backstage/plugin-gcalendar@0.3.22-next.1 + - @backstage/plugin-gcp-projects@0.3.45-next.1 + - @backstage/plugin-microsoft-calendar@0.1.11-next.1 + - @backstage/plugin-newrelic@0.3.44-next.1 + - @backstage/plugin-shortcuts@0.3.18-next.1 + - @backstage/plugin-stackstorm@0.1.10-next.1 + +## example-app-next@0.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/plugin-user-settings@0.8.0-next.2 + - @backstage/plugin-octopus-deploy@0.2.11-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/frontend-app-api@0.4.1-next.2 + - @backstage/plugin-home@0.6.1-next.2 + - @backstage/plugin-scaffolder-react@1.7.1-next.2 + - @backstage/plugin-scaffolder@1.17.1-next.2 + - @backstage/plugin-linguist@0.1.14-next.2 + - @backstage/plugin-catalog@1.16.1-next.2 + - @backstage/plugin-catalog-import@0.10.5-next.2 + - @backstage/plugin-graphiql@0.3.2-next.2 + - @backstage/plugin-search@1.4.5-next.2 + - @backstage/plugin-tech-radar@0.6.12-next.2 + - @backstage/plugin-techdocs@1.9.3-next.2 + - app-next-example-plugin@0.0.5-next.2 + - @backstage/plugin-adr@0.6.12-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-explore@0.4.15-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + - @backstage/plugin-visualizer@0.0.2-next.2 + - @backstage/cli@0.25.1-next.1 + - @backstage/plugin-pagerduty@0.7.1-next.2 + - @backstage/plugin-api-docs@0.10.3-next.2 + - @backstage/plugin-catalog-graph@0.3.3-next.2 + - @backstage/plugin-org@0.6.19-next.2 + - @backstage/plugin-airbrake@0.3.29-next.2 + - @backstage/plugin-azure-devops@0.3.11-next.2 + - @backstage/plugin-azure-sites@0.1.18-next.2 + - @backstage/plugin-badges@0.2.53-next.2 + - @backstage/plugin-cloudbuild@0.3.29-next.2 + - @backstage/plugin-code-coverage@0.2.22-next.2 + - @backstage/plugin-cost-insights@0.12.18-next.2 + - @backstage/plugin-dynatrace@8.0.3-next.2 + - @backstage/plugin-entity-feedback@0.2.12-next.2 + - @backstage/plugin-github-actions@0.6.10-next.2 + - @backstage/plugin-gocd@0.1.35-next.2 + - @backstage/plugin-jenkins@0.9.4-next.2 + - @backstage/plugin-kafka@0.3.29-next.2 + - @backstage/plugin-kubernetes@0.11.4-next.2 + - @backstage/plugin-lighthouse@0.4.14-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.2 + - @backstage/plugin-playlist@0.2.3-next.2 + - @backstage/plugin-puppetdb@0.1.12-next.2 + - @backstage/plugin-rollbar@0.4.29-next.2 + - @backstage/plugin-sentry@0.5.14-next.2 + - @backstage/plugin-tech-insights@0.3.21-next.2 + - @backstage/plugin-todo@0.2.33-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-apache-airflow@0.2.19-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.1 + - @backstage/plugin-devtools@0.1.8-next.1 + - @backstage/plugin-gcalendar@0.3.22-next.1 + - @backstage/plugin-gcp-projects@0.3.45-next.1 + - @backstage/plugin-microsoft-calendar@0.1.11-next.1 + - @backstage/plugin-newrelic@0.3.44-next.1 + - @backstage/plugin-shortcuts@0.3.18-next.1 + - @backstage/plugin-stackstorm@0.1.10-next.1 + +## app-next-example-plugin@0.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + +## example-backend@0.2.91-next.2 + +### Patch Changes + +- Updated dependencies + - example-app@0.2.91-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-adr-backend@0.4.6-next.2 + - @backstage/plugin-app-backend@0.3.57-next.2 + - @backstage/plugin-auth-backend@0.20.3-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-azure-devops-backend@0.5.1-next.2 + - @backstage/plugin-badges-backend@0.3.6-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-code-coverage-backend@0.2.23-next.2 + - @backstage/plugin-devtools-backend@0.2.6-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.2 + - @backstage/plugin-events-backend@0.2.18-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/plugin-jenkins-backend@0.3.3-next.2 + - @backstage/plugin-kafka-backend@0.3.7-next.2 + - @backstage/plugin-kubernetes-backend@0.14.1-next.2 + - @backstage/plugin-lighthouse-backend@0.4.1-next.2 + - @backstage/plugin-linguist-backend@0.5.6-next.2 + - @backstage/plugin-nomad-backend@0.1.11-next.2 + - @backstage/plugin-permission-backend@0.5.32-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-playlist-backend@0.3.13-next.2 + - @backstage/plugin-proxy-backend@0.4.7-next.2 + - @backstage/plugin-scaffolder-backend@1.19.3-next.2 + - @backstage/plugin-search-backend@1.4.9-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.18-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + - @backstage/plugin-techdocs-backend@1.9.2-next.2 + - @backstage/plugin-todo-backend@0.3.7-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/plugin-azure-sites-backend@0.1.19-next.2 + - @backstage/plugin-explore-backend@0.0.19-next.2 + - @backstage/plugin-rollbar-backend@0.1.54-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.2 + - @backstage/plugin-tech-insights-backend@0.5.23-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.2 + - @backstage/plugin-tech-insights-node@0.4.15-next.2 + +## example-backend-next@0.0.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-sonarqube-backend@0.2.11-next.2 + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-defaults@0.2.9-next.2 + - @backstage/plugin-adr-backend@0.4.6-next.2 + - @backstage/plugin-app-backend@0.3.57-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-azure-devops-backend@0.5.1-next.2 + - @backstage/plugin-badges-backend@0.3.6-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.2 + - @backstage/plugin-devtools-backend@0.2.6-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.2 + - @backstage/plugin-jenkins-backend@0.3.3-next.2 + - @backstage/plugin-kubernetes-backend@0.14.1-next.2 + - @backstage/plugin-lighthouse-backend@0.4.1-next.2 + - @backstage/plugin-linguist-backend@0.5.6-next.2 + - @backstage/plugin-nomad-backend@0.1.11-next.2 + - @backstage/plugin-permission-backend@0.5.32-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-playlist-backend@0.3.13-next.2 + - @backstage/plugin-proxy-backend@0.4.7-next.2 + - @backstage/plugin-scaffolder-backend@1.19.3-next.2 + - @backstage/plugin-search-backend@1.4.9-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + - @backstage/plugin-techdocs-backend@1.9.2-next.2 + - @backstage/plugin-todo-backend@0.3.7-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.2 + +## @backstage/backend-plugin-manager@0.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-events-backend@0.2.18-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/cli-node@0.2.2-next.0 + +## e2e-test@0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.9-next.2 + +## techdocs-cli-embedded-app@0.2.90-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.16.1-next.2 + - @backstage/plugin-techdocs@1.9.3-next.2 + - @backstage/cli@0.25.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + +## @internal/plugin-todo-list-backend@1.0.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## @backstage/plugin-visualizer@0.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 diff --git a/docs/releases/v1.22.0.md b/docs/releases/v1.22.0.md new file mode 100644 index 0000000000..e5ce345ccc --- /dev/null +++ b/docs/releases/v1.22.0.md @@ -0,0 +1,69 @@ +--- +id: v1.22.0 +title: v1.22.0 +description: Backstage Release v1.22.0 +--- + +These are the release notes for the v1.22.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### Updates to new frontend system + +There have been several updates to alpha packages in the new frontend system including a breaking change where the `app/router` extension was renamed to `app/root`. Furthermore `elements`, `wrappers`, and `router` were added as inputs to `app/root` making it possible to pass extensions into the root of the app. + +### Plugins and modules migrated to the New Backend System + +Some more features have been migrated to the new backend system: + +- `@backstage/plugin-auth-backend-module-microsoft-provider` +- `@backstage/plugin-auth-backend-module-pinniped-provider` +- `@backstage/plugin-catalog-backend-module-openapi` +- `@backstage/plugin-events-backend-module-azure` +- `@backstage/plugin-events-backend-module-bitbucket-cloud` +- `@backstage/plugin-events-backend-module-gerrit` +- `@backstage/plugin-linguist` + +### New plugin: App Visualizer + +This release includes the new `@backstage/plugin-app-visualizer` package. This plugin for the new frontend system lets you browse and view the extension structure of your app as a graph, detailed list, or in text form. + +### New feature: Dynamic Feature Service + +This release includes the new `@backstage/backend-dynamic-feature-service` package. +It is a new and experimental service that lets you dynamically detect and load local plugins and modules in your Backstage instance. + +Contributed by [@davidfestal](https://github.com/davidfestal) in [#18862](https://github.com/backstage/backstage/pull/18862) + +### New Scaffolder action `gitlab:issues:create` + +You can now create GitHub issues in your scaffolder flows! Contributed by [@elaine-mattos](https://github.com/elaine-mattos) in [#21929](https://github.com/backstage/backstage/pull/21929) + +### New Scaffolder action `gitlab:repo:push` + +You can now push raw branches to GitLab in your scaffolder flows! Contributed by [@gavlyukovskiy](https://github.com/gavlyukovskiy) in [#21977](https://github.com/backstage/backstage/pull/21977) + +## Security Fixes + +This release does not contain any security fixes. + +However, some updates were made to the build facilities in the CLI and the caches in the backend system, such that you can now perform builds on FIPS compliant systems. This may lead to some internal cache invalidation happening, since the hashing algorithms used were updated. This should not pose a problem unless caches were being used as reliable persistent storage systems. Please let us know if you encounter any issues that may be related to this. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest release. For more guidance on how to upgrade, check out the documentation for [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and start using this new release. + +- [Backstage official website](https://backstage.io/), [documentation](https://backstage.io/docs/), and [getting started guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/backstage-687207715902193673) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.22.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), [Blog](https://backstage.io/blog), [Roadmap](https://backstage.io/docs/overview/roadmap) and [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://info.backstage.spotify.com/newsletter_subscribe) if you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.23.0-next.0-changelog.md b/docs/releases/v1.23.0-next.0-changelog.md new file mode 100644 index 0000000000..59257b1030 --- /dev/null +++ b/docs/releases/v1.23.0-next.0-changelog.md @@ -0,0 +1,2958 @@ +# Release v1.23.0-next.0 + +## @backstage/backend-common@0.21.0-next.0 + +### Minor Changes + +- e85aa98: drop databases after unit tests if the database instance is not running in docker + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.11-next.0 + - @backstage/config-loader@1.6.1 + - @backstage/backend-dev-utils@0.1.3 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.3.0-next.0 + +### Minor Changes + +- e85aa98: drop databases after unit tests if the database instance is not running in docker + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-app-api@0.5.11-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/catalog-client@1.6.0-next.0 + +### Minor Changes + +- 04907c3: Updates the OpenAPI specification title to plugin ID instead of package name. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## @backstage/frontend-app-api@0.6.0-next.0 + +### Minor Changes + +- 86346c2: **BREAKING**: The `app.routes.bindings` app-config mapping has been simplified. You now only need to specify the plugin ID and route ID on both sides of the mapping. + + Old form: + + ```yaml + app: + routes: + bindings: + plugin.catalog.externalRoutes.viewTechDoc: plugin.techdocs.routes.docRoot + plugin.catalog.externalRoutes.createComponent: plugin.catalog-import.routes.importPage + ``` + + New form: + + ```yaml + app: + routes: + bindings: + catalog.viewTechDoc: techdocs.docRoot + catalog.createComponent: catalog-import.importPage + ``` + +### Patch Changes + +- 42ebf27: Added `IconsApi` implementation and the ability to configure icons through the `icons` option for `createApp` and `createSpecializedApp`. This is not a long-term solution as icons should be installable via extensions instead. This is just a stop-gap to make sure there is feature parity with the existing frontend system. +- e0a4dd1: Improved the error message when data input/output shapes do not match +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/repo-tools@0.6.0-next.0 + +### Minor Changes + +- 04907c3: Updates the OpenAPI client template to support the new format for identifying plugin ID. You should now use `info.title` like so, + + ```diff + info: + + title: yourPluginId + - title: @internal/plugin-*-backend + + servers: + - / + - - yourPluginId + ``` + +- b10c603: Add support for `oneOf` in client generated by `schema openapi generate-client`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/cli-node@0.2.2 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## @backstage/test-utils@1.5.0-next.0 + +### Minor Changes + +- bb40898: Added `components` option to `TestAppOptions`, which will be forwarded as the `components` option to `createApp`. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-react@0.4.19 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.1.0-next.0 + +### Minor Changes + +- 5d2fcba: Created new `@backstage/plugin-auth-backend-module-oidc-provider` module package to house oidc auth provider migration. + +### Patch Changes + +- 8afb6f4: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.4-next.0 + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## @backstage/plugin-catalog@1.17.0-next.0 + +### Minor Changes + +- e541c0e: Exported `CatalogTable.defaultColumnsFunc` for defining the columns in `` of some Kinds while using the default columns for the others. + +### Patch Changes + +- 916da47: Change default icon for unknown entities to nothing instead of the help icon. +- f899eec: Change default icon for `kind:resource` to the storage icon. +- 912ca7b: Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. +- 797a329: Fixed inconsistencies in icons used for System and Template +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration-react@1.1.23 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-catalog-backend@1.17.0-next.0 + +### Minor Changes + +- 126c2f9: Updates the OpenAPI spec to use plugin as `info.title` instead of package name. +- 04907c3: Updates the OpenAPI specification title to plugin ID instead of package name. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-openapi-utils@0.1.3-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-events-node@0.2.19-next.0 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-cloudbuild@0.4.0-next.0 + +### Minor Changes + +- 0328d1b: Changed build list view to automatically filter builds based on repository name matching component-info's metadata.name. + Added optional `google.com/cloudbuild-repo-name` annotation which allows you to specify a different repository to filter on. + Added optional `google.com/cloudbuild-trigger-name` annotation which allows you to filter based on a trigger name instead of a repo name. + Updated the ReadMe with information about the filtering and some other minor verbiage updates. + Changed `substitutions.BRANCH_NAME` to `substitutions.REF_NAME` so that the Ref field is populated properly. + Added optional `google.com/cloudbuild-location` annotation which allows you to specify the Cloud Build location of your builds. Default is global scope. + Changed build list view to show builds in a specific location if the location annotation is used. + Updated ReadMe with information about the use of the location filtering. + +### Patch Changes + +- ef3cad4: Add telemetry HTTP header Google Cloud Platform +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-kubernetes-react@0.3.0-next.0 + +### Minor Changes + +- 0d526c8: **BREAKING** The pod exec terminal is now disabled by default since there are several scenarios where it is known not to work. It can be re-enabled at your own risk by setting the config parameter `kubernetes.podExecTerminal.enabled` to `true`. + +### Patch Changes + +- 536f67d: Fix broken XtermJS CSS import +- db1054b: Fixed a bug where the logs dialog and any other functionality depending on the proxy endpoint would fail for clusters configured with the OIDC auth provider. +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder@1.18.0-next.0 + +### Minor Changes + +- c56f1a2: Remove the old legacy exports from `/alpha` +- a86920b: Introduced a new `MultiEntityPicker` field that supports selecting multiple Entities + +### Patch Changes + +- b0d1d80: Added basic support for the new frontend system, exported from the `/alpha` subpath. +- 912ca7b: Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. +- da059d7: Removed alpha symbol from Task List header +- 6a74ffd: Updated dependency `@rjsf/utils` to `5.16.1`. + Updated dependency `@rjsf/core` to `5.16.1`. + Updated dependency `@rjsf/material-ui` to `5.16.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. +- 11b9a08: Introduced the first version of recoverable tasks. +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-scaffolder-react@1.8.0-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.23 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-react@0.4.19 + +## @backstage/plugin-scaffolder-backend@1.21.0-next.0 + +### Minor Changes + +- 11b9a08: Introduced the first version of recoverable tasks. +- e9a5228: The built-in module list has been trimmed down when using the new Backend System. Provider specific modules should now be installed with `backend.add` to provide additional actions to the scaffolder. These modules are as follows: + + - `@backstage/plugin-scaffolder-backend-module-github` + - `@backstage/plugin-scaffolder-backend-module-gitlab` + - `@backstage/plugin-scaffolder-backend-module-bitbucket` + - `@backstage/plugin-scaffolder-backend-module-gitea` + - `@backstage/plugin-scaffolder-backend-module-gerrit` + - `@backstage/plugin-scaffolder-backend-module-confluence-to-markdown` + - `@backstage/plugin-scaffolder-backend-module-cookiecutter` + - `@backstage/plugin-scaffolder-backend-module-rails` + - `@backstage/plugin-scaffolder-backend-module-sentry` + - `@backstage/plugin-scaffolder-backend-module-yeoman` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.2-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.2-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.1.2-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.13-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.2-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-scaffolder-common@1.5.0-next.0 + +### Minor Changes + +- 11b9a08: Introduced the first version of recoverable tasks. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-scaffolder-node@0.3.0-next.0 + +### Minor Changes + +- 3a9ba42: Added functions to clone a repo, create a branch, add files and push and commit to the branch. This allows for files to be added to the a PR for use in the bitbucket pull request action for issue #21762 +- 11b9a08: Introduced the first version of recoverable tasks. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-react@1.8.0-next.0 + +### Minor Changes + +- c56f1a2: Remove the old legacy exports from `/alpha` +- 11b9a08: Introduced the first version of recoverable tasks. + +### Patch Changes + +- 0b0c6b6: Allow defining default output text to be shown +- 6a74ffd: Updated dependency `@rjsf/utils` to `5.16.1`. + Updated dependency `@rjsf/core` to `5.16.1`. + Updated dependency `@rjsf/material-ui` to `5.16.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/plugin-search-backend@1.5.0-next.0 + +### Minor Changes + +- 126c2f9: Updates the OpenAPI spec to use plugin as `info.title` instead of package name. +- 04907c3: Updates the OpenAPI specification title to plugin ID instead of package name. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-openapi-utils@0.1.3-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/app-defaults@1.4.8-next.0 + +### Patch Changes + +- f899eec: Change default icon for `kind:resource` to the storage icon. +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/plugin-permission-react@0.4.19 + +## @backstage/backend-app-api@0.5.11-next.0 + +### Patch Changes + +- e0c18ef: Include the extension point ID and the module ID in the backend init error message. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/cli-node@0.2.2 + - @backstage/config-loader@1.6.1 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-app-api@0.5.11-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## @backstage/backend-dynamic-feature-service@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/cli-node@0.2.2 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-events-backend@0.2.19-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.19-next.0 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/backend-openapi-utils@0.1.3-next.0 + +### Patch Changes + +- 2067689: Internal updates due to `json-schema-to-ts` +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/errors@1.2.3 + +## @backstage/backend-plugin-api@0.6.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/backend-tasks@0.5.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/cli@0.25.2-next.0 + +### Patch Changes + +- c624938: Add experimental support for optional `auth` app entry point. +- acd2860: Updated dependency `vite-plugin-node-polyfills` to `^0.19.0`. +- 35725e2: Updated dependencies in frontend plugin templates +- c7259dc: Updated the backend module template to make the module instance the package default export. +- Updated dependencies + - @backstage/eslint-plugin@0.1.5-next.0 + - @backstage/cli-node@0.2.2 + - @backstage/config-loader@1.6.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/core-compat-api@0.1.2-next.0 + +### Patch Changes + +- 1fa5041: The backwards compatibility provider will now use the new `ComponentsApi` and `IconsApi` when implementing the old `AppContext`. +- 7155c30: Added `convertLegacyRouteRefs` for bulk conversion of plugin routes. +- 2f2a1d2: Plugins converted by `convertLegacyApp` now have their `routes` and `externalRoutes` included as well, allowing them to be used to bind external routes in configuration. +- 1184990: collectLegacyRoutes throws in case invalid element is found +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/version-bridge@1.0.7 + +## @backstage/create-app@0.5.11-next.0 + +### Patch Changes + +- aeec29c: Updated `packages/app` as well as the root `package.json` type resolutions to use React v18. + + The `@testing-library/*` dependencies have also been updated to the ones compatible with React v18, and the test at `packages/app/src/App.test.tsx` had been updated to use more modern patterns that work better with these new versions. + + For information on how to migrate existing apps to React v18, see the [migration guide](https://backstage.io/docs/tutorials/react18-migration) + +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## @backstage/dev-utils@1.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/app-defaults@1.4.8-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration-react@1.1.23 + - @backstage/theme@0.5.0 + +## @backstage/eslint-plugin@0.1.5-next.0 + +### Patch Changes + +- 995d280: Added new `@backstage/no-top-level-material-ui-4-imports` rule that forbids top level imports from Material UI v4 packages + +## @backstage/frontend-plugin-api@0.5.1-next.0 + +### Patch Changes + +- 7eae3e0: Added initial `IconsApi` definition. +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## @backstage/frontend-test-utils@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.6.0-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/test-utils@1.5.0-next.0 + - @backstage/types@1.1.1 + +## @techdocs/cli@1.8.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-techdocs-node@1.11.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## @backstage/plugin-adr@0.6.13-next.0 + +### Patch Changes + +- 0b03962: Updated README +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration-react@1.1.23 + - @backstage/plugin-adr-common@0.2.19 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-adr-backend@0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-adr-common@0.2.19 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-airbrake@0.3.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/test-utils@1.5.0-next.0 + - @backstage/dev-utils@1.0.27-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-airbrake-backend@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-allure@0.1.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-api-docs@0.10.4-next.0 + +### Patch Changes + +- 170c023: Adding `supportedSubmitMethods` prop to `api-docs` to pass to the Swagger UI. This allows users to specify which HTTP methods they wish to allow end-users to make requests through the `Try It Out` button on the Swagger UI. +- c03f977: Updated dependency `graphiql` to `3.1.0`. +- 49b3b5e: Updated dependency `@asyncapi/react-component` to `1.2.13`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-catalog@1.17.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-react@0.4.19 + +## @backstage/plugin-app-backend@0.3.58-next.0 + +### Patch Changes + +- 9dfd57d: Do not force caching of the Javascript asset that contains the injected config. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/config-loader@1.6.1 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.10-next.0 + +## @backstage/plugin-app-node@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + +## @backstage/plugin-app-visualizer@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-auth-backend@0.20.4-next.0 + +### Patch Changes + +- a3f1fa3: Use the externalized `auth-backend-module-microsoft-provider` again. +- 5d2fcba: Migrated oidc auth provider to new `@backstage/plugin-auth-backend-module-oidc-provider` module package. +- Updated dependencies + - @backstage/plugin-auth-backend-module-okta-provider@0.0.3-next.0 + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.0-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.5-next.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.7-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.7-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.7-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.7-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.4-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.2-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.5-next.0 + +### Patch Changes + +- 1ff2684: Added the possibility to use custom scopes for performing login with Microsoft EntraID. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/errors@1.2.3 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.3-next.0 + +### Patch Changes + +- cd5114c: Added missing `additionalScopes` option to configuration schema. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-auth-node@0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-azure-devops@0.3.12-next.0 + +### Patch Changes + +- 995d280: Updated imports from named to default imports to help with the Material UI v4 to v5 migration +- cb0afaa: Prefer `dev.azure.com/build-definition` annotation when it is provided, as it is more specific than `dev.azure.com/project-repo`. This can also be used as a filter for mono-repos. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-azure-devops-common@0.3.2 + +## @backstage/plugin-azure-devops-backend@0.5.2-next.0 + +### Patch Changes + +- 353244d: Added a note about Service Principles +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-azure-devops-common@0.3.2 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-azure-sites@0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-azure-sites-backend@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## @backstage/plugin-badges@0.2.54-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-badges-backend@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bazaar@0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-bazaar-backend@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-bitbucket-cloud-common@0.2.16-next.0 + +### Patch Changes + +- 2e6af00: Updated dependency `ts-morph` to `^21.0.0`. +- Updated dependencies + - @backstage/integration@1.8.0 + +## @backstage/plugin-bitrise@0.1.57-next.0 + +### Patch Changes + +- e24e4d3: Update README +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-catalog-backend-module-aws@0.3.4-next.0 + +### Patch Changes + +- a81b1ba: The default EKS cluster entity transformer now sets the new + `kubernetes.io/x-k8s-aws-id` annotation. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-backend-module-azure@0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-openapi-utils@0.1.3-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.16-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.16-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-events-node@0.2.19-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-catalog-backend-module-github@0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-events-node@0.2.19-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-backend-module-github@0.4.8-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.7-next.0 + +### Patch Changes + +- 60e4c2a: Added the option to provide custom `groupTransformer`, `userTransformer` and `groupNameTransformer` to allow custom transformations of groups and users +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-events-node@0.2.19-next.0 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + +## @backstage/plugin-catalog-graph@0.3.4-next.0 + +### Patch Changes + +- 916da47: Change default icon for unknown entities to nothing instead of the help icon. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.10.6-next.0 + +### Patch Changes + +- 916da47: Change default icon for unknown entities to nothing instead of the help icon. +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.23 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-catalog-node@1.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-catalog-react@1.9.4-next.0 + +### Patch Changes + +- 916da47: Change default icon for unknown entities to nothing instead of the help icon. +- 71c6d7a: Overflowing labels in OwnerPicker (Catalog) are now truncated. Hovering over them shows the full label +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration-react@1.1.23 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-react@0.4.19 + +## @backstage/plugin-cicd-statistics@0.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cicd-statistics@0.1.32-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-circleci@0.3.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-code-climate@0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-code-coverage@0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-code-coverage-backend@0.2.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-cost-insights@0.12.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/plugin-cost-insights-common@0.1.2 + +## @backstage/plugin-devtools@0.1.9-next.0 + +### Patch Changes + +- c12a86c: Refactored code to improve accessibility by moving elements outside the `ul` tag and placing them appropriately. Also adjusted theme to offer better contrast. +- b89d8be: Added alpha support for the New Frontend System (Declarative Integration) +- 995d280: Updated imports from named to default imports to help with the Material UI v4 to v5 migration +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-devtools-common@0.1.8 + - @backstage/plugin-permission-react@0.4.19 + +## @backstage/plugin-devtools-backend@0.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/config-loader@1.6.1 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.8 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-dynatrace@8.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-entity-feedback@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-feedback-backend@0.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## @backstage/plugin-entity-validation@0.1.15-next.0 + +### Patch Changes + +- 916da47: Change default icon for unknown entities to nothing instead of the help icon. +- 1f70e46: Improves UX of the EntityValidationPage: Moves the validate button below the EntityTextArea which is actually validated, the location TextField can now be hidden to prevent confusion about what is validated and additional content can be added to the top of the validation page. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-events-backend@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.19-next.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.19-next.0 + +## @backstage/plugin-events-backend-module-azure@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/plugin-events-node@0.2.19-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/plugin-events-node@0.2.19-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/plugin-events-node@0.2.19-next.0 + +## @backstage/plugin-events-backend-module-github@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.19-next.0 + +## @backstage/plugin-events-backend-module-gitlab@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.19-next.0 + +## @backstage/plugin-events-backend-test-utils@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.19-next.0 + +## @backstage/plugin-events-node@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + +## @backstage/plugin-explore@0.4.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-explore-react@0.0.35 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-explore-backend@0.0.20-next.0 + +### Patch Changes + +- fd3d51c: Add support for the new backend system. + + A new backend plugin for the explore backend + was added and exported as `default`. + + You can use it with the new backend system like + + ```ts title="packages/backend/src/index.ts" + backend.add(import('@backstage/plugin-explore-backend')); + ``` + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + +## @backstage/plugin-firehydrant@0.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-fossa@0.2.62-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-github-actions@0.6.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.23 + +## @backstage/plugin-github-deployments@0.1.61-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.23 + +## @backstage/plugin-github-issues@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-github-pull-requests-board@0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration@1.8.0 + +## @backstage/plugin-gocd@0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-graphiql@0.3.3-next.0 + +### Patch Changes + +- 912ca7b: Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-home@0.6.2-next.0 + +### Patch Changes + +- 6a74ffd: Updated dependency `@rjsf/utils` to `5.16.1`. + Updated dependency `@rjsf/core` to `5.16.1`. + Updated dependency `@rjsf/material-ui` to `5.16.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. +- e9cdfd3: Fix typo in VisitsStorageApi +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-home-react@0.1.8-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + +## @backstage/plugin-home-react@0.1.8-next.0 + +### Patch Changes + +- 6a74ffd: Updated dependency `@rjsf/utils` to `5.16.1`. + Updated dependency `@rjsf/core` to `5.16.1`. + Updated dependency `@rjsf/material-ui` to `5.16.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-ilert@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-jenkins@0.9.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.23 + +## @backstage/plugin-jenkins-backend@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.23 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-kafka@0.3.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-kafka-backend@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-kubernetes@0.11.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-kubernetes-react@0.3.0-next.0 + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-kubernetes-backend@0.14.2-next.0 + +### Patch Changes + +- 7233f57: Fixed an issue where a misleading error message would be logged when an + unsupported service locator method was specified. +- a775596: Enabled a way to include custom auth metadata info on the clusters endpoint. If you want to implement a Kubernetes auth strategy which requires surfacing custom auth metadata to the frontend, use the new presentAuthMetadata method on the AuthenticationStrategy interface. +- 7278d80: The purpose of this patch is to add a new login method which is `googleServiceAccount` configuring the kubernetes properties in the app-config.yaml file with authProvider key +- daad576: Clusters configured with the `aws` authentication strategy can now customize the + `x-k8s-aws-id` header value used to generate tokens. This value can be specified + specified via the `kubernetes.io/x-k8s-aws-id` parameter (in + `metadata.annotations` for clusters in the catalog, or the `authMetadata` block + on clusters in the app-config). This is particularly helpful when a Backstage + instance contains multiple AWS clusters with the same name in different regions + \-- using this new parameter, the clusters can be given different logical names + to distinguish them but still use the same ID for the purposes of generating + tokens. +- f180cba: Enabling authentication to kubernetes clusters with mTLS x509 client certs +- 7f6ff25: Custom per-cluster auth metadata (mainly for use with custom `AuthenticationStrategy` implementations) can now be specified in the `authMetadata` property of clusters in the app-config. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-kubernetes-node@0.1.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-kubernetes-cluster@0.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-kubernetes-react@0.3.0-next.0 + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-kubernetes-common@0.7.4-next.0 + +### Patch Changes + +- daad576: Clusters configured with the `aws` authentication strategy can now customize the + `x-k8s-aws-id` header value used to generate tokens. This value can be specified + specified via the `kubernetes.io/x-k8s-aws-id` parameter (in + `metadata.annotations` for clusters in the catalog, or the `authMetadata` block + on clusters in the app-config). This is particularly helpful when a Backstage + instance contains multiple AWS clusters with the same name in different regions + \-- using this new parameter, the clusters can be given different logical names + to distinguish them but still use the same ID for the purposes of generating + tokens. +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-kubernetes-node@0.1.4-next.0 + +### Patch Changes + +- a775596: Enabled a way to include custom auth metadata info on the clusters endpoint. If you want to implement a Kubernetes auth strategy which requires surfacing custom auth metadata to the frontend, use the new presentAuthMetadata method on the AuthenticationStrategy interface. +- f180cba: Enabling authentication to kubernetes clusters with mTLS x509 client certs +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-lighthouse@0.4.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-lighthouse-backend@0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## @backstage/plugin-linguist@0.1.15-next.0 + +### Patch Changes + +- 995d280: Updated imports from named to default imports to help with the Material UI v4 to v5 migration +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-linguist-backend@0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-linguist-common@0.1.2 + +## @backstage/plugin-newrelic-dashboard@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-nomad@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-nomad-backend@0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-octopus-deploy@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-org@0.6.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-common@1.0.20 + +## @backstage/plugin-org-react@0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-pagerduty@0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-home-react@0.1.8-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-periskop@0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-periskop-backend@0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-permission-backend@0.5.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-permission-node@0.7.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-playlist@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-playlist-common@0.1.14 + +## @backstage/plugin-playlist-backend@0.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-playlist-common@0.1.14 + +## @backstage/plugin-proxy-backend@0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-puppetdb@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-rollbar@0.4.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-rollbar-backend@0.1.55-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/config@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.2-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.2-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- fc98bb6: Enhanced the pull request action to allow for adding new content to the PR as described in this issue #21762 +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.34-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.2-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.1.2-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.2.13-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.27-next.0 + +### Patch Changes + +- e9a5228: Make `containerRunner` argument optional +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.18-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.31-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-search@1.4.6-next.0 + +### Patch Changes + +- 912ca7b: Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-module-catalog@0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-module-elasticsearch@1.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-module-explore@0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-module-pg@0.5.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-module-techdocs@0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-techdocs-node@1.11.2-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-backend-node@1.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-search-react@1.7.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-sentry@0.5.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-signals@0.0.1-next.0 + +### Patch Changes + +- 047bead: Add support to subscribe and publish messages through signals plugins +- Updated dependencies + - @backstage/plugin-signals-react@0.0.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-signals-backend@0.0.1-next.0 + +### Patch Changes + +- 047bead: Add support to subscribe and publish messages through signals plugins +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-signals-node@0.0.1-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.19-next.0 + +## @backstage/plugin-signals-node@0.0.1-next.0 + +### Patch Changes + +- 047bead: Add support to subscribe and publish messages through signals plugins +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.19-next.0 + +## @backstage/plugin-signals-react@0.0.1-next.0 + +### Patch Changes + +- 047bead: Add support to subscribe and publish messages through signals plugins +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/types@1.1.1 + +## @backstage/plugin-sonarqube@0.7.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-sonarqube-react@0.1.12 + +## @backstage/plugin-sonarqube-backend@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## @backstage/plugin-splunk-on-call@0.4.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-stack-overflow@0.1.25-next.0 + +### Patch Changes + +- c1bc331: Fixes a bug that made the API return questions not related to the tags provided +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/plugin-home-react@0.1.8-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-stack-overflow-backend@0.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.3-next.0 + +## @backstage/plugin-tech-insights@0.3.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend@0.5.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-tech-insights-node@0.4.16-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-tech-insights-node@0.4.16-next.0 + - @backstage/errors@1.2.3 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-insights-node@0.4.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-tech-insights-common@0.2.12 + +## @backstage/plugin-tech-radar@0.6.13-next.0 + +### Patch Changes + +- 912ca7b: Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## @backstage/plugin-techdocs@1.9.4-next.0 + +### Patch Changes + +- 912ca7b: Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.23 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-common@1.2.10 + - @backstage/plugin-techdocs-react@1.1.15 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-catalog@1.17.0-next.0 + - @backstage/plugin-techdocs@1.9.4-next.0 + - @backstage/test-utils@1.5.0-next.0 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration-react@1.1.23 + - @backstage/plugin-techdocs-react@1.1.15 + +## @backstage/plugin-techdocs-backend@1.9.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.0 + - @backstage/plugin-techdocs-node@1.11.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-common@0.7.12 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.5-next.0 + +### Patch Changes + +- 131ffdc: Fix position of the ReportIssue component when is displaying at the top of the container. +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.23 + - @backstage/plugin-techdocs-react@1.1.15 + +## @backstage/plugin-techdocs-node@1.11.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-search-common@1.2.10 + +## @backstage/plugin-todo@0.2.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-todo-backend@0.3.8-next.0 + +### Patch Changes + +- 126c2f9: Updates the OpenAPI spec to use plugin as `info.title` instead of package name. +- 04907c3: Updates the OpenAPI specification title to plugin ID instead of package name. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-openapi-utils@0.1.3-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## @backstage/plugin-user-settings@0.8.1-next.0 + +### Patch Changes + +- 912ca7b: Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-user-settings-backend@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-vault@0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## @backstage/plugin-vault-backend@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-vault-node@0.1.3-next.0 + +## @backstage/plugin-vault-node@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + +## example-app@0.2.92-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-import@0.10.6-next.0 + - @backstage/plugin-catalog-graph@0.3.4-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-catalog@1.17.0-next.0 + - @backstage/plugin-adr@0.6.13-next.0 + - @backstage/app-defaults@1.4.8-next.0 + - @backstage/frontend-app-api@0.6.0-next.0 + - @backstage/plugin-scaffolder-react@1.8.0-next.0 + - @backstage/plugin-scaffolder@1.18.0-next.0 + - @backstage/plugin-devtools@0.1.9-next.0 + - @backstage/plugin-user-settings@0.8.1-next.0 + - @backstage/plugin-tech-radar@0.6.13-next.0 + - @backstage/plugin-graphiql@0.3.3-next.0 + - @backstage/plugin-techdocs@1.9.4-next.0 + - @backstage/plugin-search@1.4.6-next.0 + - @backstage/plugin-api-docs@0.10.4-next.0 + - @backstage/cli@0.25.2-next.0 + - @backstage/plugin-stack-overflow@0.1.25-next.0 + - @backstage/plugin-signals@0.0.1-next.0 + - @backstage/plugin-home@0.6.2-next.0 + - @backstage/plugin-cloudbuild@0.4.0-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.5-next.0 + - @backstage/plugin-azure-devops@0.3.12-next.0 + - @backstage/plugin-linguist@0.1.15-next.0 + - @backstage/plugin-airbrake@0.3.30-next.0 + - @backstage/plugin-azure-sites@0.1.19-next.0 + - @backstage/plugin-badges@0.2.54-next.0 + - @backstage/plugin-code-coverage@0.2.23-next.0 + - @backstage/plugin-cost-insights@0.12.19-next.0 + - @backstage/plugin-dynatrace@8.0.4-next.0 + - @backstage/plugin-entity-feedback@0.2.13-next.0 + - @backstage/plugin-explore@0.4.16-next.0 + - @backstage/plugin-github-actions@0.6.11-next.0 + - @backstage/plugin-gocd@0.1.36-next.0 + - @backstage/plugin-jenkins@0.9.5-next.0 + - @backstage/plugin-kafka@0.3.30-next.0 + - @backstage/plugin-kubernetes@0.11.5-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.6-next.0 + - @backstage/plugin-lighthouse@0.4.15-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.5-next.0 + - @backstage/plugin-nomad@0.1.11-next.0 + - @backstage/plugin-octopus-deploy@0.2.12-next.0 + - @backstage/plugin-org@0.6.20-next.0 + - @backstage/plugin-pagerduty@0.7.2-next.0 + - @backstage/plugin-playlist@0.2.4-next.0 + - @backstage/plugin-puppetdb@0.1.13-next.0 + - @backstage/plugin-rollbar@0.4.30-next.0 + - @backstage/plugin-sentry@0.5.15-next.0 + - @backstage/plugin-tech-insights@0.3.22-next.0 + - @backstage/plugin-todo@0.2.34-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration-react@1.1.23 + - @backstage/theme@0.5.0 + - @backstage/plugin-apache-airflow@0.2.19 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7 + - @backstage/plugin-gcalendar@0.3.22 + - @backstage/plugin-gcp-projects@0.3.45 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.11 + - @backstage/plugin-newrelic@0.3.44 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-search-common@1.2.10 + - @backstage/plugin-shortcuts@0.3.18 + - @backstage/plugin-stackstorm@0.1.10 + - @backstage/plugin-techdocs-react@1.1.15 + +## example-app-next@0.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-import@0.10.6-next.0 + - @backstage/plugin-catalog-graph@0.3.4-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-catalog@1.17.0-next.0 + - @backstage/plugin-adr@0.6.13-next.0 + - @backstage/app-defaults@1.4.8-next.0 + - @backstage/frontend-app-api@0.6.0-next.0 + - @backstage/plugin-scaffolder-react@1.8.0-next.0 + - @backstage/plugin-scaffolder@1.18.0-next.0 + - @backstage/plugin-devtools@0.1.9-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/plugin-user-settings@0.8.1-next.0 + - @backstage/plugin-tech-radar@0.6.13-next.0 + - @backstage/plugin-graphiql@0.3.3-next.0 + - @backstage/plugin-techdocs@1.9.4-next.0 + - @backstage/plugin-search@1.4.6-next.0 + - @backstage/plugin-api-docs@0.10.4-next.0 + - @backstage/cli@0.25.2-next.0 + - @backstage/plugin-home@0.6.2-next.0 + - @backstage/plugin-cloudbuild@0.4.0-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.5-next.0 + - @backstage/plugin-azure-devops@0.3.12-next.0 + - @backstage/plugin-linguist@0.1.15-next.0 + - @backstage/plugin-airbrake@0.3.30-next.0 + - @backstage/plugin-azure-sites@0.1.19-next.0 + - @backstage/plugin-badges@0.2.54-next.0 + - @backstage/plugin-code-coverage@0.2.23-next.0 + - @backstage/plugin-cost-insights@0.12.19-next.0 + - @backstage/plugin-dynatrace@8.0.4-next.0 + - @backstage/plugin-entity-feedback@0.2.13-next.0 + - @backstage/plugin-explore@0.4.16-next.0 + - @backstage/plugin-github-actions@0.6.11-next.0 + - @backstage/plugin-gocd@0.1.36-next.0 + - @backstage/plugin-jenkins@0.9.5-next.0 + - @backstage/plugin-kafka@0.3.30-next.0 + - @backstage/plugin-kubernetes@0.11.5-next.0 + - @backstage/plugin-lighthouse@0.4.15-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.5-next.0 + - @backstage/plugin-octopus-deploy@0.2.12-next.0 + - @backstage/plugin-org@0.6.20-next.0 + - @backstage/plugin-pagerduty@0.7.2-next.0 + - @backstage/plugin-playlist@0.2.4-next.0 + - @backstage/plugin-puppetdb@0.1.13-next.0 + - @backstage/plugin-rollbar@0.4.30-next.0 + - @backstage/plugin-sentry@0.5.15-next.0 + - @backstage/plugin-tech-insights@0.3.22-next.0 + - @backstage/plugin-todo@0.2.34-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - app-next-example-plugin@0.0.6-next.0 + - @backstage/plugin-app-visualizer@0.1.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration-react@1.1.23 + - @backstage/theme@0.5.0 + - @backstage/plugin-apache-airflow@0.2.19 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7 + - @backstage/plugin-gcalendar@0.3.22 + - @backstage/plugin-gcp-projects@0.3.45 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.11 + - @backstage/plugin-newrelic@0.3.44 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-search-common@1.2.10 + - @backstage/plugin-shortcuts@0.3.18 + - @backstage/plugin-stackstorm@0.1.10 + - @backstage/plugin-techdocs-react@1.1.15 + +## app-next-example-plugin@0.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + +## example-backend@0.2.92-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-rails@0.4.27-next.0 + - @backstage/plugin-azure-devops-backend@0.5.2-next.0 + - @backstage/plugin-explore-backend@0.0.20-next.0 + - @backstage/plugin-auth-backend@0.20.4-next.0 + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-kubernetes-backend@0.14.2-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/plugin-search-backend@1.5.0-next.0 + - @backstage/plugin-todo-backend@0.3.8-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-signals-backend@0.0.1-next.0 + - @backstage/plugin-signals-node@0.0.1-next.0 + - @backstage/plugin-scaffolder-backend@1.21.0-next.0 + - @backstage/plugin-app-backend@0.3.58-next.0 + - example-app@0.2.92-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-badges-backend@0.3.7-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.7-next.0 + - @backstage/plugin-events-backend@0.2.19-next.0 + - @backstage/plugin-linguist-backend@0.5.7-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/plugin-playlist-backend@0.3.14-next.0 + - @backstage/plugin-proxy-backend@0.4.8-next.0 + - @backstage/plugin-rollbar-backend@0.1.55-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.14-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.14-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.19-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.0 + - @backstage/plugin-tech-insights-backend@0.5.24-next.0 + - @backstage/plugin-techdocs-backend@1.9.3-next.0 + - @backstage/plugin-adr-backend@0.4.7-next.0 + - @backstage/plugin-azure-sites-backend@0.1.20-next.0 + - @backstage/plugin-code-coverage-backend@0.2.24-next.0 + - @backstage/plugin-devtools-backend@0.2.7-next.0 + - @backstage/plugin-jenkins-backend@0.3.4-next.0 + - @backstage/plugin-kafka-backend@0.3.8-next.0 + - @backstage/plugin-lighthouse-backend@0.4.2-next.0 + - @backstage/plugin-nomad-backend@0.1.12-next.0 + - @backstage/plugin-permission-backend@0.5.33-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.13-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42-next.0 + - @backstage/plugin-tech-insights-node@0.4.16-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.0 + - @backstage/plugin-events-node@0.2.19-next.0 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## example-backend-next@0.0.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops-backend@0.5.2-next.0 + - @backstage/plugin-kubernetes-backend@0.14.2-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/plugin-search-backend@1.5.0-next.0 + - @backstage/plugin-todo-backend@0.3.8-next.0 + - @backstage/plugin-scaffolder-backend@1.21.0-next.0 + - @backstage/plugin-app-backend@0.3.58-next.0 + - @backstage/backend-defaults@0.2.10-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-badges-backend@0.3.7-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.27-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.7-next.0 + - @backstage/plugin-linguist-backend@0.5.7-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/plugin-playlist-backend@0.3.14-next.0 + - @backstage/plugin-proxy-backend@0.4.8-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.14-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.14-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.0 + - @backstage/plugin-sonarqube-backend@0.2.12-next.0 + - @backstage/plugin-techdocs-backend@1.9.3-next.0 + - @backstage/plugin-adr-backend@0.4.7-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3-next.0 + - @backstage/plugin-devtools-backend@0.2.7-next.0 + - @backstage/plugin-jenkins-backend@0.3.4-next.0 + - @backstage/plugin-lighthouse-backend@0.4.2-next.0 + - @backstage/plugin-nomad-backend@0.1.12-next.0 + - @backstage/plugin-permission-backend@0.5.33-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.0 + - @backstage/plugin-permission-common@0.7.12 + +## e2e-test@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.11-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## techdocs-cli-embedded-app@0.2.91-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.17.0-next.0 + - @backstage/app-defaults@1.4.8-next.0 + - @backstage/plugin-techdocs@1.9.4-next.0 + - @backstage/cli@0.25.2-next.0 + - @backstage/test-utils@1.5.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration-react@1.1.23 + - @backstage/theme@0.5.0 + - @backstage/plugin-techdocs-react@1.1.15 + +## @internal/plugin-todo-list-backend@1.0.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/errors@1.2.3 diff --git a/docs/tutorials/migrate-to-mui5.md b/docs/tutorials/migrate-to-mui5.md new file mode 100644 index 0000000000..aaf8cff7f5 --- /dev/null +++ b/docs/tutorials/migrate-to-mui5.md @@ -0,0 +1,65 @@ +--- +id: migrate-to-mui5 +title: Migrating from Material UI v4 to v5 +description: Additional resources for the Material UI v5 migration guide specifically for Backstage +--- + +Backstage supports developing new plugins or components using Material UI v5. At the same time, large parts of the application as well as existing plugins will still be using Material UI v4. To support Material UI v4 and v5 at the same time, we have introduced a new concept called the `UnifiedTheme`. The goal of the `UnifiedTheme` is to allow gradual migration by running both versions in parallel, applying theme options similarly & supporting potential future versions of Material UI. + +By default, the `UnifiedThemeProvider` is already used. If you add a custom theme in your `createApp` function, you would need to replace the Material UI `ThemeProvider` with the `UnifiedThemeProvider`: + +```diff ts ++ import import { ++ UnifiedThemeProvider, ++ themes as builtinThemes, ++ } from '@backstage/theme'; + + const app = createApp({ + // ... + themes: [ + { + // ... + provider: ({ children }) => ( +- . +- {children}. +- + ), + } + ] + }); +``` + +Before making specific changes to your Backstage instance, it might be helpful to take a look at the [Migration Guide provided by Material UI](https://mui.com/material-ui/migration/migration-v4/) first. It breaks down the differences between v4 and v5, and will make it easier to understand the impact on your Backstage instance & plugins. + +It is worth noting that we are still using `@mui/styles` & `jss`. You may stumble upon documentation for migrating to `emotion` when using `makeStyles` or `withStyles`. It is not necessary to switch to `emotion`. + +Important to keep in mind is that Material UI v5 is meant to be used with React Version 17 or higher. This means if you intend to use the Material UI v5 components in your plugins, you have to enforce React Version to be at least 17 for these plugins: + +```json +... + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0", + "react-router-dom": "6.0.0-beta.0 || ^6.3.0" + }, +... +``` + +To comply with Material UI recommendations, we are enforcing a new linting rule that favors standard imports over named imports and also restricts 3rd-level imports as they are considered private ([Guide: Minimizing Bundle Size](https://mui.com/material-ui/guides/minimizing-bundle-size)). + +There are `core-components` as well as components exported from Backstage `*-react` plugins written in Material UI v4, which expect Material UI components as props. In these cases you will still be forced to use Material UI v4. + +For current known issues with the Material UI v5 migration, follow our [Milestone on GitHub](https://github.com/backstage/backstage/milestone/40). Please open a new issue if you run into different problems. + +### Plugins + +To migrate your plugin to Material UI v5, you can build on the resources available. + +1. Manually fix the imports from named to default imports to match the new [linting rules for minimizing bundle size](https://mui.com/material-ui/guides/minimizing-bundle-size). Note: you can use the [new `@backstage/no-top-level-material-ui-4-imports` ESLint](https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-top-level-material-ui-4-imports.md) rule to help with this. +2. Run the migration `codemod` for the path of the specific plugin: `npx @mui/codemod v5.0.0/preset-safe plugins/`. +3. Take a look at possible `TODO:` items the `codemod` could not fix. +4. Remove types & methods from `@backstage/theme` which are marked as `@deprecated`. +5. Ensure you are using `"react": "^17.0.0"` (or newer) as a peer dependency + +You can follow the [migration of the GraphiQL plugin](https://github.com/backstage/backstage/pull/17696) as an example of a plugin migration. diff --git a/docs/tutorials/react18-migration.md b/docs/tutorials/react18-migration.md new file mode 100644 index 0000000000..2d3b3a961a --- /dev/null +++ b/docs/tutorials/react18-migration.md @@ -0,0 +1,117 @@ +--- +id: react18-migration +title: Migrating to React 18 +description: Additional resources for the Material UI v5 migration guide specifically for Backstage +--- + +The Backstage core libraries and plugins are compatible with all versions of React from v16.8 to v18. This means that you can migrate projects at your own pace. We do however encourage you to do so sooner rather than later, both to keep up with the evolving ecosystem, but also because React 18 brings performance improvements, in particular in tests. + +## Migration + +_Before diving in, this is a heads-up that for large projects this can be a tricky migration due to the fact that it is hard to break down into a gradual migration. In practice the difficult part of this migration is switching to the new version of the `@testing-library/react` package for tests, since there is no overlapping support across major React versions, more on that later._ + +### Switching to React 18 + +To switch a project to React 18, there are generally three changes that need to be made. + +1. Update the resolutions in your root `package.json` to the new versions of `@types/react` and `@types/react-dom`: + +```json title="package.json" + "resolutions": { + // highlight-remove-next-line + "@types/react": "^17", + // highlight-remove-next-line + "@types/react": "^17", + // highlight-add-next-line + "@types/react-dom": "^18", + // highlight-add-next-line + "@types/react-dom": "^18", + }, +``` + +2. Update the `react` and `react-dom` dependencies in your `packages/app/package.json` to the new versions: + +```json title="packages/app/package.json" + "dependencies": { + ... + // highlight-remove-next-line + "react": "^17.0.2", + // highlight-remove-next-line + "react-dom": "^17.0.2", + // highlight-add-next-line + "react": "^18.0.2", + // highlight-add-next-line + "react-dom": "^18.0.2", + ... + }, +``` + +3. Update `packages/app/src/index.tsx` to use the new `react-dom/client` API to render the app: + +```tsx title="packages/app/src/index.tsx" +import '@backstage/cli/asset-types'; +import React from 'react'; +// highlight-remove-next-line +import ReactDOM from 'react-dom'; +// highlight-add-next-line +import ReactDOM from 'react-dom/client'; +import App from './App'; + +// highlight-remove-next-line +ReactDOM.render(, document.getElementById('root')); +// highlight-add-next-line +ReactDOM.createRoot(document.getElementById('root')!).render(); +``` + +Once these steps are done you should be able to run your app and see it working as before, except now using React 18. + +### TypeScript Errors + +When upgrading to React 18 you are likely to see a fair number of TypeScript type errors. A summary of the breaking changes can be found in the [Pull Request that introduced them](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/56210). A codemod is also provided to help with the migration. + +Run `yarn tsc:full` to asses the damage. + +The good news is that these errors can be fixed while still staying on React 17. If you have a large number of errors to fix you can address as few or many is you like at a time and merge them into your main branch **without** the version bumps from step 1. This lets you gradually migrate the types in your project while not yet fully moving over to React 18. Once all type breakages are fixed you can move on to the next step of migrating tests. + +### Migrating Tests + +At this point the app hopefully works and you have no type errors, but if you run your tests you may see that a lot of them are failing. This is because the current version of the `@testing-library/react` package does not support React 18. Unfortunately the new version that we will be moving to does not support React 17, which is why we need to do this all at once. + +:::info +If migrating your entire project at once is not feasible, you can try to add `devDependencies` for `react` and `react-dom` v17 to individual plugins to be migrated later. This is not something we have tried ourselves in practice, so let us know in the community Discord if you attempt this and how it goes. +::: + +#### Dependency Upgrades + +To get the tests working again we need to update `@testing-library/react` to at least v13, although while at it is sensible to move at least all the way to v14 since the additional breaking changes have low impact. For more information on the changes in v13, see the [release notes](https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0). + +In addition to bumping `@testing-library/react` you also need to remove the `@testing-library/react-hooks` package, since it is now included in `@testing-library/react` itself. You can find more information on this change in the `@testing-library/react-hooks` [README.md](https://github.com/testing-library/react-hooks-testing-library?tab=readme-ov-file#a-note-about-react-18-support). + +The following search-and-replace RegEx patterns may by helpful in updating your `package.json` files: + +Find: `"@testing-library/react": ".*"`
+Replace: `"@testing-library/react": "^14.0.0"` + +Find: `"@testing-library/react-hooks": ".*",?`
+Replace: `` + +#### Test Updates + +Once you have installed the new versions of the dependencies this turns into a fairly mechanical process of updating the tests. Use your own favorite method for this, running all tests once to find the breakages and then focusing on one test file at a time was fairly smooth. + +When updating the tests in the Backstage project we found the following patterns to be useful: + +- Many existing `act(...)` calls can be removed, it is built into most testing utilities like `waitFor`, `.findBy*`, and `@testing-library/user-event`. +- Use `.findBy*` to wait for elements to appear. +- Use `waitFor(...)` to wait for any other expected state changes or multiple elements. +- Use `@testing-library/user-event` for user interactions. +- The `renderHook` API has changes in several ways: + - It no longer returns `waitForValueToChange` or `waitForNextUpdate`, you'll likely want to use `waitFor` instead. + - It now throws errors rather than returns them as part of the result. + - It no longer forwards `initialProps` to the `wrapper`, a workaround for this is provided in the [docs](https://testing-library.com/docs/react-testing-library/api/#renderhook-options-initialprops). +- Waiting for mock functions to be called by a component and then expecting render state to be updated is no longer reliable. +- Rendered components often don't immediately update on user input, it's more common to need to use `waitFor` or other utilities to wait for the expected state to be reached. + +You can also refer to the test changes in this [PR](https://github.com/backstage/backstage/pull/20598/files?file-filters%5B%5D=.ts&file-filters%5B%5D=.tsx), which was the migration to React 18 for the Backstage project itself. + +Best of luck! For question please join the [community Discord](https://discord.gg/backstage-687207715902193673). If you think this documentation could be improved we welcome you to [open an issue](https://github.com/backstage/backstage/issues/new/choose) or submit a pull request. diff --git a/docs/tutorials/setup-opentelemetry.md b/docs/tutorials/setup-opentelemetry.md new file mode 100644 index 0000000000..b85cbd4398 --- /dev/null +++ b/docs/tutorials/setup-opentelemetry.md @@ -0,0 +1,69 @@ +--- +id: setup-opentelemetry +title: Setup OpenTelemetry +description: Tutorial to setup OpenTelemetry metrics and traces exporters in Backstage +--- + +Backstage uses [OpenTelemetery](https://opentelemetry.io/) to instrument its components by reporting traces and metrics. + +This tutorial shows how to setup exporters in your Backstage backend package. For demonstration purposes we will use the simple console exporters. + +## Install dependencies + +We will use the OpenTelemetry Node SDK and the `auto-instrumentations-node` packages. + +Backstage packages, such as the catalog, uses the OpenTelemetry API to send custom traces and metrics. +The `auto-instrumentations-node` will automatically create spans for code called in libraries like Express. + +```bash +yarn --cwd packages/backend add @opentelemetry/sdk-node \ + @opentelemetry/auto-instrumentations-node \ + @opentelemetry/sdk-metrics +``` + +## Configure + +In your `packages/backend/src` folder, create an `instrumentation.ts` file. + +```typescript +import { NodeSDK } from '@opentelemetry/sdk-node'; +import { ConsoleSpanExporter } from '@opentelemetry/sdk-trace-node'; +import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; +import { + PeriodicExportingMetricReader, + ConsoleMetricExporter, +} from '@opentelemetry/sdk-metrics'; + +const sdk = new NodeSDK({ + traceExporter: new ConsoleSpanExporter(), + metricReader: new PeriodicExportingMetricReader({ + exporter: new ConsoleMetricExporter(), + }), + instrumentations: [getNodeAutoInstrumentations()], +}); + +sdk.start(); +``` + +In the `index.ts`, import this file **at the beginning**: + +```typescript +import './instrumentation'; // Setup the OpenTelemetry instrumentation + +// other imports and backend init... +``` + +It's important to setup the NodeSDK and the automatic instrumentation **before** importing any library. + +## Run Backstage + +You can now start your Backstage instance as usual, using `yarn dev`. + +When the backend is started, you should see in your console traces and metrics emitted by OpenTelemetry. + +Of course in production you probably won't use the console exporters but instead send traces and metrics to an OpenTelemetry Collector using [OTLP exporters](https://opentelemetry.io/docs/instrumentation/js/exporters/). + +## References + +- [Getting started with OpenTelemetry Node.js](https://opentelemetry.io/docs/instrumentation/js/getting-started/nodejs/) +- [OpenTelemetry NodeSDK API](https://open-telemetry.github.io/opentelemetry-js/classes/_opentelemetry_sdk_node.NodeSDK.html) diff --git a/microsite/blog/2023-02-15-backend-system-alpha.mdx b/microsite/blog/2023-02-15-backend-system-alpha.mdx index 28ff7bdf6b..24004d00b6 100644 --- a/microsite/blog/2023-02-15-backend-system-alpha.mdx +++ b/microsite/blog/2023-02-15-backend-system-alpha.mdx @@ -7,6 +7,8 @@ authorImageURL: https://avatars.githubusercontent.com/u/4984472?v=4 ![backend system alpha header image](assets/2023-02-15/backend-system-alpha.gif) +> UPDATE: The backend system is now released! See [the v1.18 release notes](https://backstage.io/docs/releases/v1.18.0). + For the past year, the Backstage maintainer team has been spending time to give the [old Backstage backend system](https://backstage.io/docs/plugins/backend-plugin) a much needed revamp. Our goal was not to build something completely new from scratch, but rather to solidify the existing foundations that have grown organically. We wanted to make plugin installation a lot simpler, while maintaining and even improving the ways in which you can customize your backend setups. We’re happy to announce that the [v1.11](https://backstage.io/docs/releases/v1.11.0) release includes the public alpha of the [new Backstage backend system](https://backstage.io/docs/backend-system/)! The system has already been available to use for some time, as some of you have already found, but this alpha release marks the point where we are finally ready to encourage widespread adoption by plugin builders. We still don’t recommend that you use this new system in your production setups just yet, more on that later. diff --git a/microsite/data/plugins/announcements.yaml b/microsite/data/plugins/announcements.yaml index d03c1a1bd7..4d53f4157a 100644 --- a/microsite/data/plugins/announcements.yaml +++ b/microsite/data/plugins/announcements.yaml @@ -1,10 +1,10 @@ --- title: Announcements -author: K-Phoen -authorUrl: https://github.com/K-Phoen +author: procore-oss +authorUrl: https://github.com/procore-oss category: Discovery description: Write and share announcements within Backstage. -documentation: https://github.com/K-Phoen/backstage-plugin-announcements/ +documentation: https://github.com/procore-oss/backstage-plugin-announcements/ iconUrl: /img/plugin-announcements-logo.png -npmPackageName: '@k-phoen/backstage-plugin-announcements' +npmPackageName: '@procore-oss/backstage-plugin-announcements' addedDate: '2022-11-09' diff --git a/microsite/data/plugins/api-wsdl.yaml b/microsite/data/plugins/api-wsdl.yaml new file mode 100644 index 0000000000..6523531f1d --- /dev/null +++ b/microsite/data/plugins/api-wsdl.yaml @@ -0,0 +1,10 @@ +--- +title: WSDL / SOAP viewer +author: dweber019 +authorUrl: https://github.com/dweber019 +category: Discovery +description: An API docs extension to render WSDL / SOAP web services human readable. +documentation: https://github.com/dweber019/backstage-plugin-api-docs-module-wsdl +iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugin-api-docs-module-wsdl/main/docs/logo.png +npmPackageName: '@dweber019/backstage-plugin-api-docs-module-wsdl' +addedDate: '2023-12-22' diff --git a/microsite/data/plugins/aws-app-development.yaml b/microsite/data/plugins/aws-app-development.yaml index 3944bbb4e0..47b40b8b3b 100644 --- a/microsite/data/plugins/aws-app-development.yaml +++ b/microsite/data/plugins/aws-app-development.yaml @@ -1,10 +1,10 @@ --- -title: AWS App Developer Tools +title: OPA on AWS author: Amazon Web Services authorUrl: https://aws.amazon.com/ category: Infrastructure -description: Create and manage AWS Apps within Backstage -documentation: https://github.com/awslabs/app-development-for-backstage-io-on-aws#readme -iconUrl: https://github.com/awslabs/app-development-for-backstage-io-on-aws/blob/main/docs/images/AWS_logo.png?raw=true +description: Orchestrate Platforms and Applications (OPA) allows customers to build and manage AWS Apps & Environments within Backstage +documentation: https://opaonaws.io +iconUrl: https://github.com/awslabs/app-development-for-backstage-io-on-aws/blob/main/website/static/img/white_OPA_text02.png?raw=true npmPackageName: '@aws/plugin-aws-apps-for-backstage' addedDate: '2023-05-10' diff --git a/microsite/data/plugins/azure-storage.yaml b/microsite/data/plugins/azure-storage.yaml new file mode 100644 index 0000000000..655a6421ed --- /dev/null +++ b/microsite/data/plugins/azure-storage.yaml @@ -0,0 +1,14 @@ +--- +title: Azure Storage Explorer +author: Deepankumar +authorUrl: https://github.com/deepan10 +category: Infrastructure +description: Explore Azure Storage Blobs in Backstage. +documentation: https://github.com/deepan10/backstage-plugins/tree/main/plugins/azure-storage +iconUrl: /img/azure-storage-folder.png +npmPackageName: 'backstage-plugin-azure-storage' +tags: + - Azure + - Azure Storage + - Infrastructure +addedDate: '2023-12-01' diff --git a/microsite/data/plugins/backchat.yaml b/microsite/data/plugins/backchat.yaml new file mode 100644 index 0000000000..b375ad26bc --- /dev/null +++ b/microsite/data/plugins/backchat.yaml @@ -0,0 +1,10 @@ +--- +title: Backchat GenAI +author: benwilcock +authorUrl: https://github.com/benwilcock +category: Services +description: Access your favorite open source GenAI GUIs privately from Backstage. Chat wth large language models in your portal. Choose from hundreds of LLMs. Run inferencing wherever you like - local or remote, CPU or GPU - it's up to you! +documentation: https://github.com/benwilcock/backstage-plugin-backchat +iconUrl: /img/backchat-logo.png +npmPackageName: '@benbravo73/backstage-plugin-backchat' +addedDate: '2024-01-12' diff --git a/microsite/data/plugins/circleci.yaml b/microsite/data/plugins/circleci.yaml index 5d7978624b..5980f8cf45 100644 --- a/microsite/data/plugins/circleci.yaml +++ b/microsite/data/plugins/circleci.yaml @@ -1,12 +1,12 @@ --- title: CircleCI -author: Spotify -authorUrl: https://github.com/spotify +author: CircleCI +authorUrl: https://circleci.com/ category: CI/CD description: Automate your development process with CI hosted in the cloud or on a private server. -documentation: https://github.com/backstage/backstage/tree/master/plugins/circleci +documentation: https://github.com/CircleCI-Public/backstage-plugin/tree/main/plugins/circleci iconUrl: /img/circleci.png -npmPackageName: '@backstage/plugin-circleci' +npmPackageName: '@circleci/backstage-plugin' tags: - ci - cd diff --git a/microsite/data/plugins/dev-quotes.yaml b/microsite/data/plugins/dev-quotes.yaml new file mode 100644 index 0000000000..ad2ac91e4c --- /dev/null +++ b/microsite/data/plugins/dev-quotes.yaml @@ -0,0 +1,10 @@ +--- +title: Dev Quotes +author: Peter Macdonald +authorUrl: https://github.com/Parsifal-M +category: Humor +description: Displays a coding/progamming related quote designed as a footer for the Homepage, although to be honest it can be used anywhere you like! +documentation: https://github.com/Parsifal-M/backstage-dev-quotes?tab=readme-ov-file#dev-quotes-homepage +iconUrl: /img/dqicon.png +npmPackageName: '@parsifal-m/plugin-dev-quotes-homepage' +addedDate: '2023-12-13' diff --git a/microsite/data/plugins/end-of-life.yaml b/microsite/data/plugins/end-of-life.yaml new file mode 100644 index 0000000000..d54a6bc5f9 --- /dev/null +++ b/microsite/data/plugins/end-of-life.yaml @@ -0,0 +1,10 @@ +--- +title: End of life +author: dweber019 +authorUrl: https://github.com/dweber019 +category: Quality +description: Display end of life data for entities from endoflife.data +documentation: https://github.com/dweber019/backstage-plugin-endoflife +iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugin-endoflife/main/plugins/endoflife/docs/pluginIcon.png +npmPackageName: '@dweber019/backstage-plugin-endoflife' +addedDate: '2024-01-18' diff --git a/microsite/data/plugins/festive-fun.yaml b/microsite/data/plugins/festive-fun.yaml new file mode 100644 index 0000000000..33995eca27 --- /dev/null +++ b/microsite/data/plugins/festive-fun.yaml @@ -0,0 +1,10 @@ +--- +title: Festive Fun +author: benjmac +authorUrl: https://github.com/benjmac +category: Humor +description: Festive, seasonal, animations to spice up your instance of Backstage! +documentation: https://github.com/benjmac/backstage-plugin-festive-fun/ +iconUrl: https://raw.githubusercontent.com/benjmac/backstage-plugin-festive-fun/main/docs/festive-fun-logo.png +npmPackageName: 'backstage-plugin-festive-fun' +addedDate: '2023-11-20' diff --git a/microsite/data/plugins/github-codespaces.yaml b/microsite/data/plugins/github-codespaces.yaml new file mode 100644 index 0000000000..de2659c1e1 --- /dev/null +++ b/microsite/data/plugins/github-codespaces.yaml @@ -0,0 +1,15 @@ +--- +title: GitHub Codespaces +author: Aditya Singhal +authorUrl: https://github.com/adityasinghal26 +category: Development +description: Integrates GitHub Codespaces for a Backstage component with the Authenticated User. +documentation: https://github.com/adityasinghal26/backstage-plugins/tree/main/plugins/github-codespaces +iconUrl: https://github.com/adityasinghal26/backstage-plugins/blob/00c9c00ba9acc3135014d6454ccf04f573195eef/plugins/github-codespaces/images/GitHubLogo.png +npmPackageName: '@adityasinghal26/plugin-github-codespaces' +tags: + - github + - codespaces + - development + - devcontainers +addedDate: '2023-12-30' diff --git a/microsite/data/plugins/hcp-consul.yaml b/microsite/data/plugins/hcp-consul.yaml new file mode 100644 index 0000000000..6dabad582e --- /dev/null +++ b/microsite/data/plugins/hcp-consul.yaml @@ -0,0 +1,10 @@ +--- +title: HCP Consul +author: Hashicorp +authorUrl: https://www.hashicorp.com/ +category: Infrastructure +description: View your HCP Consul clusters, services, instances in Backstage. +documentation: https://github.com/hashicorp-forge/backstage-plugin-hcp-consul/tree/main +iconUrl: /img/hcp-consul.svg +npmPackageName: '@hashicorp/plugin-hcp-consul' +addedDate: '2023-11-24' diff --git a/microsite/data/plugins/highlights.yaml b/microsite/data/plugins/highlights.yaml new file mode 100644 index 0000000000..82d303f4ed --- /dev/null +++ b/microsite/data/plugins/highlights.yaml @@ -0,0 +1,12 @@ +--- +title: Highlights +author: RSC Labs +authorUrl: https://rsoftcon.com/ +category: Discovery +description: Highlight what is important for you and put it on front for the quick view and access. Built-in support of Git information from GitHub and GitLab. +documentation: https://github.com/RSC-Labs/backstage-highlights-plugin/blob/main/README.md +iconUrl: https://raw.githubusercontent.com/RSC-Labs/backstage-highlights-plugin/main/docs/highlighter.png +npmPackageName: '@rsc-labs/backstage-highlights-plugin' +tags: + - highlights +addedDate: '2023-11-20' diff --git a/microsite/data/plugins/jira-dashboard.yaml b/microsite/data/plugins/jira-dashboard.yaml new file mode 100644 index 0000000000..8cb9351c8d --- /dev/null +++ b/microsite/data/plugins/jira-dashboard.yaml @@ -0,0 +1,10 @@ +--- +title: Jira Dashboard +author: AxisCommunications +authorUrl: https://github.com/AxisCommunications +category: Agile Planning +description: Allows you to fetch and display Jira issues for your entity. You get quickly access to issue summaries to achieve better task visibility and more efficient project management. +documentation: https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/jira-dashboard/README.md +iconUrl: https://raw.githubusercontent.com/AxisCommunications/backstage-plugins/main/plugins/jira-dashboard/media/jira-logo.png +npmPackageName: '@axis-backstage/plugin-jira-dashboard' +addedDate: '2023-12-07' diff --git a/microsite/data/plugins/nobl9.yaml b/microsite/data/plugins/nobl9.yaml new file mode 100644 index 0000000000..ee447fd626 --- /dev/null +++ b/microsite/data/plugins/nobl9.yaml @@ -0,0 +1,10 @@ +--- +title: Nobl9 +author: Nobl9 +authorUrl: https://nobl9.com +category: Reliability +description: View and drill down into the reliability of your services via Nobl9 SLOs and error budgets. +documentation: https://github.com/nobl9/nobl9-backstage-plugin/blob/main/README.MD +iconUrl: /img/nobl9.svg +npmPackageName: '@nobl9/nobl9-backstage-plugin' +addedDate: '2023-12-15' diff --git a/microsite/data/plugins/open-dora.yaml b/microsite/data/plugins/open-dora.yaml new file mode 100644 index 0000000000..16b4c63969 --- /dev/null +++ b/microsite/data/plugins/open-dora.yaml @@ -0,0 +1,10 @@ +--- +title: OpenDORA +author: Devoteam +authorUrl: https://www.devoteam.com/ +category: Monitoring +description: View DORA metrics within Backstage using DevLake. +documentation: https://github.com/DevoteamNL/opendora/tree/main/backstage-plugin/plugins/open-dora#readme +iconUrl: /img/open-dora-icon.png +npmPackageName: '@devoteam-nl/open-dora-backstage-plugin' +addedDate: '2023-11-29' diff --git a/microsite/data/plugins/pager-duty.yaml b/microsite/data/plugins/pager-duty.yaml index a4e6e42d47..7d603a37c1 100644 --- a/microsite/data/plugins/pager-duty.yaml +++ b/microsite/data/plugins/pager-duty.yaml @@ -1,12 +1,12 @@ --- title: PagerDuty -author: Spotify -authorUrl: https://github.com/spotify +author: PagerDuty +authorUrl: https://github.com/pagerduty category: Monitoring -description: PagerDuty offers a simple way to identify any active incidents for an entity and the escalation policy. -documentation: https://github.com/backstage/backstage/tree/master/plugins/pagerduty +description: Bring the power of PagerDuty to Backstage, reduce cognitive load, improve service visibility and enforce incident management best practices. +documentation: https://pagerduty.github.io/backstage-plugin-docs/index.html iconUrl: https://avatars2.githubusercontent.com/u/766800?s=200&v=4 -npmPackageName: '@backstage/plugin-pagerduty' +npmPackageName: '@pagerduty/backstage-plugin' tags: - monitoring - errors diff --git a/microsite/data/plugins/readme.yaml b/microsite/data/plugins/readme.yaml new file mode 100644 index 0000000000..c914efe355 --- /dev/null +++ b/microsite/data/plugins/readme.yaml @@ -0,0 +1,10 @@ +--- +title: Readme +author: AxisCommunications +authorUrl: https://github.com/AxisCommunications +category: Metadata +description: The Readme-plugin enables easy access and viewing of the README.md file in your entity overview. +documentation: https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/readme/README.md +iconUrl: https://raw.githubusercontent.com/AxisCommunications/backstage-plugins/main/plugins/readme/media/readme.png +npmPackageName: '@axis-backstage/plugin-readme' +addedDate: '2023-12-07' diff --git a/microsite/data/plugins/scaffolder-backend-odo.yaml b/microsite/data/plugins/scaffolder-backend-odo.yaml new file mode 100644 index 0000000000..954d51ecb2 --- /dev/null +++ b/microsite/data/plugins/scaffolder-backend-odo.yaml @@ -0,0 +1,10 @@ +--- +title: Scaffolder odo CLI actions +author: Red Hat +authorUrl: https://developers.redhat.com +category: Scaffolder +description: Collection of actions to run odo CLI commands. odo is a developer-focused CLI for container-based application development on Podman and Kubernetes. +documentation: https://github.com/redhat-developer/backstage-odo-devfile-plugin/blob/main/packages/scaffolder-odo-actions-backend/README.md +iconUrl: https://odo.dev/img/logo.png +npmPackageName: '@redhat-developer/plugin-scaffolder-odo-actions' +addedDate: '2023-12-08' diff --git a/microsite/data/plugins/scaffolder-backend-slack.yaml b/microsite/data/plugins/scaffolder-backend-slack.yaml index dce7b60eb7..a726751eaf 100644 --- a/microsite/data/plugins/scaffolder-backend-slack.yaml +++ b/microsite/data/plugins/scaffolder-backend-slack.yaml @@ -5,6 +5,6 @@ authorUrl: https://github.com/arhill05 category: Scaffolder description: Interact with Slack from scaffolder templates documentation: https://github.com/arhill05/backstage-plugin-scaffolder-backend-module-slack#readme -iconUrl: img/Slack-mark-RGB.png +iconUrl: /img/Slack-mark-RGB.png npmPackageName: '@mdude2314/backstage-plugin-scaffolder-backend-module-slack' addedDate: '2023-08-04' diff --git a/microsite/data/plugins/scaffolder-frontend-devfile-field.yaml b/microsite/data/plugins/scaffolder-frontend-devfile-field.yaml new file mode 100644 index 0000000000..3215c63795 --- /dev/null +++ b/microsite/data/plugins/scaffolder-frontend-devfile-field.yaml @@ -0,0 +1,10 @@ +--- +title: Devfile Selector Field Extension +author: Red Hat +authorUrl: https://developers.redhat.com +category: Scaffolder +description: Devfile Selector Field Extension to use in your Software Templates. Devfile is an open standard defining containerized development environments. +documentation: https://github.com/redhat-developer/backstage-odo-devfile-plugin/blob/main/packages/devfile-field-extension/README.md +iconUrl: https://landscape.cncf.io/logos/devfile.svg +npmPackageName: '@redhat-developer/plugin-scaffolder-frontend-module-devfile-field' +addedDate: '2023-12-08' diff --git a/microsite/data/plugins/search-backend-module-azure-devops-wiki.yaml b/microsite/data/plugins/search-backend-module-azure-devops-wiki.yaml index 02d4131cce..cc8849ee08 100644 --- a/microsite/data/plugins/search-backend-module-azure-devops-wiki.yaml +++ b/microsite/data/plugins/search-backend-module-azure-devops-wiki.yaml @@ -5,6 +5,6 @@ authorUrl: https://github.com/arhill05 category: Search description: Index wiki articles from an Azure DevOps wiki into Backstage to allow you to search them with the Backstage Search feature. documentation: https://github.com/arhill05/backstage-plugin-search-backend-module-azure-devops-wiki#readme -iconUrl: img/ado-wiki-search-icon.png +iconUrl: /img/ado-wiki-search-icon.png npmPackageName: '@mdude2314/backstage-plugin-search-backend-module-azure-devops-wiki' addedDate: '2023-06-13' diff --git a/microsite/data/plugins/snyk-security.yaml b/microsite/data/plugins/snyk-security.yaml index 4abd068247..8c22d44510 100644 --- a/microsite/data/plugins/snyk-security.yaml +++ b/microsite/data/plugins/snyk-security.yaml @@ -5,6 +5,6 @@ authorUrl: https://snyk.io category: Security description: View Snyk scanned vulnerabilities and license compliance of your components directly in Backstage. documentation: https://github.com/snyk-tech-services/backstage-plugin-snyk/blob/main/README.md -iconUrl: https://storage.googleapis.com/snyk-technical-services.appspot.com/snyk-logo-vertical-black.png +iconUrl: https://github.com/snyk-tech-services/backstage-plugin-snyk/assets/109112986/95eb1b06-b3e8-4910-9233-11926e02fa18 npmPackageName: 'backstage-plugin-snyk' addedDate: '2021-01-22' diff --git a/microsite/docusaurus.config.js b/microsite/docusaurus.config.js index 7a01498646..f07f9da6af 100644 --- a/microsite/docusaurus.config.js +++ b/microsite/docusaurus.config.js @@ -140,6 +140,10 @@ module.exports = { from: '/docs/getting-started/running-backstage-locally', to: '/docs/getting-started/', }, + { + from: '/docs/features/software-templates/testing-scaffolder-alpha', + to: '/docs/features/software-templates/migrating-to-rjsf-v5', + }, ], }, ], @@ -178,7 +182,7 @@ module.exports = { position: 'left', }, { - to: 'docs/releases/v1.20.0', + to: 'docs/releases/v1.22.0', label: 'Releases', position: 'left', }, @@ -200,7 +204,10 @@ module.exports = { { items: [ { - html: '', + html: ` + + + `, }, ], }, @@ -277,7 +284,7 @@ module.exports = { }, ], copyright: - '

Made with ❤️ at Spotify

', + '

Made with ❤️ at Spotify

', }, algolia: { apiKey: '1f0ba86672ccfc3576faa94583e5b318', @@ -287,6 +294,9 @@ module.exports = { }, prism: { theme: prismTheme, + // Supported languages: https://prismjs.com/#supported-languages + // Default languages: https://github.com/FormidableLabs/prism-react-renderer/blob/master/packages/generate-prism-languages/index.ts#L9-L23 + additionalLanguages: ['docker', 'bash'], magicComments: [ // Extend the default highlight class name { diff --git a/microsite/package.json b/microsite/package.json index 61bfcbd04a..53309fb358 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -6,6 +6,7 @@ "scripts": { "start": "node scripts/pre-build.js && docusaurus start", "build": "node scripts/pre-build.js && docusaurus build", + "prettier:fix": "prettier --write .", "prettier:check": "prettier --check .", "publish-gh-pages": "docusaurus-publish", "write-translations": "docusaurus-write-translations", @@ -33,7 +34,7 @@ "@docusaurus/plugin-client-redirects": "0.0.0-5703", "@docusaurus/preset-classic": "0.0.0-5703", "@swc/core": "^1.3.46", - "clsx": "^1.1.1", + "clsx": "^2.0.0", "docusaurus-plugin-sass": "^0.2.3", "luxon": "^3.0.0", "prism-react-renderer": "^1.3.5", diff --git a/microsite/sidebars.json b/microsite/sidebars.json index c9b1c99eba..e32e027807 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -1,6 +1,8 @@ { "releases": { "Release Notes": [ + "releases/v1.22.0", + "releases/v1.21.0", "releases/v1.20.0", "releases/v1.19.0", "releases/v1.18.0", @@ -248,6 +250,15 @@ "plugins/internationalization", "plugins/analytics", "plugins/feature-flags", + { + "type": "category", + "label": "OpenAPI", + "items": [ + "openapi/01-getting-started", + "openapi/generate-client", + "openapi/test-case-validation" + ] + }, { "type": "category", "label": "Backends and APIs", @@ -380,6 +391,44 @@ "items": ["backend-system/core-services/index"] } ], + "New Frontend System": [ + "frontend-system/index", + { + "type": "category", + "label": "Architecture", + "items": [ + "frontend-system/architecture/index", + "frontend-system/architecture/app", + "frontend-system/architecture/extensions", + "frontend-system/architecture/plugins", + "frontend-system/architecture/extension-overrides", + "frontend-system/architecture/utility-apis", + "frontend-system/architecture/routes", + "frontend-system/architecture/naming-patterns", + "frontend-system/architecture/references" + ] + }, + { + "type": "category", + "label": "Building Plugins", + "items": [ + "frontend-system/building-plugins/index", + "frontend-system/building-plugins/testing", + "frontend-system/building-plugins/extension-types" + ] + }, + { + "type": "category", + "label": "Utility APIs", + "items": [ + "frontend-system/utility-apis/index", + "frontend-system/utility-apis/creating", + "frontend-system/utility-apis/consuming", + "frontend-system/utility-apis/configuring", + "frontend-system/utility-apis/migrating" + ] + } + ], "Designing for Backstage": [ "dls/design", "dls/component-design-guidelines", @@ -403,12 +452,15 @@ "tutorials/journey", "tutorials/quickstart-app-plugin", "tutorials/react-router-stable-migration", + "tutorials/react18-migration", "tutorials/package-role-migration", "tutorials/migrating-away-from-core", "tutorials/configuring-plugin-databases", "tutorials/switching-sqlite-postgres", "tutorials/using-backstage-proxy-within-plugin", - "tutorials/yarn-migration" + "tutorials/yarn-migration", + "tutorials/migrate-to-mui5", + "tutorials/setup-opentelemetry" ], "Architecture Decision Records (ADRs)": [ "architecture-decisions/adrs-overview", @@ -426,7 +478,7 @@ "architecture-decisions/adrs-adr012", "architecture-decisions/adrs-adr013" ], - "FAQ": ["FAQ"], + "FAQ": ["faq/index", "faq/product", "faq/technical"], "Accessibility": ["accessibility/index"] } } diff --git a/microsite/src/pages/community/index.tsx b/microsite/src/pages/community/index.tsx index 82dd69d1a0..9b10183ea4 100644 --- a/microsite/src/pages/community/index.tsx +++ b/microsite/src/pages/community/index.tsx @@ -110,6 +110,11 @@ const Community = () => { url: 'https://frontside.com/backstage/', logo: 'img/partner-logo-frontside.png', }, + { + name: 'RedHat', + url: 'https://www.redhat.com/', + logo: 'img/partner-logo-redhat.png', + }, { name: 'Roadie', url: 'https://roadie.io/', diff --git a/microsite/src/pages/home/_home.tsx b/microsite/src/pages/home/_home.tsx index ddcfed7d03..5cf0f85072 100644 --- a/microsite/src/pages/home/_home.tsx +++ b/microsite/src/pages/home/_home.tsx @@ -95,10 +95,12 @@ const HomePage = () => { Illustration of a laptop Animated Backstage Logo diff --git a/microsite/src/pages/home/hubSpotNewAdoptersForm.module.scss b/microsite/src/pages/home/hubSpotNewAdoptersForm.module.scss index 77e1a87a53..9533890660 100644 --- a/microsite/src/pages/home/hubSpotNewAdoptersForm.module.scss +++ b/microsite/src/pages/home/hubSpotNewAdoptersForm.module.scss @@ -1,9 +1,13 @@ +$page-header-height: 44px; + .hubSpotNewAdopterFormContainer { top: 50%; right: 0; display: flex; position: fixed; transform: translateY(-50%); + margin-top: $page-header-height; + max-height: calc(100% - $page-header-height); transition: all 250ms ease-in-out; @@ -22,9 +26,7 @@ } .hubSpotNewAdopterFormContent { - height: 600px; max-width: 500px; - overflow-y: hidden; border-radius: 8px 0 0 8px; background-color: var(--ifm-color-white); diff --git a/microsite/src/pages/plugins/index.tsx b/microsite/src/pages/plugins/index.tsx index 006b7d4807..2abdbabf4b 100644 --- a/microsite/src/pages/plugins/index.tsx +++ b/microsite/src/pages/plugins/index.tsx @@ -151,6 +151,12 @@ const Plugins = () => { {showOtherPlugins && (

All Plugins

+

+ Friendly reminder: While we love the variety and contributions of + our open source plugins, they haven't been fully vetted by the + core Backstage team. We encourage you to exercise caution and do + your due diligence before installing. Happy exploring! +

{plugins.otherPlugins .filter( diff --git a/microsite/src/pages/plugins/plugins.module.scss b/microsite/src/pages/plugins/plugins.module.scss index 80ac60cf98..dbd0c62c75 100644 --- a/microsite/src/pages/plugins/plugins.module.scss +++ b/microsite/src/pages/plugins/plugins.module.scss @@ -78,6 +78,8 @@ :global(.dropdown) { float: right; margin-bottom: 1rem; + text-overflow: ellipsis; + white-space: nowrap; } :global(.button--info) { diff --git a/microsite/src/theme/customTheme.scss b/microsite/src/theme/customTheme.scss index f216762418..57e1f8828e 100644 --- a/microsite/src/theme/customTheme.scss +++ b/microsite/src/theme/customTheme.scss @@ -8,6 +8,14 @@ --ifm-color-primary-darkest: #268271; } +#__docusaurus { + .theme-doc-markdown { + a { + text-decoration: underline; + } + } +} + .footerLogo { background-image: url(/img/logo.svg); background-repeat: no-repeat; diff --git a/microsite/static/img/azure-storage-folder.png b/microsite/static/img/azure-storage-folder.png new file mode 100644 index 0000000000..479340c5b3 Binary files /dev/null and b/microsite/static/img/azure-storage-folder.png differ diff --git a/microsite/static/img/backchat-logo.png b/microsite/static/img/backchat-logo.png new file mode 100644 index 0000000000..2d58948007 Binary files /dev/null and b/microsite/static/img/backchat-logo.png differ diff --git a/microsite/static/img/dqicon.png b/microsite/static/img/dqicon.png new file mode 100644 index 0000000000..3746841cd8 Binary files /dev/null and b/microsite/static/img/dqicon.png differ diff --git a/microsite/static/img/hcp-consul.svg b/microsite/static/img/hcp-consul.svg new file mode 100644 index 0000000000..d84972585c --- /dev/null +++ b/microsite/static/img/hcp-consul.svg @@ -0,0 +1,17 @@ + +Consul Logo + + + + + + + + + + + + + + + diff --git a/microsite/static/img/nobl9.svg b/microsite/static/img/nobl9.svg new file mode 100644 index 0000000000..a4065df602 --- /dev/null +++ b/microsite/static/img/nobl9.svg @@ -0,0 +1,4 @@ + + + + diff --git a/microsite/static/img/open-dora-icon.png b/microsite/static/img/open-dora-icon.png new file mode 100644 index 0000000000..3612fb06af Binary files /dev/null and b/microsite/static/img/open-dora-icon.png differ diff --git a/microsite/static/img/partner-logo-redhat.png b/microsite/static/img/partner-logo-redhat.png new file mode 100644 index 0000000000..ad9bc9ceba Binary files /dev/null and b/microsite/static/img/partner-logo-redhat.png differ diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 10ad6d0d68..5c92ba5e69 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -2701,90 +2701,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.96": - version: 1.3.96 - resolution: "@swc/core-darwin-arm64@npm:1.3.96" +"@swc/core-darwin-arm64@npm:1.3.105": + version: 1.3.105 + resolution: "@swc/core-darwin-arm64@npm:1.3.105" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.96": - version: 1.3.96 - resolution: "@swc/core-darwin-x64@npm:1.3.96" +"@swc/core-darwin-x64@npm:1.3.105": + version: 1.3.105 + resolution: "@swc/core-darwin-x64@npm:1.3.105" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.96": - version: 1.3.96 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.96" +"@swc/core-linux-arm-gnueabihf@npm:1.3.105": + version: 1.3.105 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.105" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.96": - version: 1.3.96 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.96" +"@swc/core-linux-arm64-gnu@npm:1.3.105": + version: 1.3.105 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.105" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.96": - version: 1.3.96 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.96" +"@swc/core-linux-arm64-musl@npm:1.3.105": + version: 1.3.105 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.105" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.96": - version: 1.3.96 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.96" +"@swc/core-linux-x64-gnu@npm:1.3.105": + version: 1.3.105 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.105" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.96": - version: 1.3.96 - resolution: "@swc/core-linux-x64-musl@npm:1.3.96" +"@swc/core-linux-x64-musl@npm:1.3.105": + version: 1.3.105 + resolution: "@swc/core-linux-x64-musl@npm:1.3.105" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.96": - version: 1.3.96 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.96" +"@swc/core-win32-arm64-msvc@npm:1.3.105": + version: 1.3.105 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.105" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.96": - version: 1.3.96 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.96" +"@swc/core-win32-ia32-msvc@npm:1.3.105": + version: 1.3.105 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.105" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.96": - version: 1.3.96 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.96" +"@swc/core-win32-x64-msvc@npm:1.3.105": + version: 1.3.105 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.105" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.3.46": - version: 1.3.96 - resolution: "@swc/core@npm:1.3.96" + version: 1.3.105 + resolution: "@swc/core@npm:1.3.105" dependencies: - "@swc/core-darwin-arm64": 1.3.96 - "@swc/core-darwin-x64": 1.3.96 - "@swc/core-linux-arm-gnueabihf": 1.3.96 - "@swc/core-linux-arm64-gnu": 1.3.96 - "@swc/core-linux-arm64-musl": 1.3.96 - "@swc/core-linux-x64-gnu": 1.3.96 - "@swc/core-linux-x64-musl": 1.3.96 - "@swc/core-win32-arm64-msvc": 1.3.96 - "@swc/core-win32-ia32-msvc": 1.3.96 - "@swc/core-win32-x64-msvc": 1.3.96 + "@swc/core-darwin-arm64": 1.3.105 + "@swc/core-darwin-x64": 1.3.105 + "@swc/core-linux-arm-gnueabihf": 1.3.105 + "@swc/core-linux-arm64-gnu": 1.3.105 + "@swc/core-linux-arm64-musl": 1.3.105 + "@swc/core-linux-x64-gnu": 1.3.105 + "@swc/core-linux-x64-musl": 1.3.105 + "@swc/core-win32-arm64-msvc": 1.3.105 + "@swc/core-win32-ia32-msvc": 1.3.105 + "@swc/core-win32-x64-msvc": 1.3.105 "@swc/counter": ^0.1.1 "@swc/types": ^0.1.5 peerDependencies: @@ -2813,7 +2813,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: 41d4a4461b2952aaf8d3be945d373d0f3bd126115ee1aad0f76f2690e2b5635b6ec5bb54a7638deb9afedb1ad6f7d8453468a704e54e5fbb8234dd4a43b80205 + checksum: 5baa880bc92748ef4845d9c65eba5d6dd01adaa673854e20a5116f5e267c12180db50e563cf3c34a415772b9742d021176a9d9a91065c190ef6f54fefe85728c languageName: node linkType: hard @@ -2855,9 +2855,9 @@ __metadata: linkType: hard "@tsconfig/docusaurus@npm:^2.0.0": - version: 2.0.1 - resolution: "@tsconfig/docusaurus@npm:2.0.1" - checksum: 63bebda70d83c56f95a90176d2e188e1ea9c08c23b499e5e7b292ebfae0ce7117f712809828ed21ae3b8440daf22191d6bf71bf2575f9bd474a51b1770ca30cc + version: 2.0.2 + resolution: "@tsconfig/docusaurus@npm:2.0.2" + checksum: 129f2532172496c108f53a15082e410b418e664c1761902664558059541a6cf7faff2b3d2953e2d507a7e3688afacf2479e17adcb35b89cae04ddb0415d0397a languageName: node linkType: hard @@ -3055,9 +3055,9 @@ __metadata: linkType: hard "@types/luxon@npm:^3.0.0": - version: 3.3.4 - resolution: "@types/luxon@npm:3.3.4" - checksum: aa4862bd62d748e7966f9a0ec76058e9d74397ee426c7d64f61c677d83de0303c303cc78515001833df3f4ad16c95f572b0e2ffaee6e55bc50b80857e8437f3a + version: 3.3.8 + resolution: "@types/luxon@npm:3.3.8" + checksum: 026711b4aefc01c6e233e359c693b6d4c0873acc3f52a08661ea3751b2cf3e34d1ce1d5b0b99b0e40a059a2a3755acc20498d37db5f9a5a4f0f330cdf72db3db languageName: node linkType: hard @@ -3897,7 +3897,7 @@ __metadata: "@tsconfig/docusaurus": ^2.0.0 "@types/luxon": ^3.0.0 "@types/webpack-env": ^1.18.0 - clsx: ^1.1.1 + clsx: ^2.0.0 docusaurus-plugin-sass: ^0.2.3 js-yaml: ^4.1.0 luxon: ^3.0.0 @@ -4375,13 +4375,20 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^1.1.1, clsx@npm:^1.2.1": +"clsx@npm:^1.2.1": version: 1.2.1 resolution: "clsx@npm:1.2.1" checksum: 30befca8019b2eb7dbad38cff6266cf543091dae2825c856a62a8ccf2c3ab9c2907c4d12b288b73101196767f66812365400a227581484a05f968b0307cfaf12 languageName: node linkType: hard +"clsx@npm:^2.0.0": + version: 2.1.0 + resolution: "clsx@npm:2.1.0" + checksum: 43fefc29b6b49c9476fbce4f8b1cc75c27b67747738e598e6651dd40d63692135dc60b18fa1c5b78a2a9ba8ae6fd2055a068924b94e20b42039bd53b78b98e1d + languageName: node + linkType: hard + "color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" @@ -5867,12 +5874,12 @@ __metadata: linkType: hard "follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.14.9": - version: 1.15.2 - resolution: "follow-redirects@npm:1.15.2" + version: 1.15.4 + resolution: "follow-redirects@npm:1.15.4" peerDependenciesMeta: debug: optional: true - checksum: faa66059b66358ba65c234c2f2a37fcec029dc22775f35d9ad6abac56003268baf41e55f9ee645957b32c7d9f62baf1f0b906e68267276f54ec4b4c597c2b190 + checksum: e178d1deff8b23d5d24ec3f7a94cde6e47d74d0dc649c35fc9857041267c12ec5d44650a0c5597ef83056ada9ea6ca0c30e7c4f97dbf07d035086be9e6a5b7b6 languageName: node linkType: hard @@ -10810,15 +10817,15 @@ __metadata: linkType: hard "sass@npm:^1.57.1": - version: 1.69.5 - resolution: "sass@npm:1.69.5" + version: 1.70.0 + resolution: "sass@npm:1.70.0" dependencies: chokidar: ">=3.0.0 <4.0.0" immutable: ^4.0.0 source-map-js: ">=0.6.2 <2.0.0" bin: sass: sass.js - checksum: c66f4f02882e7aa7aa49703824dadbb5a174dcd05e3cd96f17f73687889aab6027d078b518e2c04b594dfd89b2f574824bf935c7aee46c770aa6be9aafcc49f2 + checksum: fd1b622cf9b7fa699a03ec634611997552ece45eb98ac365fef22f42bdcb8ed63b326b64173379c966830c8551ae801e44e4a00d2de16fdadda2dc8f35400bbb languageName: node linkType: hard diff --git a/mkdocs.yml b/mkdocs.yml index de66c97884..8c9f84ba81 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -128,6 +128,10 @@ nav: - Composability System: 'plugins/composability.md' - Plugin Analytics: 'plugins/analytics.md' - Feature Flags: 'plugins/feature-flags.md' + - OpenAPI: + - Schema-first plugins with OpenAPI (Experimental): 'openapi/01-getting-started.md' + - Generate a client from your OpenAPI spec: 'openapi/generate-client.md' + - Validate your OpenAPI spec against test data: 'openapi/test-case-validation.md' - Backends and APIs: - Proxying: 'plugins/proxying.md' - Backend plugin: 'plugins/backend-plugin.md' @@ -193,6 +197,8 @@ nav: - Switching Backstage from SQLite to PostgreSQL: 'tutorials/switching-sqlite-postgres.md' - Using the Backstage Proxy from Within a Plugin: 'tutorials/using-backstage-proxy-within-plugin.md' - Migration to Yarn 3: 'tutorials/yarn-migration.md' + - Migration to Material UI v5: 'tutorials/migrate-to-mui5.md' + - Setup OpenTelemetry: 'tutorials/setup-opentelemetry.md' - Architecture Decision Records (ADRs): - Overview: 'architecture-decisions/index.md' - ADR001 - Architecture Decision Record (ADR) log: 'architecture-decisions/adr001-add-adr-log.md' @@ -208,4 +214,7 @@ nav: - ADR011 - Plugin Package Structure: 'architecture-decisions/adr011-plugin-package-structure.md' - ADR012 - Plugin Package Structure: 'architecture-decisions/adr012-use-luxon-locale-and-date-presets.md' - ADR013 - Plugin Package Structure: 'architecture-decisions/adr013-use-node-fetch.md' - - FAQ: FAQ.md + - FAQ: + - Overview: 'faq/index.md' + - Product FAQ: 'faq/product.md' + - Technical FAQ: 'faq/technical.md' diff --git a/package.json b/package.json index 1b23829f76..e24cedb527 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,16 @@ }, "scripts": { "dev": "concurrently 'yarn start' 'yarn start-backend'", + "dev:next": "concurrently 'yarn start:next' 'yarn start-backend:next'", "start": "yarn workspace example-app start", "start-backend": "yarn workspace example-backend start", + "start:next": "yarn workspace example-app-next start", + "start-backend:next": "yarn workspace example-backend-next start", "build:backend": "yarn workspace example-backend build", "build:all": "backstage-cli repo build --all", "build:api-reports": "yarn build:api-reports:only --tsc", "build:api-reports:only": "backstage-repo-tools api-reports --allow-warnings 'packages/core-components,plugins/+(catalog|catalog-import|git-release-manager|jenkins|kubernetes)' -o ae-wrong-input-file-type --validate-release-tags", - "build:api-docs": "LANG=en_EN yarn build:api-reports --docs", + "build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(adr|adr-backend|adr-common|airbrake|airbrake-backend|allure|analytics-module-ga|analytics-module-ga4|analytics-module-newrelic-browser|apache-airflow|api-docs|api-docs-module-protoc-gen-doc|apollo-explorer|app-visualizer|azure-devops|azure-devops-backend|azure-devops-common|azure-sites|azure-sites-backend|azure-sites-common|badges|badges-backend|bazaar|bazaar-backend|bitbucket-cloud-common|bitrise|catalog-graph|catalog-graphql|catalog-import|catalog-unprocessed-entities|cicd-statistics|cicd-statistics-module-gitlab|circleci|cloudbuild|code-climate|code-coverage|code-coverage-backend|codescene|config-schema|cost-insights|cost-insights-common|dynatrace|entity-feedback|entity-feedback-backend|entity-feedback-common|entity-validation|example-todo-list|example-todo-list-backend|example-todo-list-common|firehydrant|fossa|gcalendar|gcp-projects|git-release-manager|github-actions|github-deployments|github-issues|github-pull-requests-board|gitops-profiles|gocd|graphiql|graphql-backend|graphql-voyager|ilert|jenkins|jenkins-backend|jenkins-common|kafka|kafka-backend|lighthouse|lighthouse-backend|lighthouse-common|linguist|linguist-backend|linguist-common|microsoft-calendar|newrelic|newrelic-dashboard|nomad|nomad-backend|octopus-deploy|opencost|pagerduty|periskop|periskop-backend|playlist|playlist-backend|playlist-common|proxy-backend|puppetdb|rollbar|rollbar-backend|sentry|shortcuts|splunk-on-call|stack-overflow|stack-overflow-backend|stackstorm|tech-radar|tech-radar-2|todo|todo-backend|xcmetrics)'", "build:plugins-report": "node ./scripts/build-plugins-report", "tsc": "tsc", "tsc:full": "backstage-cli repo clean && tsc --skipLibCheck false --incremental false", @@ -50,11 +53,10 @@ "@types/react": "^18", "@types/react-dom": "^18", "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch", - "mock-fs@^5.2.0": "patch:mock-fs@npm%3A5.2.0#./.yarn/patches/mock-fs-npm-5.2.0-5103a7b507.patch", "@material-ui/pickers@^3.3.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch", "@material-ui/pickers@^3.2.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch" }, - "version": "1.21.0-next.1", + "version": "1.23.0-next.0", "dependencies": { "@backstage/errors": "workspace:^", "@manypkg/get-packages": "^1.1.3", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 62873d9809..9ac73e575a 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,94 @@ # @backstage/app-defaults +## 1.4.8-next.0 + +### Patch Changes + +- f899eec: Change default icon for `kind:resource` to the storage icon. +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/plugin-permission-react@0.4.19 + +## 1.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + +## 1.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/theme@0.5.0 + +## 1.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + +## 1.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/core-app-api@1.11.2 + - @backstage/plugin-permission-react@0.4.18 + +## 1.4.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## 1.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## 1.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/theme@0.5.0-next.0 + ## 1.4.6-next.0 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 550672d7fb..4b15d5875a 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/app-defaults", "description": "Provides the default wiring of a Backstage App", - "version": "1.4.6-next.0", + "version": "1.4.8-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/app-defaults/src/defaults/icons.tsx b/packages/app-defaults/src/defaults/icons.tsx index fbdfb14e5b..1ed5bf03c6 100644 --- a/packages/app-defaults/src/defaults/icons.tsx +++ b/packages/app-defaults/src/defaults/icons.tsx @@ -34,7 +34,7 @@ import MuiMenuBookIcon from '@material-ui/icons/MenuBook'; import MuiPeopleIcon from '@material-ui/icons/People'; import MuiPersonIcon from '@material-ui/icons/Person'; import MuiWarningIcon from '@material-ui/icons/Warning'; -import MuiWorkIcon from '@material-ui/icons/Work'; +import MuiStorageIcon from '@material-ui/icons/Storage'; import MuiFeaturedPlayListIcon from '@material-ui/icons/FeaturedPlayList'; export const icons = { @@ -58,7 +58,7 @@ export const icons = { 'kind:location': MuiLocationOnIcon as IconComponent, 'kind:system': MuiCategoryIcon as IconComponent, 'kind:user': MuiPersonIcon as IconComponent, - 'kind:resource': MuiWorkIcon as IconComponent, + 'kind:resource': MuiStorageIcon as IconComponent, 'kind:template': MuiFeaturedPlayListIcon as IconComponent, user: MuiPersonIcon as IconComponent, warning: MuiWarningIcon as IconComponent, diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md index 2eff29241d..4e41b9690a 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,76 @@ # app-next-example-plugin +## 0.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + +## 0.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + +## 0.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + +## 0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + +## 0.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + +## 0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-components@0.13.9 + +## 0.0.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + +## 0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/core-components@0.13.9-next.2 + +## 0.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + ## 0.0.4-next.0 ### Patch Changes diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index b3de8f9a83..05d7e20823 100644 --- a/packages/app-next-example-plugin/package.json +++ b/packages/app-next-example-plugin/package.json @@ -1,7 +1,7 @@ { "name": "app-next-example-plugin", "description": "Backstage internal example plugin", - "version": "0.0.4-next.0", + "version": "0.0.6-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/app-next-example-plugin/src/plugin.tsx b/packages/app-next-example-plugin/src/plugin.tsx index a3c95e900c..99c377b7e5 100644 --- a/packages/app-next-example-plugin/src/plugin.tsx +++ b/packages/app-next-example-plugin/src/plugin.tsx @@ -21,7 +21,6 @@ import { } from '@backstage/frontend-plugin-api'; export const ExamplePage = createPageExtension({ - id: 'example.page', defaultPath: '/example', loader: () => import('./Component').then(m => ), }); diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 879025f1a0..e641aa8f71 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,679 @@ # example-app-next +## 0.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-import@0.10.6-next.0 + - @backstage/plugin-catalog-graph@0.3.4-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-catalog@1.17.0-next.0 + - @backstage/plugin-adr@0.6.13-next.0 + - @backstage/app-defaults@1.4.8-next.0 + - @backstage/frontend-app-api@0.6.0-next.0 + - @backstage/plugin-scaffolder-react@1.8.0-next.0 + - @backstage/plugin-scaffolder@1.18.0-next.0 + - @backstage/plugin-devtools@0.1.9-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/plugin-user-settings@0.8.1-next.0 + - @backstage/plugin-tech-radar@0.6.13-next.0 + - @backstage/plugin-graphiql@0.3.3-next.0 + - @backstage/plugin-techdocs@1.9.4-next.0 + - @backstage/plugin-search@1.4.6-next.0 + - @backstage/plugin-api-docs@0.10.4-next.0 + - @backstage/cli@0.25.2-next.0 + - @backstage/plugin-home@0.6.2-next.0 + - @backstage/plugin-cloudbuild@0.4.0-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.5-next.0 + - @backstage/plugin-azure-devops@0.3.12-next.0 + - @backstage/plugin-linguist@0.1.15-next.0 + - @backstage/plugin-airbrake@0.3.30-next.0 + - @backstage/plugin-azure-sites@0.1.19-next.0 + - @backstage/plugin-badges@0.2.54-next.0 + - @backstage/plugin-code-coverage@0.2.23-next.0 + - @backstage/plugin-cost-insights@0.12.19-next.0 + - @backstage/plugin-dynatrace@8.0.4-next.0 + - @backstage/plugin-entity-feedback@0.2.13-next.0 + - @backstage/plugin-explore@0.4.16-next.0 + - @backstage/plugin-github-actions@0.6.11-next.0 + - @backstage/plugin-gocd@0.1.36-next.0 + - @backstage/plugin-jenkins@0.9.5-next.0 + - @backstage/plugin-kafka@0.3.30-next.0 + - @backstage/plugin-kubernetes@0.11.5-next.0 + - @backstage/plugin-lighthouse@0.4.15-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.5-next.0 + - @backstage/plugin-octopus-deploy@0.2.12-next.0 + - @backstage/plugin-org@0.6.20-next.0 + - @backstage/plugin-pagerduty@0.7.2-next.0 + - @backstage/plugin-playlist@0.2.4-next.0 + - @backstage/plugin-puppetdb@0.1.13-next.0 + - @backstage/plugin-rollbar@0.4.30-next.0 + - @backstage/plugin-sentry@0.5.15-next.0 + - @backstage/plugin-tech-insights@0.3.22-next.0 + - @backstage/plugin-todo@0.2.34-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - app-next-example-plugin@0.0.6-next.0 + - @backstage/plugin-app-visualizer@0.1.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration-react@1.1.23 + - @backstage/theme@0.5.0 + - @backstage/plugin-apache-airflow@0.2.19 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7 + - @backstage/plugin-gcalendar@0.3.22 + - @backstage/plugin-gcp-projects@0.3.45 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.11 + - @backstage/plugin-newrelic@0.3.44 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-search-common@1.2.10 + - @backstage/plugin-shortcuts@0.3.18 + - @backstage/plugin-stackstorm@0.1.10 + - @backstage/plugin-techdocs-react@1.1.15 + +## 0.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.10.3 + - @backstage/core-compat-api@0.1.1 + - @backstage/plugin-scaffolder-react@1.7.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-user-settings@0.8.0 + - @backstage/plugin-azure-sites@0.1.18 + - @backstage/cli@0.25.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-octopus-deploy@0.2.11 + - @backstage/frontend-app-api@0.5.0 + - @backstage/plugin-kubernetes@0.11.4 + - @backstage/plugin-home@0.6.1 + - @backstage/plugin-scaffolder@1.17.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7 + - @backstage/plugin-microsoft-calendar@0.1.11 + - @backstage/plugin-newrelic-dashboard@0.3.4 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-entity-feedback@0.2.12 + - @backstage/plugin-apache-airflow@0.2.19 + - @backstage/plugin-github-actions@0.6.10 + - @backstage/plugin-techdocs-react@1.1.15 + - @backstage/plugin-catalog-graph@0.3.3 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-code-coverage@0.2.22 + - @backstage/plugin-cost-insights@0.12.18 + - @backstage/plugin-tech-insights@0.3.21 + - @backstage/plugin-azure-devops@0.3.11 + - @backstage/plugin-gcp-projects@0.3.45 + - @backstage/plugin-cloudbuild@0.3.29 + - @backstage/plugin-lighthouse@0.4.14 + - @backstage/plugin-stackstorm@0.1.10 + - @backstage/plugin-tech-radar@0.6.12 + - @backstage/plugin-dynatrace@8.0.3 + - @backstage/plugin-gcalendar@0.3.22 + - @backstage/plugin-pagerduty@0.7.1 + - @backstage/plugin-shortcuts@0.3.18 + - @backstage/plugin-airbrake@0.3.29 + - @backstage/plugin-devtools@0.1.8 + - @backstage/plugin-graphiql@0.3.2 + - @backstage/plugin-linguist@0.1.14 + - @backstage/plugin-newrelic@0.3.44 + - @backstage/plugin-playlist@0.2.3 + - @backstage/plugin-puppetdb@0.1.12 + - @backstage/plugin-techdocs@1.9.3 + - @backstage/plugin-catalog@1.16.1 + - @backstage/plugin-explore@0.4.15 + - @backstage/plugin-jenkins@0.9.4 + - @backstage/plugin-rollbar@0.4.29 + - @backstage/plugin-badges@0.2.53 + - @backstage/plugin-search@1.4.5 + - @backstage/plugin-sentry@0.5.14 + - @backstage/plugin-kafka@0.3.29 + - @backstage/plugin-gocd@0.1.35 + - @backstage/plugin-todo@0.2.33 + - @backstage/plugin-adr@0.6.12 + - @backstage/plugin-org@0.6.19 + - @backstage/plugin-app-visualizer@0.1.0 + - @backstage/plugin-catalog-import@0.10.5 + - app-next-example-plugin@0.0.5 + - @backstage/plugin-search-react@1.7.5 + - @backstage/app-defaults@1.4.7 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.10 + +## 0.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/plugin-user-settings@0.8.0-next.2 + - @backstage/plugin-octopus-deploy@0.2.11-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/frontend-app-api@0.4.1-next.2 + - @backstage/plugin-home@0.6.1-next.2 + - @backstage/plugin-scaffolder-react@1.7.1-next.2 + - @backstage/plugin-scaffolder@1.17.1-next.2 + - @backstage/plugin-linguist@0.1.14-next.2 + - @backstage/plugin-catalog@1.16.1-next.2 + - @backstage/plugin-catalog-import@0.10.5-next.2 + - @backstage/plugin-graphiql@0.3.2-next.2 + - @backstage/plugin-search@1.4.5-next.2 + - @backstage/plugin-tech-radar@0.6.12-next.2 + - @backstage/plugin-techdocs@1.9.3-next.2 + - app-next-example-plugin@0.0.5-next.2 + - @backstage/plugin-adr@0.6.12-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-explore@0.4.15-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + - @backstage/plugin-visualizer@0.0.2-next.2 + - @backstage/cli@0.25.1-next.1 + - @backstage/plugin-pagerduty@0.7.1-next.2 + - @backstage/plugin-api-docs@0.10.3-next.2 + - @backstage/plugin-catalog-graph@0.3.3-next.2 + - @backstage/plugin-org@0.6.19-next.2 + - @backstage/plugin-airbrake@0.3.29-next.2 + - @backstage/plugin-azure-devops@0.3.11-next.2 + - @backstage/plugin-azure-sites@0.1.18-next.2 + - @backstage/plugin-badges@0.2.53-next.2 + - @backstage/plugin-cloudbuild@0.3.29-next.2 + - @backstage/plugin-code-coverage@0.2.22-next.2 + - @backstage/plugin-cost-insights@0.12.18-next.2 + - @backstage/plugin-dynatrace@8.0.3-next.2 + - @backstage/plugin-entity-feedback@0.2.12-next.2 + - @backstage/plugin-github-actions@0.6.10-next.2 + - @backstage/plugin-gocd@0.1.35-next.2 + - @backstage/plugin-jenkins@0.9.4-next.2 + - @backstage/plugin-kafka@0.3.29-next.2 + - @backstage/plugin-kubernetes@0.11.4-next.2 + - @backstage/plugin-lighthouse@0.4.14-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.2 + - @backstage/plugin-playlist@0.2.3-next.2 + - @backstage/plugin-puppetdb@0.1.12-next.2 + - @backstage/plugin-rollbar@0.4.29-next.2 + - @backstage/plugin-sentry@0.5.14-next.2 + - @backstage/plugin-tech-insights@0.3.21-next.2 + - @backstage/plugin-todo@0.2.33-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-apache-airflow@0.2.19-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.1 + - @backstage/plugin-devtools@0.1.8-next.1 + - @backstage/plugin-gcalendar@0.3.22-next.1 + - @backstage/plugin-gcp-projects@0.3.45-next.1 + - @backstage/plugin-microsoft-calendar@0.1.11-next.1 + - @backstage/plugin-newrelic@0.3.44-next.1 + - @backstage/plugin-shortcuts@0.3.18-next.1 + - @backstage/plugin-stackstorm@0.1.10-next.1 + +## 0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-sites@0.1.18-next.1 + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-user-settings@0.8.0-next.1 + - @backstage/plugin-kubernetes@0.11.4-next.1 + - @backstage/core-components@0.13.10-next.1 + - @backstage/app-defaults@1.4.7-next.1 + - @backstage/cli@0.25.1-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-app-api@0.4.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-adr@0.6.12-next.1 + - @backstage/plugin-airbrake@0.3.29-next.1 + - @backstage/plugin-apache-airflow@0.2.19-next.1 + - @backstage/plugin-api-docs@0.10.3-next.1 + - @backstage/plugin-azure-devops@0.3.11-next.1 + - @backstage/plugin-badges@0.2.53-next.1 + - @backstage/plugin-catalog@1.16.1-next.1 + - @backstage/plugin-catalog-graph@0.3.3-next.1 + - @backstage/plugin-catalog-import@0.10.5-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.1 + - @backstage/plugin-cloudbuild@0.3.29-next.1 + - @backstage/plugin-code-coverage@0.2.22-next.1 + - @backstage/plugin-cost-insights@0.12.18-next.1 + - @backstage/plugin-devtools@0.1.8-next.1 + - @backstage/plugin-dynatrace@8.0.3-next.1 + - @backstage/plugin-entity-feedback@0.2.12-next.1 + - @backstage/plugin-explore@0.4.15-next.1 + - @backstage/plugin-gcalendar@0.3.22-next.1 + - @backstage/plugin-gcp-projects@0.3.45-next.1 + - @backstage/plugin-github-actions@0.6.10-next.1 + - @backstage/plugin-gocd@0.1.35-next.1 + - @backstage/plugin-graphiql@0.3.2-next.1 + - @backstage/plugin-home@0.6.1-next.1 + - @backstage/plugin-jenkins@0.9.4-next.1 + - @backstage/plugin-kafka@0.3.29-next.1 + - @backstage/plugin-lighthouse@0.4.14-next.1 + - @backstage/plugin-linguist@0.1.14-next.1 + - @backstage/plugin-microsoft-calendar@0.1.11-next.1 + - @backstage/plugin-newrelic@0.3.44-next.1 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.1 + - @backstage/plugin-octopus-deploy@0.2.11-next.1 + - @backstage/plugin-org@0.6.19-next.1 + - @backstage/plugin-pagerduty@0.7.1-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/plugin-playlist@0.2.3-next.1 + - @backstage/plugin-puppetdb@0.1.12-next.1 + - @backstage/plugin-rollbar@0.4.29-next.1 + - @backstage/plugin-scaffolder@1.17.1-next.1 + - @backstage/plugin-scaffolder-react@1.7.1-next.1 + - @backstage/plugin-search@1.4.5-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/plugin-sentry@0.5.14-next.1 + - @backstage/plugin-shortcuts@0.3.18-next.1 + - @backstage/plugin-stackstorm@0.1.10-next.1 + - @backstage/plugin-tech-insights@0.3.21-next.1 + - @backstage/plugin-tech-radar@0.6.12-next.1 + - @backstage/plugin-techdocs@1.9.3-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.1 + - @backstage/plugin-techdocs-react@1.1.15-next.1 + - @backstage/plugin-todo@0.2.33-next.1 + - @backstage/plugin-visualizer@0.0.2-next.1 + - app-next-example-plugin@0.0.5-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.7.1-next.0 + - @backstage/core-components@0.13.10-next.0 + - @backstage/cli@0.25.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-microsoft-calendar@0.1.11-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/plugin-entity-feedback@0.2.12-next.0 + - @backstage/plugin-apache-airflow@0.2.19-next.0 + - @backstage/plugin-github-actions@0.6.10-next.0 + - @backstage/plugin-octopus-deploy@0.2.11-next.0 + - @backstage/plugin-techdocs-react@1.1.15-next.0 + - @backstage/plugin-catalog-graph@0.3.3-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-code-coverage@0.2.22-next.0 + - @backstage/plugin-cost-insights@0.12.18-next.0 + - @backstage/plugin-tech-insights@0.3.21-next.0 + - @backstage/plugin-user-settings@0.7.15-next.0 + - @backstage/plugin-azure-devops@0.3.11-next.0 + - @backstage/plugin-gcp-projects@0.3.45-next.0 + - @backstage/plugin-azure-sites@0.1.18-next.0 + - @backstage/plugin-cloudbuild@0.3.29-next.0 + - @backstage/plugin-kubernetes@0.11.4-next.0 + - @backstage/plugin-lighthouse@0.4.14-next.0 + - @backstage/plugin-scaffolder@1.17.1-next.0 + - @backstage/plugin-stackstorm@0.1.10-next.0 + - @backstage/plugin-tech-radar@0.6.12-next.0 + - @backstage/plugin-dynatrace@8.0.3-next.0 + - @backstage/plugin-gcalendar@0.3.22-next.0 + - @backstage/plugin-pagerduty@0.7.1-next.0 + - @backstage/plugin-shortcuts@0.3.18-next.0 + - @backstage/plugin-airbrake@0.3.29-next.0 + - @backstage/plugin-api-docs@0.10.3-next.0 + - @backstage/plugin-devtools@0.1.8-next.0 + - @backstage/plugin-graphiql@0.3.2-next.0 + - @backstage/plugin-linguist@0.1.14-next.0 + - @backstage/plugin-newrelic@0.3.44-next.0 + - @backstage/plugin-playlist@0.2.3-next.0 + - @backstage/plugin-puppetdb@0.1.12-next.0 + - @backstage/plugin-techdocs@1.9.3-next.0 + - @backstage/plugin-catalog@1.16.1-next.0 + - @backstage/plugin-explore@0.4.15-next.0 + - @backstage/plugin-jenkins@0.9.4-next.0 + - @backstage/plugin-rollbar@0.4.29-next.0 + - @backstage/plugin-badges@0.2.53-next.0 + - @backstage/plugin-search@1.4.5-next.0 + - @backstage/plugin-sentry@0.5.14-next.0 + - @backstage/plugin-kafka@0.3.29-next.0 + - @backstage/plugin-gocd@0.1.35-next.0 + - @backstage/plugin-home@0.6.1-next.0 + - @backstage/plugin-todo@0.2.33-next.0 + - @backstage/plugin-adr@0.6.12-next.0 + - @backstage/plugin-org@0.6.19-next.0 + - @backstage/app-defaults@1.4.7-next.0 + - app-next-example-plugin@0.0.5-next.0 + - @backstage/frontend-app-api@0.4.1-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-catalog-import@0.10.5-next.0 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/plugin-visualizer@0.0.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.2 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/frontend-app-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/plugin-api-docs@0.10.2 + - @backstage/core-components@0.13.9 + - @backstage/cli@0.25.0 + - @backstage/theme@0.5.0 + - @backstage/plugin-scaffolder@1.17.0 + - @backstage/plugin-home@0.6.0 + - @backstage/plugin-catalog@1.16.0 + - @backstage/plugin-scaffolder-react@1.7.0 + - @backstage/plugin-kubernetes@0.11.3 + - @backstage/plugin-org@0.6.18 + - @backstage/plugin-github-actions@0.6.9 + - @backstage/plugin-azure-devops@0.3.10 + - @backstage/core-app-api@1.11.2 + - @backstage/plugin-lighthouse@0.4.13 + - @backstage/plugin-explore@0.4.14 + - @backstage/plugin-catalog-import@0.10.4 + - @backstage/plugin-user-settings@0.7.14 + - @backstage/plugin-tech-radar@0.6.11 + - @backstage/plugin-graphiql@0.3.1 + - @backstage/plugin-techdocs@1.9.2 + - @backstage/plugin-search@1.4.4 + - @backstage/plugin-search-react@1.7.4 + - @backstage/plugin-adr@0.6.11 + - @backstage/plugin-gcp-projects@0.3.44 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3 + - @backstage/plugin-pagerduty@0.7.0 + - @backstage/app-defaults@1.4.6 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-airbrake@0.3.28 + - @backstage/plugin-apache-airflow@0.2.18 + - @backstage/plugin-azure-sites@0.1.17 + - @backstage/plugin-badges@0.2.52 + - @backstage/plugin-catalog-graph@0.3.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6 + - @backstage/plugin-cloudbuild@0.3.28 + - @backstage/plugin-code-coverage@0.2.21 + - @backstage/plugin-cost-insights@0.12.17 + - @backstage/plugin-devtools@0.1.7 + - @backstage/plugin-dynatrace@8.0.2 + - @backstage/plugin-entity-feedback@0.2.11 + - @backstage/plugin-gcalendar@0.3.21 + - @backstage/plugin-gocd@0.1.34 + - @backstage/plugin-jenkins@0.9.3 + - @backstage/plugin-kafka@0.3.28 + - @backstage/plugin-linguist@0.1.13 + - @backstage/plugin-microsoft-calendar@0.1.10 + - @backstage/plugin-newrelic@0.3.43 + - @backstage/plugin-newrelic-dashboard@0.3.3 + - @backstage/plugin-octopus-deploy@0.2.10 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/plugin-playlist@0.2.2 + - @backstage/plugin-puppetdb@0.1.11 + - @backstage/plugin-rollbar@0.4.28 + - @backstage/plugin-sentry@0.5.13 + - @backstage/plugin-shortcuts@0.3.17 + - @backstage/plugin-stackstorm@0.1.9 + - @backstage/plugin-tech-insights@0.3.20 + - @backstage/plugin-techdocs-react@1.1.14 + - @backstage/plugin-todo@0.2.32 + - @backstage/plugin-visualizer@0.0.1 + - app-next-example-plugin@0.0.4 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.0.4-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.6.0-next.3 + - @backstage/plugin-org@0.6.18-next.3 + - @backstage/plugin-azure-devops@0.3.10-next.3 + - @backstage/cli@0.25.0-next.3 + - @backstage/plugin-api-docs@0.10.2-next.4 + - @backstage/plugin-scaffolder-react@1.6.2-next.3 + - @backstage/plugin-scaffolder@1.16.2-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/app-defaults@1.4.6-next.3 + - app-next-example-plugin@0.0.4-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-app-api@0.4.0-next.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-adr@0.6.11-next.3 + - @backstage/plugin-airbrake@0.3.28-next.3 + - @backstage/plugin-apache-airflow@0.2.18-next.3 + - @backstage/plugin-azure-sites@0.1.17-next.3 + - @backstage/plugin-badges@0.2.52-next.3 + - @backstage/plugin-catalog@1.16.0-next.4 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-graph@0.3.2-next.3 + - @backstage/plugin-catalog-import@0.10.4-next.4 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.3 + - @backstage/plugin-cloudbuild@0.3.28-next.3 + - @backstage/plugin-code-coverage@0.2.21-next.3 + - @backstage/plugin-cost-insights@0.12.17-next.3 + - @backstage/plugin-devtools@0.1.7-next.3 + - @backstage/plugin-dynatrace@8.0.2-next.3 + - @backstage/plugin-entity-feedback@0.2.11-next.3 + - @backstage/plugin-explore@0.4.14-next.3 + - @backstage/plugin-gcalendar@0.3.21-next.3 + - @backstage/plugin-gcp-projects@0.3.44-next.3 + - @backstage/plugin-github-actions@0.6.9-next.3 + - @backstage/plugin-gocd@0.1.34-next.3 + - @backstage/plugin-graphiql@0.3.1-next.4 + - @backstage/plugin-jenkins@0.9.3-next.3 + - @backstage/plugin-kafka@0.3.28-next.3 + - @backstage/plugin-kubernetes@0.11.3-next.3 + - @backstage/plugin-lighthouse@0.4.13-next.3 + - @backstage/plugin-linguist@0.1.13-next.3 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.10-next.3 + - @backstage/plugin-newrelic@0.3.43-next.3 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.3 + - @backstage/plugin-octopus-deploy@0.2.10-next.3 + - @backstage/plugin-pagerduty@0.7.0-next.3 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.3 + - @backstage/plugin-puppetdb@0.1.11-next.3 + - @backstage/plugin-rollbar@0.4.28-next.3 + - @backstage/plugin-search@1.4.4-next.4 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + - @backstage/plugin-sentry@0.5.13-next.3 + - @backstage/plugin-shortcuts@0.3.17-next.3 + - @backstage/plugin-stackstorm@0.1.9-next.3 + - @backstage/plugin-tech-insights@0.3.20-next.3 + - @backstage/plugin-tech-radar@0.6.11-next.4 + - @backstage/plugin-techdocs@1.9.2-next.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.3 + - @backstage/plugin-techdocs-react@1.1.14-next.3 + - @backstage/plugin-todo@0.2.32-next.3 + - @backstage/plugin-user-settings@0.7.14-next.4 + +## 0.0.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/frontend-app-api@0.4.0-next.2 + - @backstage/cli@0.25.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-kubernetes@0.11.3-next.2 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/plugin-lighthouse@0.4.13-next.2 + - @backstage/plugin-catalog-import@0.10.4-next.3 + - @backstage/plugin-user-settings@0.7.14-next.3 + - @backstage/plugin-tech-radar@0.6.11-next.3 + - @backstage/plugin-graphiql@0.3.1-next.3 + - @backstage/plugin-techdocs@1.9.2-next.3 + - @backstage/plugin-catalog@1.16.0-next.3 + - @backstage/plugin-search@1.4.4-next.3 + - @backstage/plugin-home@0.6.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/plugin-explore@0.4.14-next.2 + - @backstage/plugin-adr@0.6.11-next.2 + - @backstage/plugin-scaffolder-react@1.6.2-next.2 + - app-next-example-plugin@0.0.4-next.2 + - @backstage/app-defaults@1.4.6-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-airbrake@0.3.28-next.2 + - @backstage/plugin-apache-airflow@0.2.18-next.2 + - @backstage/plugin-api-docs@0.10.2-next.3 + - @backstage/plugin-azure-devops@0.3.10-next.2 + - @backstage/plugin-azure-sites@0.1.17-next.2 + - @backstage/plugin-badges@0.2.52-next.2 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-graph@0.3.2-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.2 + - @backstage/plugin-cloudbuild@0.3.28-next.2 + - @backstage/plugin-code-coverage@0.2.21-next.2 + - @backstage/plugin-cost-insights@0.12.17-next.2 + - @backstage/plugin-devtools@0.1.7-next.2 + - @backstage/plugin-dynatrace@8.0.2-next.2 + - @backstage/plugin-entity-feedback@0.2.11-next.2 + - @backstage/plugin-gcalendar@0.3.21-next.2 + - @backstage/plugin-gcp-projects@0.3.44-next.2 + - @backstage/plugin-github-actions@0.6.9-next.2 + - @backstage/plugin-gocd@0.1.34-next.2 + - @backstage/plugin-jenkins@0.9.3-next.2 + - @backstage/plugin-kafka@0.3.28-next.2 + - @backstage/plugin-linguist@0.1.13-next.2 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.10-next.2 + - @backstage/plugin-newrelic@0.3.43-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.2 + - @backstage/plugin-octopus-deploy@0.2.10-next.2 + - @backstage/plugin-org@0.6.18-next.2 + - @backstage/plugin-pagerduty@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.2 + - @backstage/plugin-puppetdb@0.1.11-next.2 + - @backstage/plugin-rollbar@0.4.28-next.2 + - @backstage/plugin-scaffolder@1.16.2-next.2 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-sentry@0.5.13-next.2 + - @backstage/plugin-shortcuts@0.3.17-next.2 + - @backstage/plugin-stackstorm@0.1.9-next.2 + - @backstage/plugin-tech-insights@0.3.20-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.2 + - @backstage/plugin-techdocs-react@1.1.14-next.2 + - @backstage/plugin-todo@0.2.32-next.2 + +## 0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/frontend-app-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/cli@0.25.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-catalog@1.16.0-next.2 + - @backstage/plugin-scaffolder-react@1.6.2-next.1 + - @backstage/plugin-home@0.6.0-next.1 + - @backstage/plugin-github-actions@0.6.9-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/plugin-azure-devops@0.3.10-next.1 + - @backstage/plugin-scaffolder@1.16.2-next.1 + - @backstage/plugin-api-docs@0.10.2-next.2 + - @backstage/plugin-gcp-projects@0.3.44-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.1 + - @backstage/plugin-user-settings@0.7.14-next.2 + - @backstage/plugin-adr@0.6.11-next.1 + - @backstage/plugin-pagerduty@0.7.0-next.1 + - app-next-example-plugin@0.0.4-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/plugin-catalog-import@0.10.4-next.2 + - @backstage/plugin-explore@0.4.14-next.1 + - @backstage/plugin-graphiql@0.3.1-next.2 + - @backstage/plugin-search@1.4.4-next.2 + - @backstage/plugin-tech-radar@0.6.11-next.2 + - @backstage/plugin-techdocs@1.9.2-next.2 + - @backstage/app-defaults@1.4.6-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-airbrake@0.3.28-next.1 + - @backstage/plugin-apache-airflow@0.2.18-next.1 + - @backstage/plugin-azure-sites@0.1.17-next.1 + - @backstage/plugin-badges@0.2.52-next.1 + - @backstage/plugin-catalog-graph@0.3.2-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.1 + - @backstage/plugin-cloudbuild@0.3.28-next.1 + - @backstage/plugin-code-coverage@0.2.21-next.1 + - @backstage/plugin-cost-insights@0.12.17-next.1 + - @backstage/plugin-devtools@0.1.7-next.1 + - @backstage/plugin-dynatrace@8.0.2-next.1 + - @backstage/plugin-entity-feedback@0.2.11-next.1 + - @backstage/plugin-gcalendar@0.3.21-next.1 + - @backstage/plugin-gocd@0.1.34-next.1 + - @backstage/plugin-jenkins@0.9.3-next.1 + - @backstage/plugin-kafka@0.3.28-next.1 + - @backstage/plugin-kubernetes@0.11.3-next.1 + - @backstage/plugin-lighthouse@0.4.13-next.1 + - @backstage/plugin-linguist@0.1.13-next.1 + - @backstage/plugin-microsoft-calendar@0.1.10-next.1 + - @backstage/plugin-newrelic@0.3.43-next.1 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.1 + - @backstage/plugin-octopus-deploy@0.2.10-next.1 + - @backstage/plugin-org@0.6.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.1 + - @backstage/plugin-puppetdb@0.1.11-next.1 + - @backstage/plugin-rollbar@0.4.28-next.1 + - @backstage/plugin-sentry@0.5.13-next.1 + - @backstage/plugin-shortcuts@0.3.17-next.1 + - @backstage/plugin-stackstorm@0.1.9-next.1 + - @backstage/plugin-tech-insights@0.3.20-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.1 + - @backstage/plugin-todo@0.2.32-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.8 + ## 0.0.4-next.1 ### Patch Changes diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index a46e119ac0..9cc3507fa4 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -3,22 +3,22 @@ app: packages: 'all' # ✨ routes: bindings: - plugin.pages.externalRoutes.pageX: plugin.pages.routes.pageX - plugin.catalog.externalRoutes.viewTechDoc: plugin.techdocs.routes.docRoot + pages.pageX: pages.pageX + catalog.viewTechDoc: techdocs.docRoot + catalog.createComponent: catalog-import.importPage extensions: - - apis.plugin.graphiql.browse.gitlab: true + # - apis.plugin.graphiql.browse.gitlab: true + - graphiql-endpoint:graphiql/gitlab: true - # Entity page cards - - entity.cards.about - - entity.cards.labels - - entity.cards.links: + - entity-card:catalog/about + - entity-card:catalog/labels + - entity-card:catalog/links: config: - filter: - - isKind: component - + filter: kind:component has:links + - entity-card:linguist/languages # Entity page content - - entity.content.techdocs + - entity-content:techdocs # scmAuthExtension: >- # createScmAuthExtension({ diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 30ac920c2c..5d29bad229 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.4-next.1", + "version": "0.0.6-next.0", "private": true, "backstage": { "role": "frontend" @@ -10,7 +10,6 @@ "@backstage/app-defaults": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/config": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", @@ -22,6 +21,7 @@ "@backstage/plugin-airbrake": "workspace:^", "@backstage/plugin-apache-airflow": "workspace:^", "@backstage/plugin-api-docs": "workspace:^", + "@backstage/plugin-app-visualizer": "workspace:^", "@backstage/plugin-azure-devops": "workspace:^", "@backstage/plugin-azure-sites": "workspace:^", "@backstage/plugin-badges": "workspace:^", @@ -31,7 +31,6 @@ "@backstage/plugin-catalog-import": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-catalog-unprocessed-entities": "workspace:^", - "@backstage/plugin-circleci": "workspace:^", "@backstage/plugin-cloudbuild": "workspace:^", "@backstage/plugin-code-coverage": "workspace:^", "@backstage/plugin-cost-insights": "workspace:^", @@ -77,6 +76,7 @@ "@backstage/plugin-todo": "workspace:^", "@backstage/plugin-user-settings": "workspace:^", "@backstage/theme": "workspace:^", + "@circleci/backstage-plugin": "^0.1.1", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index 978c40ef44..03a6649c27 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -17,6 +17,7 @@ import React from 'react'; import { createApp } from '@backstage/frontend-app-api'; import { pagesPlugin } from './examples/pagesPlugin'; +import notFoundErrorPage from './examples/notFoundErrorPageExtension'; import graphiqlPlugin from '@backstage/plugin-graphiql/alpha'; import techRadarPlugin from '@backstage/plugin-tech-radar/alpha'; import userSettingsPlugin from '@backstage/plugin-user-settings/alpha'; @@ -31,17 +32,24 @@ import { createExtensionOverrides, } from '@backstage/frontend-plugin-api'; import techdocsPlugin from '@backstage/plugin-techdocs/alpha'; +import appVisualizerPlugin from '@backstage/plugin-app-visualizer'; import { homePage } from './HomePage'; -import { collectLegacyRoutes } from '@backstage/core-compat-api'; +import { convertLegacyApp } from '@backstage/core-compat-api'; import { FlatRoutes } from '@backstage/core-app-api'; import { Route } from 'react-router'; import { CatalogImportPage } from '@backstage/plugin-catalog-import'; -import { createApiFactory, configApiRef } from '@backstage/core-plugin-api'; +import { + createApiFactory, + configApiRef, + SignInPageProps, +} from '@backstage/core-plugin-api'; import { ScmAuth, ScmIntegrationsApi, scmIntegrationsApiRef, } from '@backstage/integration-react'; +import { createSignInPageExtension } from '@backstage/frontend-plugin-api'; +import { SignInPage } from '@backstage/core-components'; /* @@ -73,7 +81,7 @@ TODO: /* app.tsx */ const homePageExtension = createExtension({ - id: 'myhomepage', + name: 'myhomepage', attachTo: { id: 'home', input: 'props' }, output: { children: coreExtensionData.reactElement, @@ -84,6 +92,12 @@ const homePageExtension = createExtension({ }, }); +const signInPage = createSignInPageExtension({ + name: 'guest', + loader: async () => (props: SignInPageProps) => + , +}); + const scmAuthExtension = createApiExtension({ factory: ScmAuth.createDefaultApiFactory(), }); @@ -96,7 +110,7 @@ const scmIntegrationApi = createApiExtension({ }), }); -const collectedLegacyPlugins = collectLegacyRoutes( +const collectedLegacyPlugins = convertLegacyApp( } /> , @@ -110,9 +124,16 @@ const app = createApp({ techdocsPlugin, userSettingsPlugin, homePlugin, + appVisualizerPlugin, ...collectedLegacyPlugins, createExtensionOverrides({ - extensions: [homePageExtension, scmAuthExtension, scmIntegrationApi], + extensions: [ + homePageExtension, + scmAuthExtension, + scmIntegrationApi, + signInPage, + notFoundErrorPage, + ], }), ], /* Handled through config instead */ diff --git a/packages/app-next/src/examples/notFoundErrorPageExtension.tsx b/packages/app-next/src/examples/notFoundErrorPageExtension.tsx new file mode 100644 index 0000000000..f83671bc23 --- /dev/null +++ b/packages/app-next/src/examples/notFoundErrorPageExtension.tsx @@ -0,0 +1,56 @@ +/* + * 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 { + createComponentExtension, + coreComponentRefs, +} from '@backstage/frontend-plugin-api'; +import { Box, Typography } from '@material-ui/core'; +import { Button } from '@backstage/core-components'; + +export function CustomNotFoundErrorPage() { + return ( + + 404 + + Unable to locate this page. Please contact your support team if this + page used to exist. + + + + ); +} + +export default createComponentExtension({ + ref: coreComponentRefs.notFoundErrorPage, + loader: { sync: () => CustomNotFoundErrorPage }, +}); diff --git a/packages/app-next/src/examples/pagesPlugin.tsx b/packages/app-next/src/examples/pagesPlugin.tsx index 1e9409a5e4..c5069f99c3 100644 --- a/packages/app-next/src/examples/pagesPlugin.tsx +++ b/packages/app-next/src/examples/pagesPlugin.tsx @@ -36,7 +36,7 @@ export const pageXRouteRef = createRouteRef(); // }); const IndexPage = createPageExtension({ - id: 'index', + name: 'index', defaultPath: '/', routeRef: indexRouteRef, loader: async () => { @@ -68,7 +68,7 @@ const IndexPage = createPageExtension({ }); const Page1 = createPageExtension({ - id: 'page1', + name: 'page1', defaultPath: '/page1', routeRef: page1RouteRef, loader: async () => { @@ -102,7 +102,7 @@ const Page1 = createPageExtension({ }); const ExternalPage = createPageExtension({ - id: 'pageX', + name: 'pageX', defaultPath: '/pageX', routeRef: pageXRouteRef, loader: async () => { diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 089aa84942..370100920f 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,678 @@ # example-app +## 0.2.92-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-import@0.10.6-next.0 + - @backstage/plugin-catalog-graph@0.3.4-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-catalog@1.17.0-next.0 + - @backstage/plugin-adr@0.6.13-next.0 + - @backstage/app-defaults@1.4.8-next.0 + - @backstage/frontend-app-api@0.6.0-next.0 + - @backstage/plugin-scaffolder-react@1.8.0-next.0 + - @backstage/plugin-scaffolder@1.18.0-next.0 + - @backstage/plugin-devtools@0.1.9-next.0 + - @backstage/plugin-user-settings@0.8.1-next.0 + - @backstage/plugin-tech-radar@0.6.13-next.0 + - @backstage/plugin-graphiql@0.3.3-next.0 + - @backstage/plugin-techdocs@1.9.4-next.0 + - @backstage/plugin-search@1.4.6-next.0 + - @backstage/plugin-api-docs@0.10.4-next.0 + - @backstage/cli@0.25.2-next.0 + - @backstage/plugin-stack-overflow@0.1.25-next.0 + - @backstage/plugin-signals@0.0.1-next.0 + - @backstage/plugin-home@0.6.2-next.0 + - @backstage/plugin-cloudbuild@0.4.0-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.5-next.0 + - @backstage/plugin-azure-devops@0.3.12-next.0 + - @backstage/plugin-linguist@0.1.15-next.0 + - @backstage/plugin-airbrake@0.3.30-next.0 + - @backstage/plugin-azure-sites@0.1.19-next.0 + - @backstage/plugin-badges@0.2.54-next.0 + - @backstage/plugin-code-coverage@0.2.23-next.0 + - @backstage/plugin-cost-insights@0.12.19-next.0 + - @backstage/plugin-dynatrace@8.0.4-next.0 + - @backstage/plugin-entity-feedback@0.2.13-next.0 + - @backstage/plugin-explore@0.4.16-next.0 + - @backstage/plugin-github-actions@0.6.11-next.0 + - @backstage/plugin-gocd@0.1.36-next.0 + - @backstage/plugin-jenkins@0.9.5-next.0 + - @backstage/plugin-kafka@0.3.30-next.0 + - @backstage/plugin-kubernetes@0.11.5-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.6-next.0 + - @backstage/plugin-lighthouse@0.4.15-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.5-next.0 + - @backstage/plugin-nomad@0.1.11-next.0 + - @backstage/plugin-octopus-deploy@0.2.12-next.0 + - @backstage/plugin-org@0.6.20-next.0 + - @backstage/plugin-pagerduty@0.7.2-next.0 + - @backstage/plugin-playlist@0.2.4-next.0 + - @backstage/plugin-puppetdb@0.1.13-next.0 + - @backstage/plugin-rollbar@0.4.30-next.0 + - @backstage/plugin-sentry@0.5.15-next.0 + - @backstage/plugin-tech-insights@0.3.22-next.0 + - @backstage/plugin-todo@0.2.34-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration-react@1.1.23 + - @backstage/theme@0.5.0 + - @backstage/plugin-apache-airflow@0.2.19 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7 + - @backstage/plugin-gcalendar@0.3.22 + - @backstage/plugin-gcp-projects@0.3.45 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.11 + - @backstage/plugin-newrelic@0.3.44 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-search-common@1.2.10 + - @backstage/plugin-shortcuts@0.3.18 + - @backstage/plugin-stackstorm@0.1.10 + - @backstage/plugin-techdocs-react@1.1.15 + +## 0.2.91 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-api-docs@0.10.3 + - @backstage/plugin-scaffolder-react@1.7.1 + - @backstage/core-components@0.13.10 + - @backstage/plugin-user-settings@0.8.0 + - @backstage/plugin-azure-sites@0.1.18 + - @backstage/cli@0.25.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-octopus-deploy@0.2.11 + - @backstage/frontend-app-api@0.5.0 + - @backstage/plugin-kubernetes@0.11.4 + - @backstage/plugin-home@0.6.1 + - @backstage/plugin-scaffolder@1.17.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7 + - @backstage/plugin-kubernetes-cluster@0.0.5 + - @backstage/plugin-microsoft-calendar@0.1.11 + - @backstage/plugin-newrelic-dashboard@0.3.4 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-entity-feedback@0.2.12 + - @backstage/plugin-apache-airflow@0.2.19 + - @backstage/plugin-github-actions@0.6.10 + - @backstage/plugin-stack-overflow@0.1.24 + - @backstage/plugin-techdocs-react@1.1.15 + - @backstage/plugin-catalog-graph@0.3.3 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-code-coverage@0.2.22 + - @backstage/plugin-cost-insights@0.12.18 + - @backstage/plugin-tech-insights@0.3.21 + - @backstage/plugin-azure-devops@0.3.11 + - @backstage/plugin-gcp-projects@0.3.45 + - @backstage/plugin-cloudbuild@0.3.29 + - @backstage/plugin-lighthouse@0.4.14 + - @backstage/plugin-stackstorm@0.1.10 + - @backstage/plugin-tech-radar@0.6.12 + - @backstage/plugin-dynatrace@8.0.3 + - @backstage/plugin-gcalendar@0.3.22 + - @backstage/plugin-pagerduty@0.7.1 + - @backstage/plugin-shortcuts@0.3.18 + - @backstage/plugin-airbrake@0.3.29 + - @backstage/plugin-devtools@0.1.8 + - @backstage/plugin-graphiql@0.3.2 + - @backstage/plugin-linguist@0.1.14 + - @backstage/plugin-newrelic@0.3.44 + - @backstage/plugin-playlist@0.2.3 + - @backstage/plugin-puppetdb@0.1.12 + - @backstage/plugin-techdocs@1.9.3 + - @backstage/plugin-catalog@1.16.1 + - @backstage/plugin-explore@0.4.15 + - @backstage/plugin-jenkins@0.9.4 + - @backstage/plugin-rollbar@0.4.29 + - @backstage/plugin-badges@0.2.53 + - @backstage/plugin-search@1.4.5 + - @backstage/plugin-sentry@0.5.14 + - @backstage/plugin-kafka@0.3.29 + - @backstage/plugin-nomad@0.1.10 + - @backstage/plugin-gocd@0.1.35 + - @backstage/plugin-todo@0.2.33 + - @backstage/plugin-adr@0.6.12 + - @backstage/plugin-org@0.6.19 + - @backstage/plugin-catalog-import@0.10.5 + - @backstage/plugin-search-react@1.7.5 + - @backstage/app-defaults@1.4.7 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.91-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-user-settings@0.8.0-next.2 + - @backstage/plugin-octopus-deploy@0.2.11-next.2 + - @backstage/frontend-app-api@0.4.1-next.2 + - @backstage/plugin-home@0.6.1-next.2 + - @backstage/plugin-scaffolder-react@1.7.1-next.2 + - @backstage/plugin-scaffolder@1.17.1-next.2 + - @backstage/plugin-linguist@0.1.14-next.2 + - @backstage/plugin-catalog@1.16.1-next.2 + - @backstage/plugin-catalog-import@0.10.5-next.2 + - @backstage/plugin-graphiql@0.3.2-next.2 + - @backstage/plugin-search@1.4.5-next.2 + - @backstage/plugin-tech-radar@0.6.12-next.2 + - @backstage/plugin-techdocs@1.9.3-next.2 + - @backstage/plugin-adr@0.6.12-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-explore@0.4.15-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + - @backstage/plugin-stack-overflow@0.1.24-next.2 + - @backstage/cli@0.25.1-next.1 + - @backstage/plugin-pagerduty@0.7.1-next.2 + - @backstage/plugin-api-docs@0.10.3-next.2 + - @backstage/plugin-catalog-graph@0.3.3-next.2 + - @backstage/plugin-org@0.6.19-next.2 + - @backstage/plugin-airbrake@0.3.29-next.2 + - @backstage/plugin-azure-devops@0.3.11-next.2 + - @backstage/plugin-azure-sites@0.1.18-next.2 + - @backstage/plugin-badges@0.2.53-next.2 + - @backstage/plugin-cloudbuild@0.3.29-next.2 + - @backstage/plugin-code-coverage@0.2.22-next.2 + - @backstage/plugin-cost-insights@0.12.18-next.2 + - @backstage/plugin-dynatrace@8.0.3-next.2 + - @backstage/plugin-entity-feedback@0.2.12-next.2 + - @backstage/plugin-github-actions@0.6.10-next.2 + - @backstage/plugin-gocd@0.1.35-next.2 + - @backstage/plugin-jenkins@0.9.4-next.2 + - @backstage/plugin-kafka@0.3.29-next.2 + - @backstage/plugin-kubernetes@0.11.4-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.5-next.2 + - @backstage/plugin-lighthouse@0.4.14-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.2 + - @backstage/plugin-nomad@0.1.10-next.2 + - @backstage/plugin-playlist@0.2.3-next.2 + - @backstage/plugin-puppetdb@0.1.12-next.2 + - @backstage/plugin-rollbar@0.4.29-next.2 + - @backstage/plugin-sentry@0.5.14-next.2 + - @backstage/plugin-tech-insights@0.3.21-next.2 + - @backstage/plugin-todo@0.2.33-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-apache-airflow@0.2.19-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.1 + - @backstage/plugin-devtools@0.1.8-next.1 + - @backstage/plugin-gcalendar@0.3.22-next.1 + - @backstage/plugin-gcp-projects@0.3.45-next.1 + - @backstage/plugin-microsoft-calendar@0.1.11-next.1 + - @backstage/plugin-newrelic@0.3.44-next.1 + - @backstage/plugin-shortcuts@0.3.18-next.1 + - @backstage/plugin-stackstorm@0.1.10-next.1 + +## 0.2.91-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-sites@0.1.18-next.1 + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-user-settings@0.8.0-next.1 + - @backstage/plugin-kubernetes@0.11.4-next.1 + - @backstage/core-components@0.13.10-next.1 + - @backstage/app-defaults@1.4.7-next.1 + - @backstage/cli@0.25.1-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/frontend-app-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-adr@0.6.12-next.1 + - @backstage/plugin-airbrake@0.3.29-next.1 + - @backstage/plugin-apache-airflow@0.2.19-next.1 + - @backstage/plugin-api-docs@0.10.3-next.1 + - @backstage/plugin-azure-devops@0.3.11-next.1 + - @backstage/plugin-badges@0.2.53-next.1 + - @backstage/plugin-catalog@1.16.1-next.1 + - @backstage/plugin-catalog-graph@0.3.3-next.1 + - @backstage/plugin-catalog-import@0.10.5-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.1 + - @backstage/plugin-cloudbuild@0.3.29-next.1 + - @backstage/plugin-code-coverage@0.2.22-next.1 + - @backstage/plugin-cost-insights@0.12.18-next.1 + - @backstage/plugin-devtools@0.1.8-next.1 + - @backstage/plugin-dynatrace@8.0.3-next.1 + - @backstage/plugin-entity-feedback@0.2.12-next.1 + - @backstage/plugin-explore@0.4.15-next.1 + - @backstage/plugin-gcalendar@0.3.22-next.1 + - @backstage/plugin-gcp-projects@0.3.45-next.1 + - @backstage/plugin-github-actions@0.6.10-next.1 + - @backstage/plugin-gocd@0.1.35-next.1 + - @backstage/plugin-graphiql@0.3.2-next.1 + - @backstage/plugin-home@0.6.1-next.1 + - @backstage/plugin-jenkins@0.9.4-next.1 + - @backstage/plugin-kafka@0.3.29-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.5-next.1 + - @backstage/plugin-lighthouse@0.4.14-next.1 + - @backstage/plugin-linguist@0.1.14-next.1 + - @backstage/plugin-microsoft-calendar@0.1.11-next.1 + - @backstage/plugin-newrelic@0.3.44-next.1 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.1 + - @backstage/plugin-nomad@0.1.10-next.1 + - @backstage/plugin-octopus-deploy@0.2.11-next.1 + - @backstage/plugin-org@0.6.19-next.1 + - @backstage/plugin-pagerduty@0.7.1-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/plugin-playlist@0.2.3-next.1 + - @backstage/plugin-puppetdb@0.1.12-next.1 + - @backstage/plugin-rollbar@0.4.29-next.1 + - @backstage/plugin-scaffolder@1.17.1-next.1 + - @backstage/plugin-scaffolder-react@1.7.1-next.1 + - @backstage/plugin-search@1.4.5-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/plugin-sentry@0.5.14-next.1 + - @backstage/plugin-shortcuts@0.3.18-next.1 + - @backstage/plugin-stack-overflow@0.1.24-next.1 + - @backstage/plugin-stackstorm@0.1.10-next.1 + - @backstage/plugin-tech-insights@0.3.21-next.1 + - @backstage/plugin-tech-radar@0.6.12-next.1 + - @backstage/plugin-techdocs@1.9.3-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.1 + - @backstage/plugin-techdocs-react@1.1.15-next.1 + - @backstage/plugin-todo@0.2.33-next.1 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.2.91-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.7.1-next.0 + - @backstage/core-components@0.13.10-next.0 + - @backstage/cli@0.25.1-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.1.7-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.5-next.0 + - @backstage/plugin-microsoft-calendar@0.1.11-next.0 + - @backstage/plugin-newrelic-dashboard@0.3.4-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/plugin-entity-feedback@0.2.12-next.0 + - @backstage/plugin-apache-airflow@0.2.19-next.0 + - @backstage/plugin-github-actions@0.6.10-next.0 + - @backstage/plugin-octopus-deploy@0.2.11-next.0 + - @backstage/plugin-stack-overflow@0.1.24-next.0 + - @backstage/plugin-techdocs-react@1.1.15-next.0 + - @backstage/plugin-catalog-graph@0.3.3-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-code-coverage@0.2.22-next.0 + - @backstage/plugin-cost-insights@0.12.18-next.0 + - @backstage/plugin-tech-insights@0.3.21-next.0 + - @backstage/plugin-user-settings@0.7.15-next.0 + - @backstage/plugin-azure-devops@0.3.11-next.0 + - @backstage/plugin-gcp-projects@0.3.45-next.0 + - @backstage/plugin-azure-sites@0.1.18-next.0 + - @backstage/plugin-cloudbuild@0.3.29-next.0 + - @backstage/plugin-kubernetes@0.11.4-next.0 + - @backstage/plugin-lighthouse@0.4.14-next.0 + - @backstage/plugin-scaffolder@1.17.1-next.0 + - @backstage/plugin-stackstorm@0.1.10-next.0 + - @backstage/plugin-tech-radar@0.6.12-next.0 + - @backstage/plugin-dynatrace@8.0.3-next.0 + - @backstage/plugin-gcalendar@0.3.22-next.0 + - @backstage/plugin-pagerduty@0.7.1-next.0 + - @backstage/plugin-shortcuts@0.3.18-next.0 + - @backstage/plugin-airbrake@0.3.29-next.0 + - @backstage/plugin-api-docs@0.10.3-next.0 + - @backstage/plugin-devtools@0.1.8-next.0 + - @backstage/plugin-graphiql@0.3.2-next.0 + - @backstage/plugin-linguist@0.1.14-next.0 + - @backstage/plugin-newrelic@0.3.44-next.0 + - @backstage/plugin-playlist@0.2.3-next.0 + - @backstage/plugin-puppetdb@0.1.12-next.0 + - @backstage/plugin-techdocs@1.9.3-next.0 + - @backstage/plugin-catalog@1.16.1-next.0 + - @backstage/plugin-explore@0.4.15-next.0 + - @backstage/plugin-jenkins@0.9.4-next.0 + - @backstage/plugin-rollbar@0.4.29-next.0 + - @backstage/plugin-badges@0.2.53-next.0 + - @backstage/plugin-search@1.4.5-next.0 + - @backstage/plugin-sentry@0.5.14-next.0 + - @backstage/plugin-kafka@0.3.29-next.0 + - @backstage/plugin-nomad@0.1.10-next.0 + - @backstage/plugin-gocd@0.1.35-next.0 + - @backstage/plugin-home@0.6.1-next.0 + - @backstage/plugin-todo@0.2.33-next.0 + - @backstage/plugin-adr@0.6.12-next.0 + - @backstage/plugin-org@0.6.19-next.0 + - @backstage/app-defaults@1.4.7-next.0 + - @backstage/frontend-app-api@0.4.1-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-catalog-import@0.10.5-next.0 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.2.90 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-app-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/plugin-api-docs@0.10.2 + - @backstage/core-components@0.13.9 + - @backstage/cli@0.25.0 + - @backstage/theme@0.5.0 + - @backstage/plugin-scaffolder@1.17.0 + - @backstage/plugin-home@0.6.0 + - @backstage/plugin-catalog@1.16.0 + - @backstage/plugin-scaffolder-react@1.7.0 + - @backstage/plugin-kubernetes@0.11.3 + - @backstage/plugin-org@0.6.18 + - @backstage/plugin-github-actions@0.6.9 + - @backstage/plugin-azure-devops@0.3.10 + - @backstage/core-app-api@1.11.2 + - @backstage/plugin-lighthouse@0.4.13 + - @backstage/plugin-explore@0.4.14 + - @backstage/plugin-catalog-import@0.10.4 + - @backstage/plugin-user-settings@0.7.14 + - @backstage/plugin-tech-radar@0.6.11 + - @backstage/plugin-graphiql@0.3.1 + - @backstage/plugin-techdocs@1.9.2 + - @backstage/plugin-search@1.4.4 + - @backstage/plugin-search-react@1.7.4 + - @backstage/plugin-stack-overflow@0.1.23 + - @backstage/plugin-adr@0.6.11 + - @backstage/plugin-gcp-projects@0.3.44 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3 + - @backstage/plugin-pagerduty@0.7.0 + - @backstage/app-defaults@1.4.6 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-airbrake@0.3.28 + - @backstage/plugin-apache-airflow@0.2.18 + - @backstage/plugin-azure-sites@0.1.17 + - @backstage/plugin-badges@0.2.52 + - @backstage/plugin-catalog-graph@0.3.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6 + - @backstage/plugin-cloudbuild@0.3.28 + - @backstage/plugin-code-coverage@0.2.21 + - @backstage/plugin-cost-insights@0.12.17 + - @backstage/plugin-devtools@0.1.7 + - @backstage/plugin-dynatrace@8.0.2 + - @backstage/plugin-entity-feedback@0.2.11 + - @backstage/plugin-gcalendar@0.3.21 + - @backstage/plugin-gocd@0.1.34 + - @backstage/plugin-jenkins@0.9.3 + - @backstage/plugin-kafka@0.3.28 + - @backstage/plugin-kubernetes-cluster@0.0.4 + - @backstage/plugin-linguist@0.1.13 + - @backstage/plugin-microsoft-calendar@0.1.10 + - @backstage/plugin-newrelic@0.3.43 + - @backstage/plugin-newrelic-dashboard@0.3.3 + - @backstage/plugin-nomad@0.1.9 + - @backstage/plugin-octopus-deploy@0.2.10 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/plugin-playlist@0.2.2 + - @backstage/plugin-puppetdb@0.1.11 + - @backstage/plugin-rollbar@0.4.28 + - @backstage/plugin-sentry@0.5.13 + - @backstage/plugin-shortcuts@0.3.17 + - @backstage/plugin-stackstorm@0.1.9 + - @backstage/plugin-tech-insights@0.3.20 + - @backstage/plugin-techdocs-react@1.1.14 + - @backstage/plugin-todo@0.2.32 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.2.90-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home@0.6.0-next.3 + - @backstage/plugin-org@0.6.18-next.3 + - @backstage/plugin-azure-devops@0.3.10-next.3 + - @backstage/cli@0.25.0-next.3 + - @backstage/plugin-api-docs@0.10.2-next.4 + - @backstage/plugin-scaffolder-react@1.6.2-next.3 + - @backstage/plugin-scaffolder@1.16.2-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/app-defaults@1.4.6-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-app-api@0.4.0-next.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-adr@0.6.11-next.3 + - @backstage/plugin-airbrake@0.3.28-next.3 + - @backstage/plugin-apache-airflow@0.2.18-next.3 + - @backstage/plugin-azure-sites@0.1.17-next.3 + - @backstage/plugin-badges@0.2.52-next.3 + - @backstage/plugin-catalog@1.16.0-next.4 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-graph@0.3.2-next.3 + - @backstage/plugin-catalog-import@0.10.4-next.4 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.3 + - @backstage/plugin-cloudbuild@0.3.28-next.3 + - @backstage/plugin-code-coverage@0.2.21-next.3 + - @backstage/plugin-cost-insights@0.12.17-next.3 + - @backstage/plugin-devtools@0.1.7-next.3 + - @backstage/plugin-dynatrace@8.0.2-next.3 + - @backstage/plugin-entity-feedback@0.2.11-next.3 + - @backstage/plugin-explore@0.4.14-next.3 + - @backstage/plugin-gcalendar@0.3.21-next.3 + - @backstage/plugin-gcp-projects@0.3.44-next.3 + - @backstage/plugin-github-actions@0.6.9-next.3 + - @backstage/plugin-gocd@0.1.34-next.3 + - @backstage/plugin-graphiql@0.3.1-next.4 + - @backstage/plugin-jenkins@0.9.3-next.3 + - @backstage/plugin-kafka@0.3.28-next.3 + - @backstage/plugin-kubernetes@0.11.3-next.3 + - @backstage/plugin-kubernetes-cluster@0.0.4-next.3 + - @backstage/plugin-lighthouse@0.4.13-next.3 + - @backstage/plugin-linguist@0.1.13-next.3 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.10-next.3 + - @backstage/plugin-newrelic@0.3.43-next.3 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.3 + - @backstage/plugin-nomad@0.1.9-next.3 + - @backstage/plugin-octopus-deploy@0.2.10-next.3 + - @backstage/plugin-pagerduty@0.7.0-next.3 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.3 + - @backstage/plugin-puppetdb@0.1.11-next.3 + - @backstage/plugin-rollbar@0.4.28-next.3 + - @backstage/plugin-search@1.4.4-next.4 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + - @backstage/plugin-sentry@0.5.13-next.3 + - @backstage/plugin-shortcuts@0.3.17-next.3 + - @backstage/plugin-stack-overflow@0.1.23-next.3 + - @backstage/plugin-stackstorm@0.1.9-next.3 + - @backstage/plugin-tech-insights@0.3.20-next.3 + - @backstage/plugin-tech-radar@0.6.11-next.4 + - @backstage/plugin-techdocs@1.9.2-next.4 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.3 + - @backstage/plugin-techdocs-react@1.1.14-next.3 + - @backstage/plugin-todo@0.2.32-next.3 + - @backstage/plugin-user-settings@0.7.14-next.4 + +## 0.2.90-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.4.0-next.2 + - @backstage/cli@0.25.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-kubernetes@0.11.3-next.2 + - @backstage/plugin-lighthouse@0.4.13-next.2 + - @backstage/plugin-catalog-import@0.10.4-next.3 + - @backstage/plugin-user-settings@0.7.14-next.3 + - @backstage/plugin-tech-radar@0.6.11-next.3 + - @backstage/plugin-graphiql@0.3.1-next.3 + - @backstage/plugin-techdocs@1.9.2-next.3 + - @backstage/plugin-catalog@1.16.0-next.3 + - @backstage/plugin-search@1.4.4-next.3 + - @backstage/plugin-home@0.6.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-stack-overflow@0.1.23-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/plugin-explore@0.4.14-next.2 + - @backstage/plugin-adr@0.6.11-next.2 + - @backstage/plugin-scaffolder-react@1.6.2-next.2 + - @backstage/app-defaults@1.4.6-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-airbrake@0.3.28-next.2 + - @backstage/plugin-apache-airflow@0.2.18-next.2 + - @backstage/plugin-api-docs@0.10.2-next.3 + - @backstage/plugin-azure-devops@0.3.10-next.2 + - @backstage/plugin-azure-sites@0.1.17-next.2 + - @backstage/plugin-badges@0.2.52-next.2 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-graph@0.3.2-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.2 + - @backstage/plugin-cloudbuild@0.3.28-next.2 + - @backstage/plugin-code-coverage@0.2.21-next.2 + - @backstage/plugin-cost-insights@0.12.17-next.2 + - @backstage/plugin-devtools@0.1.7-next.2 + - @backstage/plugin-dynatrace@8.0.2-next.2 + - @backstage/plugin-entity-feedback@0.2.11-next.2 + - @backstage/plugin-gcalendar@0.3.21-next.2 + - @backstage/plugin-gcp-projects@0.3.44-next.2 + - @backstage/plugin-github-actions@0.6.9-next.2 + - @backstage/plugin-gocd@0.1.34-next.2 + - @backstage/plugin-jenkins@0.9.3-next.2 + - @backstage/plugin-kafka@0.3.28-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.4-next.2 + - @backstage/plugin-linguist@0.1.13-next.2 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-microsoft-calendar@0.1.10-next.2 + - @backstage/plugin-newrelic@0.3.43-next.2 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.2 + - @backstage/plugin-nomad@0.1.9-next.2 + - @backstage/plugin-octopus-deploy@0.2.10-next.2 + - @backstage/plugin-org@0.6.18-next.2 + - @backstage/plugin-pagerduty@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.2 + - @backstage/plugin-puppetdb@0.1.11-next.2 + - @backstage/plugin-rollbar@0.4.28-next.2 + - @backstage/plugin-scaffolder@1.16.2-next.2 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-sentry@0.5.13-next.2 + - @backstage/plugin-shortcuts@0.3.17-next.2 + - @backstage/plugin-stackstorm@0.1.9-next.2 + - @backstage/plugin-tech-insights@0.3.20-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.2 + - @backstage/plugin-techdocs-react@1.1.14-next.2 + - @backstage/plugin-todo@0.2.32-next.2 + +## 0.2.90-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/cli@0.25.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-catalog@1.16.0-next.2 + - @backstage/plugin-scaffolder-react@1.6.2-next.1 + - @backstage/plugin-home@0.6.0-next.1 + - @backstage/plugin-github-actions@0.6.9-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/plugin-azure-devops@0.3.10-next.1 + - @backstage/plugin-scaffolder@1.16.2-next.1 + - @backstage/plugin-api-docs@0.10.2-next.2 + - @backstage/plugin-gcp-projects@0.3.44-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.3-next.1 + - @backstage/plugin-user-settings@0.7.14-next.2 + - @backstage/plugin-adr@0.6.11-next.1 + - @backstage/plugin-pagerduty@0.7.0-next.1 + - @backstage/plugin-catalog-import@0.10.4-next.2 + - @backstage/plugin-explore@0.4.14-next.1 + - @backstage/plugin-graphiql@0.3.1-next.2 + - @backstage/plugin-search@1.4.4-next.2 + - @backstage/plugin-stack-overflow@0.1.23-next.1 + - @backstage/plugin-tech-radar@0.6.11-next.2 + - @backstage/plugin-techdocs@1.9.2-next.2 + - @backstage/app-defaults@1.4.6-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-airbrake@0.3.28-next.1 + - @backstage/plugin-apache-airflow@0.2.18-next.1 + - @backstage/plugin-azure-sites@0.1.17-next.1 + - @backstage/plugin-badges@0.2.52-next.1 + - @backstage/plugin-catalog-graph@0.3.2-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.1.6-next.1 + - @backstage/plugin-cloudbuild@0.3.28-next.1 + - @backstage/plugin-code-coverage@0.2.21-next.1 + - @backstage/plugin-cost-insights@0.12.17-next.1 + - @backstage/plugin-devtools@0.1.7-next.1 + - @backstage/plugin-dynatrace@8.0.2-next.1 + - @backstage/plugin-entity-feedback@0.2.11-next.1 + - @backstage/plugin-gcalendar@0.3.21-next.1 + - @backstage/plugin-gocd@0.1.34-next.1 + - @backstage/plugin-jenkins@0.9.3-next.1 + - @backstage/plugin-kafka@0.3.28-next.1 + - @backstage/plugin-kubernetes@0.11.3-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.4-next.1 + - @backstage/plugin-lighthouse@0.4.13-next.1 + - @backstage/plugin-linguist@0.1.13-next.1 + - @backstage/plugin-microsoft-calendar@0.1.10-next.1 + - @backstage/plugin-newrelic@0.3.43-next.1 + - @backstage/plugin-newrelic-dashboard@0.3.3-next.1 + - @backstage/plugin-nomad@0.1.9-next.1 + - @backstage/plugin-octopus-deploy@0.2.10-next.1 + - @backstage/plugin-org@0.6.18-next.1 + - @backstage/plugin-playlist@0.2.2-next.1 + - @backstage/plugin-puppetdb@0.1.11-next.1 + - @backstage/plugin-rollbar@0.4.28-next.1 + - @backstage/plugin-sentry@0.5.13-next.1 + - @backstage/plugin-shortcuts@0.3.17-next.1 + - @backstage/plugin-stackstorm@0.1.9-next.1 + - @backstage/plugin-tech-insights@0.3.20-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.1 + - @backstage/plugin-todo@0.2.32-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-linguist-common@0.1.2 + - @backstage/plugin-search-common@1.2.8 + ## 0.2.90-next.1 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 7cf7ae3290..ae03c7aa11 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.90-next.1", + "version": "0.2.92-next.0", "private": true, "backstage": { "role": "frontend" @@ -35,7 +35,6 @@ "@backstage/plugin-catalog-import": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-catalog-unprocessed-entities": "workspace:^", - "@backstage/plugin-circleci": "workspace:^", "@backstage/plugin-cloudbuild": "workspace:^", "@backstage/plugin-code-coverage": "workspace:^", "@backstage/plugin-cost-insights": "workspace:^", @@ -74,6 +73,7 @@ "@backstage/plugin-search-react": "workspace:^", "@backstage/plugin-sentry": "workspace:^", "@backstage/plugin-shortcuts": "workspace:^", + "@backstage/plugin-signals": "workspace:^", "@backstage/plugin-stack-overflow": "workspace:^", "@backstage/plugin-stackstorm": "workspace:^", "@backstage/plugin-tech-insights": "workspace:^", @@ -84,6 +84,7 @@ "@backstage/plugin-todo": "workspace:^", "@backstage/plugin-user-settings": "workspace:^", "@backstage/theme": "workspace:^", + "@circleci/backstage-plugin": "^0.1.1", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", @@ -102,7 +103,7 @@ "react-use": "^17.2.4", "vite": "^4.4.9", "vite-plugin-html": "^3.2.0", - "vite-plugin-node-polyfills": "^0.16.0", + "vite-plugin-node-polyfills": "^0.19.0", "zen-observable": "^0.10.0" }, "devDependencies": { diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx index 8c7cefeb2c..919a60e53d 100644 --- a/packages/app/src/App.test.tsx +++ b/packages/app/src/App.test.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { renderWithEffects } from '@backstage/test-utils'; +import { render, waitFor } from '@testing-library/react'; import App from './App'; describe('App', () => { @@ -42,7 +42,10 @@ describe('App', () => { ] as any, }; - const rendered = await renderWithEffects(); - expect(rendered.baseElement).toBeInTheDocument(); + const rendered = render(); + + await waitFor(() => { + expect(rendered.baseElement).toBeInTheDocument(); + }); }); }); diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index c2296d01e7..d2dde95f68 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -31,7 +31,6 @@ import { AppRouter, ConfigReader, defaultConfigLoader, - FeatureFlagged, FlatRoutes, } from '@backstage/core-app-api'; import { @@ -64,7 +63,6 @@ import { GcpProjectsPage } from '@backstage/plugin-gcp-projects'; import { HomepageCompositionRoot, VisitListener } from '@backstage/plugin-home'; import { LighthousePage } from '@backstage/plugin-lighthouse'; import { NewRelicPage } from '@backstage/plugin-newrelic'; -import { LegacyScaffolderPage } from '@backstage/plugin-scaffolder/alpha'; import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; import { ScaffolderFieldExtensions, @@ -96,10 +94,7 @@ import { apis } from './apis'; import { entityPage } from './components/catalog/EntityPage'; import { homePage } from './components/home/HomePage'; import { Root } from './components/Root'; -import { - DelayingComponentFieldExtension, - LowerCaseValuePickerFieldExtension, -} from './components/scaffolder/customScaffolderExtensions'; +import { DelayingComponentFieldExtension } from './components/scaffolder/customScaffolderExtensions'; import { defaultPreviewTemplate } from './components/scaffolder/defaultPreviewTemplate'; import { searchPage } from './components/search/SearchPage'; import { providers } from './identityProviders'; @@ -236,53 +231,28 @@ const routes = ( - - - entity?.metadata?.tags?.includes('recommended') ?? false, - }, - ]} - /> - } - > - - - - - - - - - - - entity?.metadata?.tags?.includes('recommended') ?? false, - }, - ]} - /> - } - > - - - - - - - - + + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + ]} + /> + } + > + + + + + + + } /> - + + + + diff --git a/packages/app/src/index-public-experimental.tsx b/packages/app/src/index-public-experimental.tsx new file mode 100644 index 0000000000..9e5f89b4c2 --- /dev/null +++ b/packages/app/src/index-public-experimental.tsx @@ -0,0 +1,82 @@ +/* + * 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 { createApp } from '@backstage/app-defaults'; +import { AppRouter } from '@backstage/core-app-api'; +import { + AlertDisplay, + OAuthRequestDialog, + SignInPage, +} from '@backstage/core-components'; +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { providers } from '../src/identityProviders'; +import { + configApiRef, + createApiFactory, + discoveryApiRef, + useApi, +} from '@backstage/core-plugin-api'; +import { AuthProxyDiscoveryApi } from '../src/AuthProxyDiscoveryApi'; + +// TODO(Rugvip): make this available via some util, or maybe Utility API? +function readBasePath(configApi: typeof configApiRef.T) { + let { pathname } = new URL( + configApi.getOptionalString('app.baseUrl') ?? '/', + 'http://sample.dev', // baseUrl can be specified as just a path + ); + pathname = pathname.replace(/\/*$/, ''); + return pathname; +} + +const app = createApp({ + apis: [ + createApiFactory({ + api: discoveryApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => AuthProxyDiscoveryApi.fromConfig(configApi), + }), + ], + components: { + SignInPage: props => { + return ( + + ); + }, + }, +}); + +function RedirectToRoot() { + window.location.pathname = readBasePath(useApi(configApiRef)); + return
; +} + +const App = app.createRoot( + <> + + + + + + , +); + +ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index 36f7a86acc..ddd12bea2c 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -19,3 +19,4 @@ export { badgesPlugin } from '@backstage/plugin-badges'; export { shortcutsPlugin } from '@backstage/plugin-shortcuts'; export { homePlugin } from '@backstage/plugin-home'; +export { signalsPlugin } from '@backstage/plugin-signals'; diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 1f19f67eea..6096ba5451 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,161 @@ # @backstage/backend-app-api +## 0.5.11-next.0 + +### Patch Changes + +- e0c18ef: Include the extension point ID and the module ID in the backend init error message. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/cli-node@0.2.2 + - @backstage/config-loader@1.6.1 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.10 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config-loader@1.6.1 + - @backstage/cli-node@0.2.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.10-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/cli-node@0.2.2-next.0 + - @backstage/config-loader@1.6.1-next.0 + +## 0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.1-next.0 + - @backstage/cli-node@0.2.2-next.0 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.1 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## 0.5.9 + +### Patch Changes + +- 1da5f43: Ensure redaction of secrets that have accidental extra whitespace around them +- 9f8f266: Add redacting for secrets in stack traces of logs +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config-loader@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/cli-node@0.2.1 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## 0.5.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.0-next.0 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## 0.5.9-next.1 + +### Patch Changes + +- 1da5f434f3: Ensure redaction of secrets that have accidental extra whitespace around them +- 9f8f266ff4: Add redacting for secrets in stack traces of logs +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.5.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-node@0.7.19-next.1 + ## 0.5.9-next.0 ### Patch Changes diff --git a/packages/backend-app-api/README.md b/packages/backend-app-api/README.md index e5b80abeaf..3fd6170b4f 100644 --- a/packages/backend-app-api/README.md +++ b/packages/backend-app-api/README.md @@ -1,8 +1,6 @@ # @backstage/backend-app-api -**This package is EXPERIMENTAL, we recommend against using it for production deployments** - -This package provides the core API used by Backstage backend apps. +This package provides the framework API used by Backstage backend apps. ## Installation diff --git a/packages/backend-app-api/alpha-api-report.md b/packages/backend-app-api/api-report-alpha.md similarity index 100% rename from packages/backend-app-api/alpha-api-report.md rename to packages/backend-app-api/api-report-alpha.md diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 51e052d8a3..b3b338031e 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-app-api", "description": "Core API used by Backstage backend apps", - "version": "0.5.9-next.0", + "version": "0.5.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-app-api/src/logging/WinstonLogger.test.ts b/packages/backend-app-api/src/logging/WinstonLogger.test.ts index 5d8cfc9c90..c7173b1cbe 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.test.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.test.ts @@ -14,25 +14,37 @@ * limitations under the License. */ +import { TransformableInfo } from 'logform'; import { WinstonLogger } from './WinstonLogger'; -function msg(message: string) { - return { message, level: 'info' }; +function msg(info: TransformableInfo): TransformableInfo { + return { message: info.message, level: info.level, stack: info.stack }; } describe('WinstonLogger', () => { it('redacter should redact and escape regex', () => { const redacter = WinstonLogger.redacter(); - expect(redacter.format.transform(msg('hello (world)'))).toEqual( - msg('hello (world)'), + const log = { + level: 'error', + message: 'hello (world)', + stack: 'hello (world) from this file', + }; + expect(redacter.format.transform(msg(log))).toEqual(msg(log)); + redacter.add(['hello\n']); + expect(redacter.format.transform(msg(log))).toEqual( + msg({ + ...log, + message: '[REDACTED] (world)', + stack: '[REDACTED] (world) from this file', + }), ); - redacter.add(['hello']); - expect(redacter.format.transform(msg('hello (world)'))).toEqual( - msg('[REDACTED] (world)'), - ); - redacter.add(['(world)']); - expect(redacter.format.transform(msg('hello (world)'))).toEqual( - msg('[REDACTED] [REDACTED]'), + redacter.add(['(world']); + expect(redacter.format.transform(msg(log))).toEqual( + msg({ + ...log, + message: '[REDACTED] [REDACTED])', + stack: '[REDACTED] [REDACTED]) from this file', + }), ); }); }); diff --git a/packages/backend-app-api/src/logging/WinstonLogger.ts b/packages/backend-app-api/src/logging/WinstonLogger.ts index 06a189508b..7dd0ed80b0 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.ts @@ -82,11 +82,18 @@ export class WinstonLogger implements RootLoggerService { if (redactionPattern && typeof info.message === 'string') { info.message = info.message.replace(redactionPattern, '[REDACTED]'); } + if (redactionPattern && typeof info.stack === 'string') { + info.stack = info.stack.replace(redactionPattern, '[REDACTED]'); + } return info; })(), add(newRedactions) { let added = 0; - for (const redaction of newRedactions) { + for (const redactionToTrim of newRedactions) { + // Trimming the string ensures that we don't accdentally get extra + // newlines or other whitespace interfering with the redaction; this + // can happen for example when using string literals in yaml + const redaction = redactionToTrim.trim(); // Exclude secrets that are empty or just one character in length. These // typically mean that you are running local dev or tests, or using the // --lax flag which sets things to just 'x'. diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index 791b9b1c3a..c025d19fa7 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -102,7 +102,7 @@ describe('BackendInitializer', () => { init.add( createBackendModule({ pluginId: 'test', - moduleId: 'modA', + moduleId: 'mod-a', register(reg) { reg.registerInit({ deps: { extension: extensionPoint }, @@ -118,7 +118,7 @@ describe('BackendInitializer', () => { init.add( createBackendModule({ pluginId: 'test', - moduleId: 'modB', + moduleId: 'mod-b', register(reg) { const values = ['b']; reg.registerExtensionPoint(extensionPoint, { values }); @@ -135,7 +135,7 @@ describe('BackendInitializer', () => { init.add( createBackendModule({ pluginId: 'test', - moduleId: 'modC', + moduleId: 'mod-c', register(reg) { reg.registerInit({ deps: { extension: extensionPoint }, @@ -265,7 +265,7 @@ describe('BackendInitializer', () => { init.add( createBackendModule({ pluginId: 'test', - moduleId: 'modA', + moduleId: 'mod-a', register(reg) { reg.registerExtensionPoint(extA, 'a'); reg.registerInit({ @@ -278,7 +278,7 @@ describe('BackendInitializer', () => { init.add( createBackendModule({ pluginId: 'test', - moduleId: 'modB', + moduleId: 'mod-b', register(reg) { reg.registerExtensionPoint(extB, 'b'); reg.registerInit({ @@ -289,7 +289,7 @@ describe('BackendInitializer', () => { })(), ); await expect(init.start()).rejects.toThrow( - "Circular dependency detected for modules of plugin 'test', 'modA' -> 'modB' -> 'modA'", + "Circular dependency detected for modules of plugin 'test', 'mod-a' -> 'mod-b' -> 'mod-a'", ); }); @@ -298,7 +298,7 @@ describe('BackendInitializer', () => { const extA = createExtensionPoint({ id: 'a' }); init.add( createBackendPlugin({ - pluginId: 'testA', + pluginId: 'test-a', register(reg) { reg.registerExtensionPoint(extA, 'a'); reg.registerInit({ @@ -310,7 +310,7 @@ describe('BackendInitializer', () => { ); init.add( createBackendModule({ - pluginId: 'testB', + pluginId: 'test-b', moduleId: 'mod', register(reg) { reg.registerInit({ @@ -321,7 +321,7 @@ describe('BackendInitializer', () => { })(), ); await expect(init.start()).rejects.toThrow( - "Extension point registered for plugin 'testA' may not be used by module for plugin 'testB'", + "Illegal dependency: Module 'mod' for plugin 'test-b' attempted to depend on extension point 'a' for plugin 'test-a'. Extension points can only be used within their plugin's scope.", ); }); }); diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index 4969f82176..2da3a5a226 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -55,6 +55,7 @@ export class BackendInitializer { async #getInitDeps( deps: { [name: string]: ServiceOrExtensionPoint }, pluginId: string, + moduleId?: string, ) { const result = new Map(); const missingRefs = new Set(); @@ -64,7 +65,7 @@ export class BackendInitializer { if (ep) { if (ep.pluginId !== pluginId) { throw new Error( - `Extension point registered for plugin '${ep.pluginId}' may not be used by module for plugin '${pluginId}'`, + `Illegal dependency: Module '${moduleId}' for plugin '${pluginId}' attempted to depend on extension point '${ref.id}' for plugin '${ep.pluginId}'. Extension points can only be used within their plugin's scope.`, ); } result.set(name, ep.impl); @@ -260,6 +261,7 @@ export class BackendInitializer { const moduleDeps = await this.#getInitDeps( moduleInit.init.deps, pluginId, + moduleId, ); await moduleInit.init.func(moduleDeps).catch(error => { throw new ForwardedError( diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 16a4bcafb4..ca05772aa5 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,176 @@ # @backstage/backend-common +## 0.21.0-next.0 + +### Minor Changes + +- e85aa98: drop databases after unit tests if the database instance is not running in docker + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.11-next.0 + - @backstage/config-loader@1.6.1 + - @backstage/backend-dev-utils@0.1.3 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## 0.20.1 + +### Patch Changes + +- 3b24eae: Adding support for removing file from git index +- 454d17c: Do not call fetch directly but rather use `fetchResponse` facility +- b6b15b2: Use sha256 instead of md5 for hash key calculation in caches + + This can have a side effect of invalidating caches (when cache key was >250 characters) + This improves compliance with FIPS nodejs + +- Updated dependencies + - @backstage/config-loader@1.6.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-dev-utils@0.1.3 + - @backstage/backend-app-api@0.5.10 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## 0.20.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-dev-utils@0.1.3-next.0 + - @backstage/backend-app-api@0.5.10-next.2 + - @backstage/config-loader@1.6.1-next.0 + +## 0.20.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.1-next.0 + - @backstage/backend-app-api@0.5.10-next.1 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/config@1.1.1 + - @backstage/backend-dev-utils@0.1.2 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.20.1-next.0 + +### Patch Changes + +- b6b15b2: Use sha256 instead of md5 for hash key calculation in caches + + This can have a side effect of invalidating caches (when cache key was >250 characters) + This improves compliance with FIPS nodejs + +- Updated dependencies + - @backstage/backend-app-api@0.5.10-next.0 + - @backstage/backend-dev-utils@0.1.2 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## 0.20.0 + +### Minor Changes + +- 870db76: Implemented `readTree` for Gitea provider to support TechDocs functionality + +### Patch Changes + +- 7f04128: Allow a default cache TTL to be set through the app config +- 1ad8906: Use `Readable.from` to fix some of the stream issues +- d86a007: Fixed the AwsS3UrlReader host regex and host to allow the S3 reading for CN AWS domain +- bc67498: Updated dependency `archiver` to `^6.0.0`. + Updated dependency `@types/archiver` to `^6.0.0`. +- 706fc3a: Updated dependency `@kubernetes/client-node` to `0.20.0`. +- 2666675: Updated dependency `@google-cloud/storage` to `^7.0.0`. +- d15d483: Add command `--runAsDefaultUser` for `@techdocs/cli generate` to bypass running the docker builds as host user for macOS and Linux. +- d1e00aa: Expose an `onAuth` handler for `git` actions to provide custom credentials +- Updated dependencies + - @backstage/config-loader@1.6.0 + - @backstage/backend-app-api@0.5.9 + - @backstage/integration@1.8.0 + - @backstage/backend-dev-utils@0.1.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## 0.20.0-next.3 + +### Patch Changes + +- d86a007: Fixed the AwsS3UrlReader host regex and host to allow the S3 reading for CN AWS domain +- Updated dependencies + - @backstage/backend-app-api@0.5.9-next.3 + - @backstage/backend-dev-utils@0.1.2 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0-next.0 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## 0.20.0-next.2 + +### Patch Changes + +- bc67498: Updated dependency `archiver` to `^6.0.0`. + Updated dependency `@types/archiver` to `^6.0.0`. +- Updated dependencies + - @backstage/config-loader@1.6.0-next.0 + - @backstage/backend-app-api@0.5.9-next.2 + - @backstage/backend-dev-utils@0.1.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## 0.20.0-next.1 + +### Patch Changes + +- 2666675457: Updated dependency `@google-cloud/storage` to `^7.0.0`. +- Updated dependencies + - @backstage/backend-app-api@0.5.9-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-dev-utils@0.1.2 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.5.3 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + ## 0.20.0-next.0 ### Minor Changes diff --git a/packages/backend-common/alpha-api-report.md b/packages/backend-common/api-report-alpha.md similarity index 100% rename from packages/backend-common/alpha-api-report.md rename to packages/backend-common/api-report-alpha.md diff --git a/packages/backend-common/testUtils-api-report.md b/packages/backend-common/api-report-testUtils.md similarity index 100% rename from packages/backend-common/testUtils-api-report.md rename to packages/backend-common/api-report-testUtils.md diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index e7d89c2aa5..e4a1ecfdf1 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -276,6 +276,12 @@ export class DockerContainerRunner implements ContainerRunner { runContainer(options: RunContainerOptions): Promise; } +// @public +export function dropDatabase( + dbConfig: Config, + ...databases: Array +): Promise; + // @public export function ensureDatabaseExists( dbConfig: Config, @@ -417,6 +423,7 @@ export class Git { force?: boolean; }): Promise; readCommit(options: { dir: string; sha: string }): Promise; + remove(options: { dir: string; filepath: string }): Promise; resolveRef(options: { dir: string; ref: string }): Promise; } diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index dd52df0b40..4df64738ce 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.20.0-next.0", + "version": "0.21.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { @@ -64,7 +64,7 @@ "@backstage/integration": "workspace:^", "@backstage/integration-aws-node": "workspace:^", "@backstage/types": "workspace:^", - "@google-cloud/storage": "^6.0.0", + "@google-cloud/storage": "^7.0.0", "@keyv/memcache": "^1.3.5", "@keyv/redis": "^2.5.3", "@kubernetes/client-node": "0.20.0", @@ -75,7 +75,7 @@ "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", "@types/webpack-env": "^1.15.2", - "archiver": "^5.0.2", + "archiver": "^6.0.0", "base64-stream": "^1.0.0", "compression": "^1.7.4", "concat-stream": "^2.0.0", @@ -118,7 +118,7 @@ "@aws-sdk/util-stream-node": "^3.350.0", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/archiver": "^5.1.0", + "@types/archiver": "^6.0.0", "@types/base64-stream": "^1.0.2", "@types/compression": "^1.7.0", "@types/concat-stream": "^2.0.0", diff --git a/packages/backend-common/src/cache/CacheClient.ts b/packages/backend-common/src/cache/CacheClient.ts index 24ee7ac455..aba62558d4 100644 --- a/packages/backend-common/src/cache/CacheClient.ts +++ b/packages/backend-common/src/cache/CacheClient.ts @@ -88,6 +88,6 @@ export class DefaultCacheClient implements CacheService { return wellFormedKey; } - return createHash('md5').update(candidateKey).digest('base64'); + return createHash('sha256').update(candidateKey).digest('base64'); } } diff --git a/packages/backend-common/src/database/connection.test.ts b/packages/backend-common/src/database/connection.test.ts index caf2fccc30..3028d46cae 100644 --- a/packages/backend-common/src/database/connection.test.ts +++ b/packages/backend-common/src/database/connection.test.ts @@ -19,10 +19,11 @@ import { createDatabaseClient, createNameOverride, createSchemaOverride, + dropDatabase, ensureSchemaExists, parseConnectionString, } from './connection'; -import { pgConnector } from './connectors'; +import { mysqlConnector, pgConnector } from './connectors'; const mocked = (f: Function) => f as jest.Mock; @@ -30,8 +31,13 @@ jest.mock('./connectors', () => { const connectors = jest.requireActual('./connectors'); return { ...connectors, + mysqlConnector: { + ...connectors.mysqlConnector, + dropDatabase: jest.fn(), + }, pgConnector: { ...connectors.pgConnector, + dropDatabase: jest.fn(), ensureSchemaExists: jest.fn(), }, }; @@ -229,4 +235,74 @@ describe('database connection', () => { ).resolves.toBeUndefined(); }); }); + + describe('dropDatabase', () => { + it('returns successfully with pg client', async () => { + await dropDatabase( + new ConfigReader({ + client: 'pg', + schema: 'catalog', + connection: 'postgresql://testuser:testpass@acme:5432/userdbname', + }), + 'backstage_plugin_foobar', + ); + + const mockCalls = mocked( + pgConnector.dropDatabase as Function, + ).mock.calls.splice(-1); + const [baseConfig, databaseName] = mockCalls[0]; + + expect(baseConfig.get()).toMatchObject({ + client: 'pg', + connection: 'postgresql://testuser:testpass@acme:5432/userdbname', + }); + + expect(databaseName).toEqual('backstage_plugin_foobar'); + }); + + it('returns successfully with mysql client', async () => { + await dropDatabase( + new ConfigReader({ + client: 'mysql2', + connection: { + host: '127.0.0.1', + user: 'foo', + password: 'bar', + database: 'dbname', + }, + }), + 'backstage_plugin_foobar', + ); + + const mockCalls = mocked( + mysqlConnector.dropDatabase as Function, + ).mock.calls.splice(-1); + const [baseConfig, databaseName] = mockCalls[0]; + + expect(baseConfig.get()).toMatchObject({ + client: 'mysql2', + connection: { + host: '127.0.0.1', + user: 'foo', + password: 'bar', + database: 'dbname', + }, + }); + + expect(databaseName).toEqual('backstage_plugin_foobar'); + }); + + it('does nothing in other database drivers', () => { + return expect( + dropDatabase( + new ConfigReader({ + client: 'better-sqlite3', + schema: 'catalog', + connection: ':memory:', + }), + 'catalog', + ), + ).resolves.toBeUndefined(); + }); + }); }); diff --git a/packages/backend-common/src/database/connection.ts b/packages/backend-common/src/database/connection.ts index e369323e04..c0e031516f 100644 --- a/packages/backend-common/src/database/connection.ts +++ b/packages/backend-common/src/database/connection.ts @@ -95,6 +95,22 @@ export async function ensureDatabaseExists( ); } +/** + * Drops the given databases. + * + * @public + */ +export async function dropDatabase( + dbConfig: Config, + ...databases: Array +): Promise { + const client: DatabaseClient = dbConfig.getString('client'); + + return await ddlLimiter(() => + ConnectorMapping[client]?.dropDatabase?.(dbConfig, ...databases), + ); +} + /** * Ensures that the given schemas all exist, creating them if they do not. * diff --git a/packages/backend-common/src/database/connectors/mysql.ts b/packages/backend-common/src/database/connectors/mysql.ts index 16610dd920..a4bac3f7e1 100644 --- a/packages/backend-common/src/database/connectors/mysql.ts +++ b/packages/backend-common/src/database/connectors/mysql.ts @@ -183,6 +183,40 @@ export async function ensureMysqlDatabaseExists( } } +/** + * Drops the given mysql databases. + * + * @param dbConfig - The database config + * @param databases - The names of the databases to create + */ +export async function dropMysqlDatabase( + dbConfig: Config, + ...databases: Array +) { + const admin = createMysqlDatabaseClient(dbConfig, { + connection: { + database: null as unknown as string, + }, + pool: { + min: 0, + acquireTimeoutMillis: 10000, + }, + }); + + try { + const dropDatabase = async (database: string) => { + await admin.raw(`DROP DATABASE ??`, [database]); + }; + await Promise.all( + databases.map(async database => { + return await dropDatabase(database); + }), + ); + } finally { + await admin.destroy(); + } +} + /** * MySQL database connector. * @@ -193,4 +227,5 @@ export const mysqlConnector: DatabaseConnector = Object.freeze({ ensureDatabaseExists: ensureMysqlDatabaseExists, createNameOverride: defaultNameOverride, parseConnectionString: parseMysqlConnectionString, + dropDatabase: dropMysqlDatabase, }); diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts index f132488e29..dc9b19f6ad 100644 --- a/packages/backend-common/src/database/connectors/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -199,6 +199,24 @@ export async function ensurePgSchemaExists( } } +/** + * Drops the Postgres databases. + * + * @param dbConfig - The database config + * @param databases - The name of the databases to drop + */ +export async function dropPgDatabase( + dbConfig: Config, + ...databases: Array +) { + const admin = createPgDatabaseClient(dbConfig); + await Promise.all( + databases.map(async database => { + await admin.raw(`DROP DATABASE ??`, [database]); + }), + ); +} + /** * PostgreSQL database connector. * @@ -211,4 +229,5 @@ export const pgConnector: DatabaseConnector = Object.freeze({ createNameOverride: defaultNameOverride, createSchemaOverride: defaultSchemaOverride, parseConnectionString: parsePgConnectionString, + dropDatabase: dropPgDatabase, }); diff --git a/packages/backend-common/src/database/index.ts b/packages/backend-common/src/database/index.ts index 0dde1ec239..330429f0ef 100644 --- a/packages/backend-common/src/database/index.ts +++ b/packages/backend-common/src/database/index.ts @@ -20,7 +20,11 @@ export * from './DatabaseManager'; * Undocumented API surface from connection is being reduced for future deprecation. * Avoid exporting additional symbols. */ -export { createDatabaseClient, ensureDatabaseExists } from './connection'; +export { + createDatabaseClient, + ensureDatabaseExists, + dropDatabase, +} from './connection'; export type { PluginDatabaseManager } from './types'; export { isDatabaseConflictError } from './util'; diff --git a/packages/backend-common/src/database/types.ts b/packages/backend-common/src/database/types.ts index 621bcc676b..3e9832aec5 100644 --- a/packages/backend-common/src/database/types.ts +++ b/packages/backend-common/src/database/types.ts @@ -79,4 +79,6 @@ export interface DatabaseConnector { dbConfig: Config, ...schemas: Array ): Promise; + + dropDatabase?(dbConfig: Config, ...databases: Array): Promise; } diff --git a/packages/backend-common/src/reading/AwsS3UrlReader.test.ts b/packages/backend-common/src/reading/AwsS3UrlReader.test.ts index b37a052c1d..e3247f190b 100644 --- a/packages/backend-common/src/reading/AwsS3UrlReader.test.ts +++ b/packages/backend-common/src/reading/AwsS3UrlReader.test.ts @@ -53,6 +53,39 @@ describe('parseUrl', () => { bucket: 'my.bucket-3', region: 'us-east-1', }); + expect( + parseUrl('https://s3.amazonaws.com.cn/my.bucket-3/a/puppy.jpg', { + host: 'amazonaws.com', + }), + ).toEqual({ + path: 'a/puppy.jpg', + bucket: 'my.bucket-3', + region: 'us-east-1', + }); + expect( + parseUrl( + 'https://ec-backstage-staging.s3.cn-north-1.amazonaws.com.cn/payments-prod-oas30.json', + { + host: 'amazonaws.com', + }, + ), + ).toEqual({ + path: 'payments-prod-oas30.json', + bucket: 'ec-backstage-staging', + region: 'cn-north-1', + }); + expect( + parseUrl( + 'https://ec-backstage-staging.s3.cn-north-1.amazonaws.com.cn/payments-prod-oas30.json', + { + host: 'amazonaws.com.cn', + }, + ), + ).toEqual({ + path: 'payments-prod-oas30.json', + bucket: 'ec-backstage-staging', + region: 'cn-north-1', + }); expect( parseUrl('https://s3.us-west-2.amazonaws.com/my.bucket-3/a/puppy.jpg', { host: 'amazonaws.com', diff --git a/packages/backend-common/src/reading/AwsS3UrlReader.ts b/packages/backend-common/src/reading/AwsS3UrlReader.ts index 4d4c28071f..23ef0f7b27 100644 --- a/packages/backend-common/src/reading/AwsS3UrlReader.ts +++ b/packages/backend-common/src/reading/AwsS3UrlReader.ts @@ -69,9 +69,9 @@ export function parseUrl( const host = parsedUrl.host; // Treat Amazon hosted separately because it has special region logic - if (config.host === 'amazonaws.com') { + if (config.host === 'amazonaws.com' || config.host === 'amazonaws.com.cn') { const match = host.match( - /^(?:([a-z0-9.-]+)\.)?s3(?:[.-]([a-z0-9-]+))?\.amazonaws\.com$/, + /^(?:([a-z0-9.-]+)\.)?s3(?:[.-]([a-z0-9-]+))?\.amazonaws\.com(\.cn)?$/, ); if (!match) { throw new Error(`Invalid AWS S3 URL ${url}`); diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 764431386d..67f6a975c1 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -105,58 +105,28 @@ export class GithubUrlReader implements UrlReader { credentials, ); - let response: Response; - try { - response = await fetch(ghUrl, { - headers: { - ...credentials?.headers, - ...(options?.etag && { 'If-None-Match': options.etag }), - ...(options?.lastModifiedAfter && { - 'If-Modified-Since': options.lastModifiedAfter.toUTCString(), - }), - Accept: 'application/vnd.github.v3.raw', - }, - // TODO(freben): The signal cast is there because pre-3.x versions of - // node-fetch have a very slightly deviating AbortSignal type signature. - // The difference does not affect us in practice however. The cast can - // be removed after we support ESM for CLI dependencies and migrate to - // version 3 of node-fetch. - // https://github.com/backstage/backstage/issues/8242 - signal: options?.signal as any, - }); - } catch (e) { - throw new Error(`Unable to read ${url}, ${e}`); - } + const response = await this.fetchResponse(ghUrl, { + headers: { + ...credentials?.headers, + ...(options?.etag && { 'If-None-Match': options.etag }), + ...(options?.lastModifiedAfter && { + 'If-Modified-Since': options.lastModifiedAfter.toUTCString(), + }), + Accept: 'application/vnd.github.v3.raw', + }, + // TODO(freben): The signal cast is there because pre-3.x versions of + // node-fetch have a very slightly deviating AbortSignal type signature. + // The difference does not affect us in practice however. The cast can + // be removed after we support ESM for CLI dependencies and migrate to + // version 3 of node-fetch. + // https://github.com/backstage/backstage/issues/8242 + signal: options?.signal as any, + }); - if (response.status === 304) { - throw new NotModifiedError(); - } - - if (response.ok) { - return ReadUrlResponseFactory.fromNodeJSReadable(response.body, { - etag: response.headers.get('ETag') ?? undefined, - lastModifiedAt: parseLastModified( - response.headers.get('Last-Modified'), - ), - }); - } - - let message = `${url} could not be read as ${ghUrl}, ${response.status} ${response.statusText}`; - if (response.status === 404) { - throw new NotFoundError(message); - } - - // GitHub returns a 403 response with a couple of headers indicating rate - // limit status. See more in the GitHub docs: - // https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting - if ( - response.status === 403 && - response.headers.get('X-RateLimit-Remaining') === '0' - ) { - message += ' (rate limit exceeded)'; - } - - throw new Error(message); + return ReadUrlResponseFactory.fromNodeJSReadable(response.body, { + etag: response.headers.get('ETag') ?? undefined, + lastModifiedAt: parseLastModified(response.headers.get('Last-Modified')), + }); } async readTree( @@ -350,10 +320,26 @@ export class GithubUrlReader implements UrlReader { const response = await fetch(urlAsString, init); if (!response.ok) { - const message = `Request failed for ${urlAsString}, ${response.status} ${response.statusText}`; + let message = `Request failed for ${urlAsString}, ${response.status} ${response.statusText}`; + + if (response.status === 304) { + throw new NotModifiedError(); + } + if (response.status === 404) { throw new NotFoundError(message); } + + // GitHub returns a 403 response with a couple of headers indicating rate + // limit status. See more in the GitHub docs: + // https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting + if ( + response.status === 403 && + response.headers.get('X-RateLimit-Remaining') === '0' + ) { + message += ' (rate limit exceeded)'; + } + throw new Error(message); } diff --git a/packages/backend-common/src/scm/git.test.ts b/packages/backend-common/src/scm/git.test.ts index 7533c6e2f3..1c07dfb917 100644 --- a/packages/backend-common/src/scm/git.test.ts +++ b/packages/backend-common/src/scm/git.test.ts @@ -63,6 +63,22 @@ describe('Git', () => { }); }); + describe('remove', () => { + it('should call isomorphic-git remove with the correct arguments', async () => { + const git = Git.fromAuth({}); + const dir = 'mockdirectory'; + const filepath = 'mockfile/path'; + + await git.remove({ dir, filepath }); + + expect(isomorphic.remove).toHaveBeenCalledWith({ + fs, + dir, + filepath, + }); + }); + }); + describe('deleteRemote', () => { it('should call isomorphic-git with the correct arguments', async () => { const git = Git.fromAuth({}); diff --git a/packages/backend-common/src/scm/git.ts b/packages/backend-common/src/scm/git.ts index 364e1bacd5..e7bad693fe 100644 --- a/packages/backend-common/src/scm/git.ts +++ b/packages/backend-common/src/scm/git.ts @@ -300,6 +300,15 @@ export class Git { return git.readCommit({ fs, dir, oid: sha }); } + /** https://isomorphic-git.org/docs/en/remove */ + async remove(options: { dir: string; filepath: string }): Promise { + const { dir, filepath } = options; + this.config.logger?.info( + `Removing file from git index {dir=${dir},filepath=${filepath}}`, + ); + return git.remove({ fs, dir, filepath }); + } + /** https://isomorphic-git.org/docs/en/resolveRef */ async resolveRef(options: { dir: string; ref: string }): Promise { const { dir, ref } = options; diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index 7354e39b9d..1b50491f54 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,88 @@ # @backstage/backend-defaults +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-app-api@0.5.11-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.2.9 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-app-api@0.5.10 + +## 0.2.9-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-app-api@0.5.10-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.10-next.1 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-app-api@0.5.10-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + +## 0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-app-api@0.5.9 + - @backstage/backend-plugin-api@0.6.8 + +## 0.2.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-app-api@0.5.9-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + +## 0.2.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-app-api@0.5.9-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.9-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + ## 0.2.8-next.0 ### Patch Changes diff --git a/packages/backend-defaults/README.md b/packages/backend-defaults/README.md index 2dcc5766df..01e42d42c4 100644 --- a/packages/backend-defaults/README.md +++ b/packages/backend-defaults/README.md @@ -1,7 +1,5 @@ # @backstage/backend-defaults -**This package is EXPERIMENTAL, we recommend against using it for production deployments** - This package provides the default implementations and setup for a standard Backstage backend app. ## Installation diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index bb2e2b0743..3e8793cd3e 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", "description": "Backend defaults used by Backstage backend apps", - "version": "0.2.8-next.0", + "version": "0.2.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-dev-utils/CHANGELOG.md b/packages/backend-dev-utils/CHANGELOG.md index f1d1887b78..1c4deba50a 100644 --- a/packages/backend-dev-utils/CHANGELOG.md +++ b/packages/backend-dev-utils/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/backend-dev-utils +## 0.1.3 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status + +## 0.1.3-next.0 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status + ## 0.1.2 ### Patch Changes diff --git a/packages/backend-dev-utils/README.md b/packages/backend-dev-utils/README.md index cebe591d50..744a9d2ca0 100644 --- a/packages/backend-dev-utils/README.md +++ b/packages/backend-dev-utils/README.md @@ -1,7 +1,5 @@ # @backstage/backend-dev-utils -**This package is EXPERIMENTAL, but we encourage use of it to add support for the new backend system in your own plugins** - This package helps set up local development environments for Backstage backend packages. ## Installation diff --git a/packages/backend-dev-utils/package.json b/packages/backend-dev-utils/package.json index b6156e622c..67fd763dcd 100644 --- a/packages/backend-dev-utils/package.json +++ b/packages/backend-dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-dev-utils", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/backend-plugin-manager/.eslintrc.js b/packages/backend-dynamic-feature-service/.eslintrc.js similarity index 100% rename from packages/backend-plugin-manager/.eslintrc.js rename to packages/backend-dynamic-feature-service/.eslintrc.js diff --git a/packages/backend-plugin-manager/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md similarity index 56% rename from packages/backend-plugin-manager/CHANGELOG.md rename to packages/backend-dynamic-feature-service/CHANGELOG.md index f826362f2d..fa60b2d208 100644 --- a/packages/backend-plugin-manager/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,4 +1,209 @@ -# @backstage/backend-plugin-manager +# @backstage/backend-dynamic-feature-service + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/cli-node@0.2.2 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-events-backend@0.2.19-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.19-next.0 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## 0.1.0 + +### Minor Changes + +- eb81f42: New `backend-dynamic-feature-service` package, for the discovery of dynamic frontend and backend plugins (and modules) and the loading of the backend ones inside the backend application. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/cli-node@0.2.2 + - @backstage/plugin-events-backend@0.2.18 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-catalog-backend@1.16.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.18 + - @backstage/plugin-search-common@1.2.10 + +## 0.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-events-backend@0.2.18-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/cli-node@0.2.2-next.0 + +## 0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-node@0.2.2-next.0 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/plugin-events-backend@0.2.18-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.18-next.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-common@1.2.9 + +## 0.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-events-backend@0.2.18-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + +## 0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/cli-node@0.2.1 + - @backstage/plugin-events-backend@0.2.17 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.17 + - @backstage/plugin-search-common@1.2.9 + +## 0.0.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-events-backend@0.2.17-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + +## 0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/plugin-events-backend@0.2.17-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + +## 0.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-events-backend@0.2.17-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 ## 0.0.4-next.0 diff --git a/packages/backend-plugin-manager/README.md b/packages/backend-dynamic-feature-service/README.md similarity index 64% rename from packages/backend-plugin-manager/README.md rename to packages/backend-dynamic-feature-service/README.md index c9fd666fb3..211148ee10 100644 --- a/packages/backend-plugin-manager/README.md +++ b/packages/backend-dynamic-feature-service/README.md @@ -1,10 +1,6 @@ -# @backstage/backend-plugin-manager +# @backstage/backend-dynamic-feature-service -This package adds experimental support for **dynamic backend plugins**, according to the content of the proposal in [RFC 18390](https://github.com/backstage/backstage/issues/18390) - -## Status - -**This package is EXPERIMENTAL, and is subject to change according to the discussions and conclusions that happen around the RFC mentioned above.** +This package adds experimental support for **dynamic backend features (plugins and modules)**, according to the content of the proposal in [RFC 18390](https://github.com/backstage/backstage/issues/18390) ## Testing the backend dynamic plugins feature @@ -12,9 +8,9 @@ In order to test the dynamic backend plugins feature provided by this package, e ## How it works -The backend plugin manager is a service that scans a configured root directory (`dynamicPlugins.rootDirectory` in the app config) for dynamic plugin packages, and loads them dynamically. +The dynamic plugin manager is a service that scans a configured root directory (`dynamicPlugins.rootDirectory` in the app config) for dynamic plugin packages, and loads them dynamically. -In the `backend-next` application, it can be enabled by adding the `backend-plugin-manager` as a dependency in the `package.json` and the following lines in the `src/index.ts` file: +In the `backend-next` application, it can be enabled by adding the `backend-dynamic-feature-service` as a dependency in the `package.json` and the following lines in the `src/index.ts` file: ```ts const backend = createBackend(); @@ -36,9 +32,9 @@ Points 2 and 3 can be done by the `export-dynamic-plugin` CLI command used to pe ### About the `export-dynamic-plugin` command -The `export-dynamic-plugin` CLI command, used the dynamic plugin examples provided in the [showcase repository](https://github.com/janus-idp/dynamic-backend-plugins-showcase), is part of a `@backstage/cli` fork (`@dfatwork-pkgs/backstage-cli@0.22.9-next.6`), and can be used to help packaging the dynamic plugins according to the constraints mentioned above, in order to allow straightforward testing of the dynamic plugins feature. +The `export-dynamic-plugin` CLI command, used the dynamic plugin examples provided in the [showcase repository](https://github.com/janus-idp/dynamic-backend-plugins-showcase), is part of the `@janus-idp/cli` package, and can be used to help packaging the dynamic plugins according to the constraints mentioned above, in order to allow straightforward testing of the dynamic plugins feature. -However the `backend-plugin-manager` experimental package does **not** depend on the use of this additional CLI command, and in future steps of this backend dynamic plugin work, the use of such a dedicated command might not even be necessary. +However the `backend-dynamic-feature-service` experimental package does **not** depend on the use of this additional CLI command, and in future steps of this backend dynamic plugin work, the use of such a dedicated command might not even be necessary. ### About the support of the legacy backend system @@ -49,4 +45,4 @@ This is why the API related to the old backend is already marked as deprecated. ### Future work -The current implementation of the backend plugin manager is a first step towards the final implementation of the dynamic backend plugins feature, which will be completed / simplified in future steps, as the status of the backstage codebase allows it. +The current implementation of the dynamic plugin manager is a first step towards the final implementation of the dynamic features loading, which will be completed / simplified in future steps, as the status of the backstage codebase allows it. diff --git a/packages/backend-plugin-manager/api-report.md b/packages/backend-dynamic-feature-service/api-report.md similarity index 85% rename from packages/backend-plugin-manager/api-report.md rename to packages/backend-dynamic-feature-service/api-report.md index b4221007ae..caf2cf01ec 100644 --- a/packages/backend-plugin-manager/api-report.md +++ b/packages/backend-dynamic-feature-service/api-report.md @@ -1,4 +1,4 @@ -## API Report File for "@backstage/backend-plugin-manager" +## API Report File for "@backstage/backend-dynamic-feature-service" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). @@ -65,6 +65,44 @@ export interface BaseDynamicPlugin { // @public (undocumented) export type DynamicPlugin = FrontendDynamicPlugin | BackendDynamicPlugin; +// @public (undocumented) +export class DynamicPluginManager implements DynamicPluginProvider { + // (undocumented) + addBackendPlugin(plugin: BackendDynamicPlugin): void; + // (undocumented) + get availablePackages(): ScannedPluginPackage[]; + // (undocumented) + backendPlugins(): BackendDynamicPlugin[]; + // (undocumented) + static create( + options: DynamicPluginManagerOptions, + ): Promise; + // (undocumented) + frontendPlugins(): FrontendDynamicPlugin[]; + // (undocumented) + plugins(): DynamicPlugin[]; +} + +// @public (undocumented) +export interface DynamicPluginManagerOptions { + // (undocumented) + config: Config; + // (undocumented) + logger: LoggerService; + // (undocumented) + moduleLoader?: ModuleLoader; + // (undocumented) + preferAlpha?: boolean; +} + +// @public (undocumented) +export interface DynamicPluginProvider + extends FrontendPluginProvider, + BackendPluginProvider { + // (undocumented) + plugins(): DynamicPlugin[]; +} + // @public (undocumented) export interface DynamicPluginsFactoryOptions { // (undocumented) @@ -80,11 +118,11 @@ export const dynamicPluginsFeatureDiscoveryServiceFactory: () => ServiceFactory< // @public (undocumented) export const dynamicPluginsServiceFactory: ( options?: DynamicPluginsFactoryOptions | undefined, -) => ServiceFactory; +) => ServiceFactory; // @public (undocumented) export const dynamicPluginsServiceRef: ServiceRef< - BackendPluginProvider, + DynamicPluginProvider, 'root' >; @@ -94,6 +132,12 @@ export interface FrontendDynamicPlugin extends BaseDynamicPlugin { platform: 'web'; } +// @public (undocumented) +export interface FrontendPluginProvider { + // (undocumented) + frontendPlugins(): FrontendDynamicPlugin[]; +} + // @public (undocumented) export function isBackendDynamicPluginInstaller( obj: any, @@ -161,25 +205,6 @@ export interface NewBackendPluginInstaller { kind: 'new'; } -// @public (undocumented) -export class PluginManager implements BackendPluginProvider { - // (undocumented) - addBackendPlugin(plugin: BackendDynamicPlugin): void; - // (undocumented) - get availablePackages(): ScannedPluginPackage[]; - // (undocumented) - backendPlugins(): BackendDynamicPlugin[]; - // (undocumented) - static fromConfig( - config: Config, - logger: LoggerService, - preferAlpha?: boolean, - moduleLoader?: ModuleLoader, - ): Promise; - // (undocumented) - readonly plugins: DynamicPlugin[]; -} - // @public (undocumented) export type ScannedPluginManifest = BackstagePackageJson & Required> & diff --git a/packages/backend-dynamic-feature-service/catalog-info.yaml b/packages/backend-dynamic-feature-service/catalog-info.yaml new file mode 100644 index 0000000000..1269a5c406 --- /dev/null +++ b/packages/backend-dynamic-feature-service/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-backend-dynamic-feature-service + title: '@backstage/backend-dynamic-feature-service' + description: Backstage backend service to handle dynamic features +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/backend-plugin-manager/config.d.ts b/packages/backend-dynamic-feature-service/config.d.ts similarity index 100% rename from packages/backend-plugin-manager/config.d.ts rename to packages/backend-dynamic-feature-service/config.d.ts diff --git a/packages/backend-plugin-manager/package.json b/packages/backend-dynamic-feature-service/package.json similarity index 92% rename from packages/backend-plugin-manager/package.json rename to packages/backend-dynamic-feature-service/package.json index 643a94f3cb..51f7184c49 100644 --- a/packages/backend-plugin-manager/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,8 +1,7 @@ { - "name": "@backstage/backend-plugin-manager", - "description": "Backstage plugin management backend", - "version": "0.0.4-next.0", - "private": true, + "name": "@backstage/backend-dynamic-feature-service", + "description": "Backstage dynamic feature service", + "version": "0.1.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-plugin-manager/src/__testUtils__/testUtils.ts b/packages/backend-dynamic-feature-service/src/__testUtils__/testUtils.ts similarity index 100% rename from packages/backend-plugin-manager/src/__testUtils__/testUtils.ts rename to packages/backend-dynamic-feature-service/src/__testUtils__/testUtils.ts diff --git a/packages/backend-plugin-manager/src/index.ts b/packages/backend-dynamic-feature-service/src/index.ts similarity index 100% rename from packages/backend-plugin-manager/src/index.ts rename to packages/backend-dynamic-feature-service/src/index.ts diff --git a/packages/backend-plugin-manager/src/loader/CommonJSModuleLoader.ts b/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts similarity index 96% rename from packages/backend-plugin-manager/src/loader/CommonJSModuleLoader.ts rename to packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts index 06a08117cd..66af367cea 100644 --- a/packages/backend-plugin-manager/src/loader/CommonJSModuleLoader.ts +++ b/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts @@ -49,6 +49,6 @@ export class CommonJSModuleLoader implements ModuleLoader { } async load(packagePath: string): Promise { - return await import(/* webpackIgnore: true */ packagePath); + return await require(/* webpackIgnore: true */ packagePath); } } diff --git a/packages/backend-plugin-manager/src/loader/index.ts b/packages/backend-dynamic-feature-service/src/loader/index.ts similarity index 100% rename from packages/backend-plugin-manager/src/loader/index.ts rename to packages/backend-dynamic-feature-service/src/loader/index.ts diff --git a/packages/backend-plugin-manager/src/loader/types.ts b/packages/backend-dynamic-feature-service/src/loader/types.ts similarity index 100% rename from packages/backend-plugin-manager/src/loader/types.ts rename to packages/backend-dynamic-feature-service/src/loader/types.ts diff --git a/packages/backend-plugin-manager/src/manager/index.ts b/packages/backend-dynamic-feature-service/src/manager/index.ts similarity index 85% rename from packages/backend-plugin-manager/src/manager/index.ts rename to packages/backend-dynamic-feature-service/src/manager/index.ts index a9cd163617..dd6aaacfe9 100644 --- a/packages/backend-plugin-manager/src/manager/index.ts +++ b/packages/backend-dynamic-feature-service/src/manager/index.ts @@ -25,14 +25,19 @@ export type { NewBackendPluginInstaller, LegacyBackendPluginInstaller, LegacyPluginEnvironment, + DynamicPluginProvider, + FrontendPluginProvider, BackendPluginProvider, } from './types'; export { - PluginManager, + DynamicPluginManager, dynamicPluginsFeatureDiscoveryServiceFactory, dynamicPluginsServiceFactory, dynamicPluginsServiceRef, } from './plugin-manager'; -export type { DynamicPluginsFactoryOptions } from './plugin-manager'; +export type { + DynamicPluginManagerOptions, + DynamicPluginsFactoryOptions, +} from './plugin-manager'; diff --git a/packages/backend-plugin-manager/src/manager/plugin-manager.test.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts similarity index 73% rename from packages/backend-plugin-manager/src/manager/plugin-manager.test.ts rename to packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts index 4aabd2c072..487729687f 100644 --- a/packages/backend-plugin-manager/src/manager/plugin-manager.test.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { PluginManager, dynamicPluginsServiceFactory } from './plugin-manager'; +import { + DynamicPluginManager, + dynamicPluginsServiceFactory, +} from './plugin-manager'; import { BackendFeature, coreServices, @@ -44,7 +47,7 @@ import { PluginScanner } from '../scanner/plugin-scanner'; import { findPaths } from '@backstage/cli-common'; import { createMockDirectory } from '@backstage/backend-test-utils'; -describe('backend-plugin-manager', () => { +describe('backend-dynamic-feature-service', () => { const mockDir = createMockDirectory(); describe('loadPlugins', () => { @@ -113,6 +116,94 @@ describe('backend-plugin-manager', () => { >([]); }, }, + { + name: 'should successfully load a new backend plugin by the default BackendFeature', + packageManifest: { + name: 'backend-dynamic-plugin-test', + version: '0.0.0', + backstage: { + role: 'backend-plugin', + }, + main: 'dist/index.cjs.js', + }, + indexFile: { + retativePath: ['dist', 'index.cjs.js'], + content: `const alpha = { $$type: '@backstage/BackendFeature' }; exports["default"] = alpha;`, + }, + expectedLogs(location) { + return { + infos: [ + { + message: `loaded dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`, + }, + ], + }; + }, + checkLoadedPlugins(plugins) { + expect(plugins).toMatchObject([ + { + name: 'backend-dynamic-plugin-test', + version: '0.0.0', + role: 'backend-plugin', + platform: 'node', + installer: { + kind: 'new', + }, + }, + ]); + const installer: NewBackendPluginInstaller = ( + plugins[0] as BackendDynamicPlugin + ).installer as NewBackendPluginInstaller; + expect(installer.install()).toEqual< + BackendFeature | BackendFeature[] + >({ $$type: '@backstage/BackendFeature' }); + }, + }, + { + name: 'should successfully load a new backend plugin by the default BackendFeatureFactory', + packageManifest: { + name: 'backend-dynamic-plugin-test', + version: '0.0.0', + backstage: { + role: 'backend-plugin', + }, + main: 'dist/index.cjs.js', + }, + indexFile: { + retativePath: ['dist', 'index.cjs.js'], + content: `const alpha = () => { return { $$type: '@backstage/BackendFeature' } }; + alpha.$$type = '@backstage/BackendFeatureFactory'; + exports["default"] = alpha;`, + }, + expectedLogs(location) { + return { + infos: [ + { + message: `loaded dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`, + }, + ], + }; + }, + checkLoadedPlugins(plugins) { + expect(plugins).toMatchObject([ + { + name: 'backend-dynamic-plugin-test', + version: '0.0.0', + role: 'backend-plugin', + platform: 'node', + installer: { + kind: 'new', + }, + }, + ]); + const installer: NewBackendPluginInstaller = ( + plugins[0] as BackendDynamicPlugin + ).installer as NewBackendPluginInstaller; + expect(installer.install()).toEqual< + BackendFeature | BackendFeature[] + >({ $$type: '@backstage/BackendFeature' }); + }, + }, { name: 'should successfully load a new backend plugin module', packageManifest: { @@ -221,7 +312,7 @@ describe('backend-plugin-manager', () => { return { errors: [ { - message: `dynamic backend plugin 'backend-dynamic-plugin-test' could not be loaded from '${location}': the module should export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field.`, + message: `dynamic backend plugin 'backend-dynamic-plugin-test' could not be loaded from '${location}': the module should either export a 'BackendFeature' or 'BackendFeatureFactory' as default export, or export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field as dynamic loading entrypoint.`, }, ], }; @@ -249,7 +340,7 @@ describe('backend-plugin-manager', () => { return { errors: [ { - message: `dynamic backend plugin 'backend-dynamic-plugin-test' could not be loaded from '${location}': the module should export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field.`, + message: `dynamic backend plugin 'backend-dynamic-plugin-test' could not be loaded from '${location}': the module should either export a 'BackendFeature' or 'BackendFeatureFactory' as default export, or export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field as dynamic loading entrypoint.`, }, ], }; @@ -374,12 +465,16 @@ describe('backend-plugin-manager', () => { mockDir.setContent(mockedFiles); const logger = new MockedLogger(); - const pluginManager = new (PluginManager as any)(logger, [plugin], { + const pluginManager = new (DynamicPluginManager as any)( logger, - async bootstrap(_: string, __: string[]): Promise {}, - load: async (packagePath: string) => - await import(/* webpackIgnore: true */ packagePath), - }); + [plugin], + { + logger, + async bootstrap(_: string, __: string[]): Promise {}, + load: async (packagePath: string) => + await require(/* webpackIgnore: true */ packagePath), + }, + ); const loadedPlugins: DynamicPlugin[] = await pluginManager.loadPlugins(); @@ -395,7 +490,10 @@ describe('backend-plugin-manager', () => { describe('backendPlugins', () => { it('should return only backend plugins and modules', async () => { const logger = new MockedLogger(); - const pluginManager = new (PluginManager as any)(logger, '', []); + const pluginManager = new (DynamicPluginManager as any)( + logger, + [], + ) as DynamicPluginManager; const plugins: BaseDynamicPlugin[] = [ { name: 'a-frontend-plugin', @@ -416,7 +514,7 @@ describe('backend-plugin-manager', () => { version: '0.0.0', }, ]; - pluginManager.plugins = plugins; + (pluginManager as any)._plugins = plugins; expect(pluginManager.backendPlugins()).toEqual([ { name: 'a-backend-plugin', @@ -434,6 +532,57 @@ describe('backend-plugin-manager', () => { }); }); + describe('frontendPlugins', () => { + it('should return only frontend plugins', async () => { + const logger = new MockedLogger(); + const pluginManager = new (DynamicPluginManager as any)( + logger, + [], + ) as DynamicPluginManager; + const plugins: BaseDynamicPlugin[] = [ + { + name: 'a-frontend-plugin', + platform: 'web', + role: 'frontend-plugin', + version: '0.0.0', + }, + { + name: 'a-frontend-module', + platform: 'web', + role: 'frontend-plugin-module', + version: '0.0.0', + }, + { + name: 'a-backend-plugin', + platform: 'node', + role: 'backend-plugin', + version: '0.0.0', + }, + { + name: 'a-backend-module', + platform: 'node', + role: 'backend-plugin-module', + version: '0.0.0', + }, + ]; + (pluginManager as any)._plugins = plugins; + expect(pluginManager.frontendPlugins()).toEqual([ + { + name: 'a-frontend-plugin', + platform: 'web', + role: 'frontend-plugin', + version: '0.0.0', + }, + { + name: 'a-frontend-module', + platform: 'web', + role: 'frontend-plugin-module', + version: '0.0.0', + }, + ]); + }); + }); + describe('dynamicPluginsServiceFactory', () => { const otherMockDir = createMockDirectory(); @@ -459,27 +608,29 @@ describe('backend-plugin-manager', () => { 'a-dynamic-plugin': {}, }); - const fromConfigSpier = jest.spyOn(PluginManager, 'fromConfig'); + const fromConfigSpier = jest.spyOn(DynamicPluginManager, 'create'); const applyConfigSpier = jest .spyOn(PluginScanner.prototype as any, 'applyConfig') .mockImplementation(() => {}); const scanRootSpier = jest .spyOn(PluginScanner.prototype, 'scanRoot') - .mockImplementation(async () => [ - { - location: url.pathToFileURL( - mockDir.resolve('dynamic-plugins-root/a-dynamic-plugin'), - ), - manifest: { - name: 'test', - version: '0.0.0', - main: 'dist/index.cjs.js', - backstage: { - role: 'backend-plugin', + .mockImplementation(async () => ({ + packages: [ + { + location: url.pathToFileURL( + mockDir.resolve('dynamic-plugins-root/a-dynamic-plugin'), + ), + manifest: { + name: 'test', + version: '0.0.0', + main: 'dist/index.cjs.js', + backstage: { + role: 'backend-plugin', + }, }, }, - }, - ]); + ], + })); const mockedModuleLoader = { logger, bootstrap: jest.fn(), diff --git a/packages/backend-plugin-manager/src/manager/plugin-manager.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts similarity index 69% rename from packages/backend-plugin-manager/src/manager/plugin-manager.ts rename to packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts index ad76c7529e..edc3e20f72 100644 --- a/packages/backend-plugin-manager/src/manager/plugin-manager.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts @@ -15,10 +15,11 @@ */ import { Config } from '@backstage/config'; import { - BackendPluginProvider, + DynamicPluginProvider, BackendDynamicPlugin, isBackendDynamicPluginInstaller, DynamicPlugin, + FrontendDynamicPlugin, } from './types'; import { ScannedPluginPackage } from '../scanner'; import { PluginScanner } from '../scanner/plugin-scanner'; @@ -44,24 +45,37 @@ import { /** * @public */ -export class PluginManager implements BackendPluginProvider { - static async fromConfig( - config: Config, - logger: LoggerService, - preferAlpha: boolean = false, - moduleLoader: ModuleLoader = new CommonJSModuleLoader(logger), - ): Promise { +export interface DynamicPluginManagerOptions { + config: Config; + logger: LoggerService; + preferAlpha?: boolean; + moduleLoader?: ModuleLoader; +} + +/** + * @public + */ +export class DynamicPluginManager implements DynamicPluginProvider { + static async create( + options: DynamicPluginManagerOptions, + ): Promise { /* eslint-disable-next-line no-restricted-syntax */ const backstageRoot = findPaths(__dirname).targetRoot; - const scanner = new PluginScanner( - config, - logger, + const scanner = PluginScanner.create({ + config: options.config, + logger: options.logger, backstageRoot, - preferAlpha, - ); - const scannedPlugins = await scanner.scanRoot(); + preferAlpha: options.preferAlpha, + }); + const scannedPlugins = (await scanner.scanRoot()).packages; scanner.trackChanges(); - const manager = new PluginManager(logger, scannedPlugins, moduleLoader); + const moduleLoader = + options.moduleLoader || new CommonJSModuleLoader(options.logger); + const manager = new DynamicPluginManager( + options.logger, + scannedPlugins, + moduleLoader, + ); const dynamicPluginsPaths = scannedPlugins.map(p => fs.realpathSync( @@ -76,15 +90,15 @@ export class PluginManager implements BackendPluginProvider { moduleLoader.bootstrap(backstageRoot, dynamicPluginsPaths); scanner.subscribeToRootDirectoryChange(async () => { - manager._availablePackages = await scanner.scanRoot(); + manager._availablePackages = (await scanner.scanRoot()).packages; // TODO: do not store _scannedPlugins again, but instead store a diff of the changes }); - manager.plugins.push(...(await manager.loadPlugins())); + manager._plugins.push(...(await manager.loadPlugins())); return manager; } - readonly plugins: DynamicPlugin[]; + private readonly _plugins: DynamicPlugin[]; private _availablePackages: ScannedPluginPackage[]; private constructor( @@ -92,7 +106,7 @@ export class PluginManager implements BackendPluginProvider { private packages: ScannedPluginPackage[], private readonly moduleLoader: ModuleLoader, ) { - this.plugins = []; + this._plugins = []; this._availablePackages = packages; } @@ -101,7 +115,7 @@ export class PluginManager implements BackendPluginProvider { } addBackendPlugin(plugin: BackendDynamicPlugin): void { - this.plugins.push(plugin); + this._plugins.push(plugin); } private async loadPlugins(): Promise { @@ -140,12 +154,25 @@ export class PluginManager implements BackendPluginProvider { `${plugin.location}/${plugin.manifest.main}`, ); try { - const { dynamicPluginInstaller } = await this.moduleLoader.load( - packagePath, - ); + const pluginModule = await this.moduleLoader.load(packagePath); + + let dynamicPluginInstaller; + if (isBackendFeature(pluginModule.default)) { + dynamicPluginInstaller = { + kind: 'new', + install: () => pluginModule.default, + }; + } else if (isBackendFeatureFactory(pluginModule.default)) { + dynamicPluginInstaller = { + kind: 'new', + install: pluginModule.default, + }; + } else { + dynamicPluginInstaller = pluginModule.dynamicPluginInstaller; + } if (!isBackendDynamicPluginInstaller(dynamicPluginInstaller)) { this.logger.error( - `dynamic backend plugin '${plugin.manifest.name}' could not be loaded from '${plugin.location}': the module should export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field.`, + `dynamic backend plugin '${plugin.manifest.name}' could not be loaded from '${plugin.location}': the module should either export a 'BackendFeature' or 'BackendFeatureFactory' as default export, or export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field as dynamic loading entrypoint.`, ); return undefined; } @@ -169,16 +196,26 @@ export class PluginManager implements BackendPluginProvider { } backendPlugins(): BackendDynamicPlugin[] { - return this.plugins.filter( + return this._plugins.filter( (p): p is BackendDynamicPlugin => p.platform === 'node', ); } + + frontendPlugins(): FrontendDynamicPlugin[] { + return this._plugins.filter( + (p): p is FrontendDynamicPlugin => p.platform === 'web', + ); + } + + plugins(): DynamicPlugin[] { + return this._plugins; + } } /** * @public */ -export const dynamicPluginsServiceRef = createServiceRef( +export const dynamicPluginsServiceRef = createServiceRef( { id: 'core.dynamicplugins', scope: 'root', @@ -203,15 +240,12 @@ export const dynamicPluginsServiceFactory = createServiceFactory( logger: coreServices.rootLogger, }, async factory({ config, logger }) { - if (options?.moduleLoader) { - return await PluginManager.fromConfig( - config, - logger, - true, - options.moduleLoader(logger), - ); - } - return await PluginManager.fromConfig(config, logger, true); + return await DynamicPluginManager.create({ + config, + logger, + preferAlpha: true, + moduleLoader: options?.moduleLoader?.(logger), + }); }, }), ); @@ -220,7 +254,7 @@ class DynamicPluginsEnabledFeatureDiscoveryService implements FeatureDiscoveryService { constructor( - private readonly dynamicPlugins: BackendPluginProvider, + private readonly dynamicPlugins: DynamicPluginProvider, private readonly featureDiscoveryService?: FeatureDiscoveryService, ) {} @@ -263,3 +297,21 @@ export const dynamicPluginsFeatureDiscoveryServiceFactory = return new DynamicPluginsEnabledFeatureDiscoveryService(dynamicPlugins); }, }); + +function isBackendFeature(value: unknown): value is BackendFeature { + return ( + !!value && + typeof value === 'object' && + (value as BackendFeature).$$type === '@backstage/BackendFeature' + ); +} + +function isBackendFeatureFactory( + value: unknown, +): value is () => BackendFeature { + return ( + !!value && + typeof value === 'function' && + (value as any).$$type === '@backstage/BackendFeatureFactory' + ); +} diff --git a/packages/backend-plugin-manager/src/manager/types.ts b/packages/backend-dynamic-feature-service/src/manager/types.ts similarity index 94% rename from packages/backend-plugin-manager/src/manager/types.ts rename to packages/backend-dynamic-feature-service/src/manager/types.ts index 201941292d..0038470686 100644 --- a/packages/backend-plugin-manager/src/manager/types.ts +++ b/packages/backend-dynamic-feature-service/src/manager/types.ts @@ -67,6 +67,15 @@ export type LegacyPluginEnvironment = { pluginProvider: BackendPluginProvider; }; +/** + * @public + */ +export interface DynamicPluginProvider + extends FrontendPluginProvider, + BackendPluginProvider { + plugins(): DynamicPlugin[]; +} + /** * @public */ @@ -74,6 +83,13 @@ export interface BackendPluginProvider { backendPlugins(): BackendDynamicPlugin[]; } +/** + * @public + */ +export interface FrontendPluginProvider { + frontendPlugins(): FrontendDynamicPlugin[]; +} + /** * @public */ diff --git a/packages/backend-plugin-manager/src/scanner/index.ts b/packages/backend-dynamic-feature-service/src/scanner/index.ts similarity index 100% rename from packages/backend-plugin-manager/src/scanner/index.ts rename to packages/backend-dynamic-feature-service/src/scanner/index.ts diff --git a/packages/backend-plugin-manager/src/scanner/plugin-scanner-watcher.test.ts b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner-watcher.test.ts similarity index 93% rename from packages/backend-plugin-manager/src/scanner/plugin-scanner-watcher.test.ts rename to packages/backend-dynamic-feature-service/src/scanner/plugin-scanner-watcher.test.ts index e346bf749f..2d017d805b 100644 --- a/packages/backend-plugin-manager/src/scanner/plugin-scanner-watcher.test.ts +++ b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner-watcher.test.ts @@ -64,22 +64,23 @@ describe('plugin-scanner', () => { unsubscribe: configUnsubscribe, }; }; - const pluginScanner = new PluginScanner( + const pluginScanner = PluginScanner.create({ config, logger, - backstageRootDirectory, - false, - ); + backstageRoot: backstageRootDirectory, + preferAlpha: false, + }); await pluginScanner.trackChanges(); expect(onConfigChange).toBeDefined(); - let scannedPlugins: ScannedPluginPackage[] = - await pluginScanner.scanRoot(); + let scannedPlugins: ScannedPluginPackage[] = ( + await pluginScanner.scanRoot() + ).packages; expect(scannedPlugins).toEqual([]); const rootDirectorySubscriber = jest.fn(async () => { - scannedPlugins = await pluginScanner.scanRoot(); + scannedPlugins = (await pluginScanner.scanRoot()).packages; }); pluginScanner.subscribeToRootDirectoryChange(rootDirectorySubscriber); @@ -229,6 +230,7 @@ describe('plugin-scanner', () => { logger.logs = {}; const debug = jest.spyOn(logger, 'debug'); + const info = jest.spyOn(logger, 'info'); // Check that not all file changes trigger a new scan of plugins await mkdir( @@ -240,8 +242,11 @@ describe('plugin-scanner', () => { ), ); await waitForExpect(() => { - expect(debug).toHaveBeenCalledTimes(1); + expect(debug).toHaveBeenCalled(); }); + expect(info).toHaveBeenCalledTimes(0); + debug.mockClear(); + await writeFile( join( backstageRootDirectory, @@ -252,8 +257,11 @@ describe('plugin-scanner', () => { 'content', ); await waitForExpect(() => { - expect(debug).toHaveBeenCalledTimes(2); + expect(debug).toHaveBeenCalled(); }); + expect(info).toHaveBeenCalledTimes(0); + debug.mockClear(); + await rm( join( backstageRootDirectory, @@ -263,8 +271,11 @@ describe('plugin-scanner', () => { ), ); await waitForExpect(() => { - expect(debug).toHaveBeenCalledTimes(3); + expect(debug).toHaveBeenCalled(); }); + expect(info).toHaveBeenCalledTimes(0); + debug.mockClear(); + await rm( join( backstageRootDirectory, @@ -275,8 +286,10 @@ describe('plugin-scanner', () => { { recursive: true }, ); await waitForExpect(() => { - expect(debug).toHaveBeenCalledTimes(4); + expect(debug).toHaveBeenCalled(); }); + expect(info).toHaveBeenCalledTimes(0); + debug.mockClear(); const onWindows = path.sep === '\\'; // Order of events is not fixed on Windows. diff --git a/packages/backend-plugin-manager/src/scanner/plugin-scanner.test.ts b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.test.ts similarity index 98% rename from packages/backend-plugin-manager/src/scanner/plugin-scanner.test.ts rename to packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.test.ts index d4ab8940c9..e52eee87c4 100644 --- a/packages/backend-plugin-manager/src/scanner/plugin-scanner.test.ts +++ b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.test.ts @@ -206,11 +206,11 @@ Please add '${mockDir.resolve( const logger = new MockedLogger(); const backstageRoot = tc.backstageRoot ? tc.backstageRoot : ''; function toTest(): PluginScanner { - return new PluginScanner( - new ConfigReader(tc.config), + return PluginScanner.create({ + config: new ConfigReader(tc.config), logger, backstageRoot, - ); + }); } if (tc.fileSystem) { mockDir.setContent(tc.fileSystem); @@ -609,8 +609,8 @@ Please add '${mockDir.resolve( const logger = new MockedLogger(); const backstageRoot = mockDir.resolve('backstageRoot'); async function toTest(): Promise { - const pluginScanner = new PluginScanner( - new ConfigReader( + const pluginScanner = PluginScanner.create({ + config: new ConfigReader( tc.fileSystem ? { dynamicPlugins: { @@ -621,9 +621,9 @@ Please add '${mockDir.resolve( ), logger, backstageRoot, - tc.preferAlpha === undefined ? false : tc.preferAlpha, - ); - return await pluginScanner.scanRoot(); + preferAlpha: tc.preferAlpha, + }); + return (await pluginScanner.scanRoot()).packages; } if (tc.fileSystem) { mockDir.setContent(tc.fileSystem); diff --git a/packages/backend-plugin-manager/src/scanner/plugin-scanner.ts b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.ts similarity index 83% rename from packages/backend-plugin-manager/src/scanner/plugin-scanner.ts rename to packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.ts index 51e38002e9..25e0d614dd 100644 --- a/packages/backend-plugin-manager/src/scanner/plugin-scanner.ts +++ b/packages/backend-dynamic-feature-service/src/scanner/plugin-scanner.ts @@ -24,28 +24,39 @@ import debounce from 'lodash/debounce'; import { PackagePlatform, PackageRoles } from '@backstage/cli-node'; import { LoggerService } from '@backstage/backend-plugin-api'; +export interface DynamicPluginScannerOptions { + config: Config; + backstageRoot: string; + logger: LoggerService; + preferAlpha?: boolean; +} + +export interface ScanRootResponse { + packages: ScannedPluginPackage[]; +} + export class PluginScanner { - private readonly logger: LoggerService; - private backstageRoot: string; - readonly #config: Config; private _rootDirectory?: string; - private readonly preferAlpha: boolean; private configUnsubscribe?: () => void; private rootDirectoryWatcher?: chokidar.FSWatcher; private subscribers: (() => void)[] = []; - constructor( - config: Config, - logger: LoggerService, - backstageRoot: string, - preferAlpha: boolean = false, - ) { - this.backstageRoot = backstageRoot; - this.logger = logger; - this.preferAlpha = preferAlpha; - this.#config = config; + private constructor( + private readonly config: Config, + private readonly logger: LoggerService, + private readonly backstageRoot: string, + private readonly preferAlpha: boolean, + ) {} - this.applyConfig(); + static create(options: DynamicPluginScannerOptions): PluginScanner { + const scanner = new PluginScanner( + options.config, + options.logger, + options.backstageRoot, + options.preferAlpha || false, + ); + scanner.applyConfig(); + return scanner; } subscribeToRootDirectoryChange(subscriber: () => void) { @@ -57,7 +68,7 @@ export class PluginScanner { } private applyConfig(): void | never { - const dynamicPlugins = this.#config.getOptional('dynamicPlugins'); + const dynamicPlugins = this.config.getOptional('dynamicPlugins'); if (!dynamicPlugins) { this.logger.info("'dynamicPlugins' config entry not found."); this._rootDirectory = undefined; @@ -114,9 +125,9 @@ export class PluginScanner { this._rootDirectory = dynamicPluginsRootPath; } - async scanRoot(): Promise { + async scanRoot(): Promise { if (!this._rootDirectory) { - return []; + return { packages: [] }; } const dynamicPluginsLocation = this._rootDirectory; @@ -189,7 +200,7 @@ export class PluginScanner { scannedPlugins.push(scannedPlugin); } - return scannedPlugins; + return { packages: scannedPlugins }; } private async scanDir(pluginHome: string): Promise { @@ -263,30 +274,28 @@ export class PluginScanner { }; await setupRootDirectoryWatcher(); - if (this.#config.subscribe) { - const { unsubscribe } = this.#config.subscribe( - async (): Promise => { - const oldRootDirectory = this._rootDirectory; - try { - this.applyConfig(); - } catch (e) { - this.logger.error( - 'failed to apply new config for dynamic plugins', - e, - ); + if (this.config.subscribe) { + const { unsubscribe } = this.config.subscribe(async (): Promise => { + const oldRootDirectory = this._rootDirectory; + try { + this.applyConfig(); + } catch (e) { + this.logger.error( + 'failed to apply new config for dynamic plugins', + e, + ); + } + if (oldRootDirectory !== this._rootDirectory) { + this.logger.info( + `rootDirectory changed in Config from '${oldRootDirectory}' to '${this._rootDirectory}'`, + ); + this.subscribers.forEach(s => s()); + if (this.rootDirectoryWatcher) { + await this.rootDirectoryWatcher.close(); } - if (oldRootDirectory !== this._rootDirectory) { - this.logger.info( - `rootDirectory changed in Config from '${oldRootDirectory}' to '${this._rootDirectory}'`, - ); - this.subscribers.forEach(s => s()); - if (this.rootDirectoryWatcher) { - await this.rootDirectoryWatcher.close(); - } - await setupRootDirectoryWatcher(); - } - }, - ); + await setupRootDirectoryWatcher(); + } + }); this.configUnsubscribe = unsubscribe; } } diff --git a/packages/backend-plugin-manager/src/scanner/types.ts b/packages/backend-dynamic-feature-service/src/scanner/types.ts similarity index 100% rename from packages/backend-plugin-manager/src/scanner/types.ts rename to packages/backend-dynamic-feature-service/src/scanner/types.ts diff --git a/packages/backend-next/CHANGELOG.md b/packages/backend-next/CHANGELOG.md index 5d035799c5..25474b6c54 100644 --- a/packages/backend-next/CHANGELOG.md +++ b/packages/backend-next/CHANGELOG.md @@ -1,5 +1,373 @@ # example-backend-next +## 0.0.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops-backend@0.5.2-next.0 + - @backstage/plugin-kubernetes-backend@0.14.2-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/plugin-search-backend@1.5.0-next.0 + - @backstage/plugin-todo-backend@0.3.8-next.0 + - @backstage/plugin-scaffolder-backend@1.21.0-next.0 + - @backstage/plugin-app-backend@0.3.58-next.0 + - @backstage/backend-defaults@0.2.10-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-badges-backend@0.3.7-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.27-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.7-next.0 + - @backstage/plugin-linguist-backend@0.5.7-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.7-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/plugin-playlist-backend@0.3.14-next.0 + - @backstage/plugin-proxy-backend@0.4.8-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.14-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.14-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.0 + - @backstage/plugin-sonarqube-backend@0.2.12-next.0 + - @backstage/plugin-techdocs-backend@1.9.3-next.0 + - @backstage/plugin-adr-backend@0.4.7-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.3-next.0 + - @backstage/plugin-devtools-backend@0.2.7-next.0 + - @backstage/plugin-jenkins-backend@0.3.4-next.0 + - @backstage/plugin-lighthouse-backend@0.4.2-next.0 + - @backstage/plugin-nomad-backend@0.1.12-next.0 + - @backstage/plugin-permission-backend@0.5.33-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.0 + - @backstage/plugin-permission-common@0.7.12 + +## 0.0.19 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-sonarqube-backend@0.2.11 + - @backstage/plugin-scaffolder-backend@1.20.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.26 + - @backstage/plugin-search-backend-module-techdocs@0.1.13 + - @backstage/plugin-search-backend-module-catalog@0.1.13 + - @backstage/plugin-search-backend-module-explore@0.1.13 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-defaults@0.2.9 + - @backstage/plugin-azure-devops-backend@0.5.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2 + - @backstage/plugin-entity-feedback-backend@0.2.6 + - @backstage/plugin-devtools-backend@0.2.6 + - @backstage/plugin-linguist-backend@0.5.6 + - @backstage/plugin-playlist-backend@0.3.13 + - @backstage/plugin-techdocs-backend@1.9.2 + - @backstage/plugin-jenkins-backend@0.3.3 + - @backstage/plugin-badges-backend@0.3.6 + - @backstage/plugin-search-backend@1.4.9 + - @backstage/plugin-nomad-backend@0.1.11 + - @backstage/plugin-todo-backend@0.3.7 + - @backstage/plugin-adr-backend@0.4.6 + - @backstage/plugin-app-backend@0.3.57 + - @backstage/plugin-permission-backend@0.5.32 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-catalog-backend@1.16.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/plugin-kubernetes-backend@0.14.1 + - @backstage/plugin-lighthouse-backend@0.4.1 + - @backstage/plugin-proxy-backend@0.4.7 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6 + +## 0.0.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-sonarqube-backend@0.2.11-next.2 + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-defaults@0.2.9-next.2 + - @backstage/plugin-adr-backend@0.4.6-next.2 + - @backstage/plugin-app-backend@0.3.57-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-azure-devops-backend@0.5.1-next.2 + - @backstage/plugin-badges-backend@0.3.6-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.2 + - @backstage/plugin-devtools-backend@0.2.6-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.2 + - @backstage/plugin-jenkins-backend@0.3.3-next.2 + - @backstage/plugin-kubernetes-backend@0.14.1-next.2 + - @backstage/plugin-lighthouse-backend@0.4.1-next.2 + - @backstage/plugin-linguist-backend@0.5.6-next.2 + - @backstage/plugin-nomad-backend@0.1.11-next.2 + - @backstage/plugin-permission-backend@0.5.32-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-playlist-backend@0.3.13-next.2 + - @backstage/plugin-proxy-backend@0.4.7-next.2 + - @backstage/plugin-scaffolder-backend@1.19.3-next.2 + - @backstage/plugin-search-backend@1.4.9-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + - @backstage/plugin-techdocs-backend@1.9.2-next.2 + - @backstage/plugin-todo-backend@0.3.7-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.2 + +## 0.0.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app-backend@0.3.57-next.1 + - @backstage/plugin-devtools-backend@0.2.6-next.1 + - @backstage/plugin-proxy-backend@0.4.7-next.1 + - @backstage/backend-defaults@0.2.9-next.1 + - @backstage/plugin-kubernetes-backend@0.14.1-next.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-adr-backend@0.4.6-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-azure-devops-backend@0.5.1-next.1 + - @backstage/plugin-badges-backend@0.3.6-next.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.1 + - @backstage/plugin-jenkins-backend@0.3.3-next.1 + - @backstage/plugin-lighthouse-backend@0.4.1-next.1 + - @backstage/plugin-linguist-backend@0.5.6-next.1 + - @backstage/plugin-nomad-backend@0.1.11-next.1 + - @backstage/plugin-permission-backend@0.5.32-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-playlist-backend@0.3.13-next.1 + - @backstage/plugin-scaffolder-backend@1.19.3-next.1 + - @backstage/plugin-search-backend@1.4.9-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/plugin-sonarqube-backend@0.2.11-next.1 + - @backstage/plugin-techdocs-backend@1.9.2-next.1 + - @backstage/plugin-todo-backend@0.3.7-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.1 + - @backstage/plugin-permission-common@0.7.11 + +## 0.0.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.19.3-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.0 + - @backstage/plugin-azure-devops-backend@0.5.1-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.6-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.2-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.0 + - @backstage/plugin-devtools-backend@0.2.6-next.0 + - @backstage/plugin-linguist-backend@0.5.6-next.0 + - @backstage/plugin-playlist-backend@0.3.13-next.0 + - @backstage/plugin-techdocs-backend@1.9.2-next.0 + - @backstage/plugin-jenkins-backend@0.3.3-next.0 + - @backstage/plugin-badges-backend@0.3.6-next.0 + - @backstage/plugin-search-backend@1.4.9-next.0 + - @backstage/plugin-nomad-backend@0.1.11-next.0 + - @backstage/plugin-todo-backend@0.3.7-next.0 + - @backstage/plugin-adr-backend@0.4.6-next.0 + - @backstage/plugin-app-backend@0.3.57-next.0 + - @backstage/backend-defaults@0.2.9-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.26-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.0 + - @backstage/plugin-kubernetes-backend@0.14.1-next.0 + - @backstage/plugin-lighthouse-backend@0.4.1-next.0 + - @backstage/plugin-permission-backend@0.5.32-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-proxy-backend@0.4.7-next.0 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-sonarqube-backend@0.2.11-next.0 + +## 0.0.18 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1 + - @backstage/plugin-techdocs-backend@1.9.1 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/plugin-azure-devops-backend@0.5.0 + - @backstage/plugin-scaffolder-backend@1.19.2 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-lighthouse-backend@0.4.0 + - @backstage/plugin-kubernetes-backend@0.14.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-backend@0.5.31 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-playlist-backend@0.3.12 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/plugin-search-backend@1.4.8 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5 + - @backstage/plugin-search-backend-module-techdocs@0.1.12 + - @backstage/plugin-search-backend-module-catalog@0.1.12 + - @backstage/plugin-search-backend-module-explore@0.1.12 + - @backstage/backend-defaults@0.2.8 + - @backstage/plugin-adr-backend@0.4.5 + - @backstage/plugin-app-backend@0.3.56 + - @backstage/plugin-badges-backend@0.3.5 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25 + - @backstage/plugin-devtools-backend@0.2.5 + - @backstage/plugin-entity-feedback-backend@0.2.5 + - @backstage/plugin-jenkins-backend@0.3.2 + - @backstage/plugin-linguist-backend@0.5.5 + - @backstage/plugin-nomad-backend@0.1.10 + - @backstage/plugin-proxy-backend@0.4.6 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/plugin-sonarqube-backend@0.2.10 + - @backstage/plugin-todo-backend@0.3.6 + - @backstage/backend-plugin-api@0.6.8 + +## 0.0.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops-backend@0.5.0-next.3 + - @backstage/plugin-scaffolder-backend@1.19.2-next.3 + - @backstage/backend-defaults@0.2.8-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/plugin-adr-backend@0.4.5-next.3 + - @backstage/plugin-app-backend@0.3.56-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-badges-backend@0.3.5-next.3 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.3 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.3 + - @backstage/plugin-devtools-backend@0.2.5-next.3 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.3 + - @backstage/plugin-jenkins-backend@0.3.2-next.3 + - @backstage/plugin-kubernetes-backend@0.14.0-next.3 + - @backstage/plugin-lighthouse-backend@0.4.0-next.3 + - @backstage/plugin-linguist-backend@0.5.5-next.3 + - @backstage/plugin-nomad-backend@0.1.10-next.3 + - @backstage/plugin-permission-backend@0.5.31-next.3 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-playlist-backend@0.3.12-next.3 + - @backstage/plugin-proxy-backend@0.4.6-next.3 + - @backstage/plugin-search-backend@1.4.8-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.3 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.3 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.3 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-sonarqube-backend@0.2.10-next.3 + - @backstage/plugin-techdocs-backend@1.9.1-next.3 + - @backstage/plugin-todo-backend@0.3.6-next.3 + +## 0.0.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/plugin-lighthouse-backend@0.4.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.2 + - @backstage/backend-defaults@0.2.8-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/plugin-adr-backend@0.4.5-next.2 + - @backstage/plugin-app-backend@0.3.56-next.2 + - @backstage/plugin-azure-devops-backend@0.5.0-next.2 + - @backstage/plugin-badges-backend@0.3.5-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.2 + - @backstage/plugin-devtools-backend@0.2.5-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.2 + - @backstage/plugin-jenkins-backend@0.3.2-next.2 + - @backstage/plugin-kubernetes-backend@0.14.0-next.2 + - @backstage/plugin-linguist-backend@0.5.5-next.2 + - @backstage/plugin-nomad-backend@0.1.10-next.2 + - @backstage/plugin-permission-backend@0.5.31-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-playlist-backend@0.3.12-next.2 + - @backstage/plugin-proxy-backend@0.4.6-next.2 + - @backstage/plugin-scaffolder-backend@1.19.2-next.2 + - @backstage/plugin-search-backend@1.4.8-next.2 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-sonarqube-backend@0.2.10-next.2 + - @backstage/plugin-techdocs-backend@1.9.1-next.2 + - @backstage/plugin-todo-backend@0.3.6-next.2 + +## 0.0.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.1.1-next.1 + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/plugin-azure-devops-backend@0.5.0-next.1 + - @backstage/plugin-kubernetes-backend@0.14.0-next.1 + - @backstage/backend-defaults@0.2.8-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/plugin-adr-backend@0.4.5-next.1 + - @backstage/plugin-app-backend@0.3.56-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-badges-backend@0.3.5-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.25-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.1 + - @backstage/plugin-devtools-backend@0.2.5-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.1 + - @backstage/plugin-jenkins-backend@0.3.2-next.1 + - @backstage/plugin-lighthouse-backend@0.3.5-next.1 + - @backstage/plugin-linguist-backend@0.5.5-next.1 + - @backstage/plugin-nomad-backend@0.1.10-next.1 + - @backstage/plugin-permission-backend@0.5.31-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.5-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-playlist-backend@0.3.12-next.1 + - @backstage/plugin-proxy-backend@0.4.6-next.1 + - @backstage/plugin-scaffolder-backend@1.19.2-next.1 + - @backstage/plugin-search-backend@1.4.8-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.1 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-sonarqube-backend@0.2.10-next.1 + - @backstage/plugin-techdocs-backend@1.9.1-next.1 + - @backstage/plugin-todo-backend@0.3.6-next.1 + ## 0.0.18-next.0 ### Patch Changes diff --git a/packages/backend-next/README.md b/packages/backend-next/README.md index 94fc0767c4..4634e5673f 100644 --- a/packages/backend-next/README.md +++ b/packages/backend-next/README.md @@ -1,5 +1,5 @@ # example-backend-next -This is an example backend for the new **EXPERIMENTAL** Backstage backend system. +This is an example backend for [the new Backstage backend system](https://backstage.io/docs/backend-system/). Do not use this in your own projects. diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json index 2d53421dfc..d230227889 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-next/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-next", - "version": "0.0.18-next.0", + "version": "0.0.20-next.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index 39c12d507f..0b21b9aff3 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,84 @@ # @backstage/backend-openapi-utils +## 0.1.3-next.0 + +### Patch Changes + +- 2067689: Internal updates due to `json-schema-to-ts` +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/errors@1.2.3 + +## 0.1.2 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/errors@1.2.3 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + +## 0.1.2-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/errors@1.2.3 + +## 0.1.1 + +### Patch Changes + +- aaa6fb3: Minor updates for TypeScript 5.2.2+ compatibility +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + ## 0.1.1-next.0 ### Patch Changes diff --git a/packages/backend-openapi-utils/api-report.md b/packages/backend-openapi-utils/api-report.md index 0d78ec9671..8e702b8c23 100644 --- a/packages/backend-openapi-utils/api-report.md +++ b/packages/backend-openapi-utils/api-report.md @@ -7,7 +7,7 @@ import type { ContentObject } from 'openapi3-ts'; import type core from 'express-serve-static-core'; import { Express as Express_2 } from 'express'; import { FromSchema } from 'json-schema-to-ts'; -import { JSONSchema7 } from 'json-schema-to-ts'; +import { JSONSchema } from 'json-schema-to-ts'; import { middleware } from 'express-openapi-validator'; import type { OpenAPIObject } from 'openapi3-ts'; import type { ParameterObject } from 'openapi3-ts'; @@ -62,7 +62,7 @@ type ComponentTypes = Extract< // @public (undocumented) type ConvertAll> = { - [Index in keyof T]: T[Index] extends JSONSchema7 + [Index in keyof T]: T[Index] extends JSONSchema ? FromSchema : T[Index]; } & { @@ -463,7 +463,7 @@ type ParameterSchema< Schema extends ImmutableParameterObject['schema'], > = SchemaRef extends infer R ? R extends ImmutableSchemaObject - ? R extends JSONSchema7 + ? R extends JSONSchema ? FromSchema : never : never diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index e3d57c8f27..a9ce8c56e2 100644 --- a/packages/backend-openapi-utils/package.json +++ b/packages/backend-openapi-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-openapi-utils", "description": "OpenAPI typescript support.", - "version": "0.1.1-next.0", + "version": "0.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -38,7 +38,6 @@ ], "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", diff --git a/packages/backend-openapi-utils/src/types/common.ts b/packages/backend-openapi-utils/src/types/common.ts index f9dedf2f84..a43ad4449f 100644 --- a/packages/backend-openapi-utils/src/types/common.ts +++ b/packages/backend-openapi-utils/src/types/common.ts @@ -18,7 +18,7 @@ * Pulled from https://github.com/varanauskas/oatx. */ -import { FromSchema, JSONSchema7 } from 'json-schema-to-ts'; +import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { ImmutableContentObject, ImmutableOpenAPIObject, @@ -221,7 +221,7 @@ export type TuplifyUnion< * @public */ export type ConvertAll> = { - [Index in keyof T]: T[Index] extends JSONSchema7 + [Index in keyof T]: T[Index] extends JSONSchema ? FromSchema : T[Index]; } & { length: T['length'] }; diff --git a/packages/backend-openapi-utils/src/types/params.ts b/packages/backend-openapi-utils/src/types/params.ts index 208cbd4e9a..969657bac7 100644 --- a/packages/backend-openapi-utils/src/types/params.ts +++ b/packages/backend-openapi-utils/src/types/params.ts @@ -36,7 +36,7 @@ import { SchemaRef, ValueOf, } from './common'; -import { FromSchema, JSONSchema7 } from 'json-schema-to-ts'; +import { FromSchema, JSONSchema } from 'json-schema-to-ts'; /** * @public @@ -96,7 +96,7 @@ export type ParameterSchema< Schema extends ImmutableParameterObject['schema'], > = SchemaRef extends infer R ? R extends ImmutableSchemaObject - ? R extends JSONSchema7 + ? R extends JSONSchema ? FromSchema : never : never diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index 98d025ca68..aa8a153f41 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,103 @@ # @backstage/backend-plugin-api +## 0.6.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + +## 0.6.9 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 0.6.9-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.6.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.11 + +## 0.6.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + +## 0.6.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 0.6.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + +## 0.6.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + +## 0.6.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + ## 0.6.8-next.0 ### Patch Changes diff --git a/packages/backend-plugin-api/README.md b/packages/backend-plugin-api/README.md index a17c8715a7..b827ecc50c 100644 --- a/packages/backend-plugin-api/README.md +++ b/packages/backend-plugin-api/README.md @@ -1,8 +1,6 @@ # @backstage/backend-plugin-api -**This package is EXPERIMENTAL, but we encourage use of it to add support for the new backend system in your own plugins** - -This package provides the core API used by Backstage backend plugins and modules. +This package provides the framework API used by Backstage backend plugins and modules. ## Installation diff --git a/packages/backend-plugin-api/alpha-api-report.md b/packages/backend-plugin-api/api-report-alpha.md similarity index 100% rename from packages/backend-plugin-api/alpha-api-report.md rename to packages/backend-plugin-api/api-report-alpha.md diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 81a4694934..2d099e7383 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-plugin-api", "description": "Core API used by Backstage backend plugins", - "version": "0.6.8-next.0", + "version": "0.6.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 8635aa3e03..654d092591 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,94 @@ # @backstage/backend-tasks +## 0.5.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + +## 0.5.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.13 + +### Patch Changes + +- d8f488a: Allow tasks to run more often that the default work check interval, which is 5 seconds. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + ## 0.5.13-next.0 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index cd71be4190..12ed0dc414 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-tasks", "description": "Common distributed task management library for Backstage backends", - "version": "0.5.13-next.0", + "version": "0.5.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { @@ -44,7 +44,7 @@ "luxon": "^3.0.0", "uuid": "^8.0.0", "winston": "^3.2.1", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/packages/backend-tasks/src/migrations.test.ts b/packages/backend-tasks/src/migrations.test.ts index 1d701c971d..82208da975 100644 --- a/packages/backend-tasks/src/migrations.test.ts +++ b/packages/backend-tasks/src/migrations.test.ts @@ -42,9 +42,7 @@ async function migrateUntilBefore(knex: Knex, target: string): Promise { jest.setTimeout(60_000); describe('migrations', () => { - const databases = TestDatabases.create({ - ids: ['POSTGRES_13', 'POSTGRES_9', 'MYSQL_8', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); it.each(databases.eachSupportedId())( '20210928160613_init.js, %p', diff --git a/packages/backend-tasks/src/setupTests.ts b/packages/backend-tasks/src/setupTests.ts index 5245d37c26..76619a2542 100644 --- a/packages/backend-tasks/src/setupTests.ts +++ b/packages/backend-tasks/src/setupTests.ts @@ -14,7 +14,12 @@ * limitations under the License. */ +import { TestDatabases } from '@backstage/backend-test-utils'; import { Settings } from 'luxon'; // TS still thinks that methods can return null / placeholders, but we still want to throw as soon as possible when things go wrong Settings.throwOnInvalid = true; + +TestDatabases.setDefaults({ + ids: ['MYSQL_8', 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'], +}); diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts index 6247755bbd..4b40791cb5 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts @@ -36,7 +36,7 @@ jest.setTimeout(60_000); describe('PluginTaskManagerImpl', () => { const databases = TestDatabases.create({ - ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], + ids: ['POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'], }); beforeAll(async () => { diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.test.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.test.ts index 50bea27d27..1f0363625e 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.test.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.test.ts @@ -40,8 +40,8 @@ describe('PluginTaskSchedulerJanitor', () => { const databases = TestDatabases.create({ ids: [ /* 'MYSQL_8' not supported yet */ - 'POSTGRES_13', - 'POSTGRES_9', + 'POSTGRES_16', + 'POSTGRES_12', 'SQLITE_3', 'MYSQL_8', ], diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.test.ts b/packages/backend-tasks/src/tasks/TaskScheduler.test.ts index 8e419437bc..d286eaee38 100644 --- a/packages/backend-tasks/src/tasks/TaskScheduler.test.ts +++ b/packages/backend-tasks/src/tasks/TaskScheduler.test.ts @@ -25,9 +25,7 @@ jest.setTimeout(60_000); describe('TaskScheduler', () => { const logger = getVoidLogger(); - const databases = TestDatabases.create({ - ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3', 'MYSQL_8'], - }); + const databases = TestDatabases.create(); const testScopedSignal = createTestScopedSignal(); async function createDatabase( diff --git a/packages/backend-tasks/src/tasks/TaskWorker.test.ts b/packages/backend-tasks/src/tasks/TaskWorker.test.ts index 45eb7d1003..a2ecdaa779 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.test.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.test.ts @@ -28,9 +28,7 @@ jest.setTimeout(60_000); describe('TaskWorker', () => { const logger = getVoidLogger(); - const databases = TestDatabases.create({ - ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3', 'MYSQL_8'], - }); + const databases = TestDatabases.create(); const testScopedSignal = createTestScopedSignal(); beforeEach(() => { diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 4931fd8b2f..4b369454a0 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,129 @@ # @backstage/backend-test-utils +## 0.3.0-next.0 + +### Minor Changes + +- e85aa98: drop databases after unit tests if the database instance is not running in docker + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-app-api@0.5.11-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-app-api@0.5.10 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-app-api@0.5.10-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.10-next.1 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-app-api@0.5.10-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.2.9 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- b7de76a: Added support for PostgreSQL versions 15 and 16 + + Also introduced a new `setDefaults(options: { ids?: TestDatabaseId[] })` static method that can be added to the `setupTests.ts` file to define the default database ids you want to use throughout your package. Usage would look like this: `TestDatabases.setDefaults({ ids: ['POSTGRES_12','POSTGRES_16'] })` and would result in PostgreSQL versions 12 and 16 being used for your tests. + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-app-api@0.5.9 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-app-api@0.5.9-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.2.9-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-app-api@0.5.9-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.5.9-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.2.9-next.0 ### Patch Changes diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index ac012e2f69..204e5dc84a 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -307,6 +307,8 @@ export interface TestBackendOptions { // @public export type TestDatabaseId = + | 'POSTGRES_16' + | 'POSTGRES_15' | 'POSTGRES_14' | 'POSTGRES_13' | 'POSTGRES_12' @@ -325,6 +327,8 @@ export class TestDatabases { eachSupportedId(): [TestDatabaseId][]; init(id: TestDatabaseId): Promise; // (undocumented) + static setDefaults(options: { ids?: TestDatabaseId[] }): void; + // (undocumented) supports(id: TestDatabaseId): boolean; } ``` diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index f449be08ed..2e517d542f 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-test-utils", "description": "Test helpers library for Backstage backends", - "version": "0.2.9-next.0", + "version": "0.3.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-test-utils/src/database/TestDatabases.test.ts b/packages/backend-test-utils/src/database/TestDatabases.test.ts index d766d38815..64e4d76443 100644 --- a/packages/backend-test-utils/src/database/TestDatabases.test.ts +++ b/packages/backend-test-utils/src/database/TestDatabases.test.ts @@ -59,6 +59,68 @@ describe('TestDatabases', () => { describe('each connect', () => { const dbs = TestDatabases.create(); + itIfDocker( + 'obeys a provided connection string for postgres 16', + async () => { + const { host, port, user, password, stop } = + await startPostgresContainer('postgres:16'); + + try { + // Leave a mark + process.env.BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING = `postgresql://${user}:${password}@${host}:${port}`; + const input = await dbs.init('POSTGRES_16'); + await input.schema.createTable('a', table => + table.string('x').primary(), + ); + await input.insert({ x: 'y' }).into('a'); + + // Look for the mark + const database = input.client.config.connection.database; + const output = knexFactory({ + client: 'pg', + connection: { host, port, user, password, database }, + }); + // eslint-disable-next-line jest/no-standalone-expect + await expect(output.select('x').from('a')).resolves.toEqual([ + { x: 'y' }, + ]); + } finally { + await stop(); + } + }, + ); + + itIfDocker( + 'obeys a provided connection string for postgres 15', + async () => { + const { host, port, user, password, stop } = + await startPostgresContainer('postgres:15'); + + try { + // Leave a mark + process.env.BACKSTAGE_TEST_DATABASE_POSTGRES15_CONNECTION_STRING = `postgresql://${user}:${password}@${host}:${port}`; + const input = await dbs.init('POSTGRES_15'); + await input.schema.createTable('a', table => + table.string('x').primary(), + ); + await input.insert({ x: 'y' }).into('a'); + + // Look for the mark + const database = input.client.config.connection.database; + const output = knexFactory({ + client: 'pg', + connection: { host, port, user, password, database }, + }); + // eslint-disable-next-line jest/no-standalone-expect + await expect(output.select('x').from('a')).resolves.toEqual([ + { x: 'y' }, + ]); + } finally { + await stop(); + } + }, + ); + itIfDocker( 'obeys a provided connection string for postgres 14', async () => { diff --git a/packages/backend-test-utils/src/database/TestDatabases.ts b/packages/backend-test-utils/src/database/TestDatabases.ts index 45d2899dbb..4ea8fe05d7 100644 --- a/packages/backend-test-utils/src/database/TestDatabases.ts +++ b/packages/backend-test-utils/src/database/TestDatabases.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DatabaseManager } from '@backstage/backend-common'; +import { DatabaseManager, dropDatabase } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { randomBytes } from 'crypto'; import { Knex } from 'knex'; @@ -44,6 +44,7 @@ const LARGER_POOL_CONFIG = { export class TestDatabases { private readonly instanceById: Map; private readonly supportedIds: TestDatabaseId[]; + private static defaultIds?: TestDatabaseId[]; /** * Creates an empty `TestDatabases` instance, and sets up Jest to clean up @@ -60,18 +61,19 @@ export class TestDatabases { ids?: TestDatabaseId[]; disableDocker?: boolean; }): TestDatabases { - const defaultOptions = { - ids: Object.keys(allDatabases) as TestDatabaseId[], - disableDocker: isDockerDisabledForTests(), - }; + const ids = options?.ids; + const disableDocker = options?.disableDocker ?? isDockerDisabledForTests(); - const { ids, disableDocker } = Object.assign( - {}, - defaultOptions, - options ?? {}, - ); + let testDatabaseIds: TestDatabaseId[]; + if (ids) { + testDatabaseIds = ids; + } else if (TestDatabases.defaultIds) { + testDatabaseIds = TestDatabases.defaultIds; + } else { + testDatabaseIds = Object.keys(allDatabases) as TestDatabaseId[]; + } - const supportedIds = ids.filter(id => { + const supportedIds = testDatabaseIds.filter(id => { const properties = allDatabases[id]; if (!properties) { return false; @@ -107,6 +109,10 @@ export class TestDatabases { return databases; } + static setDefaults(options: { ids?: TestDatabaseId[] }) { + TestDatabases.defaultIds = options.ids; + } + private constructor(supportedIds: TestDatabaseId[]) { this.instanceById = new Map(); this.supportedIds = supportedIds; @@ -151,11 +157,13 @@ export class TestDatabases { } // Ensure that a unique logical database is created in the instance + const databaseName = `db${randomBytes(16).toString('hex')}`; const connection = await instance.databaseManager - .forPlugin(`db${randomBytes(16).toString('hex')}`) + .forPlugin(databaseName) .getClient(); instance.connections.push(connection); + instance.databaseNames.push(databaseName); return connection; } @@ -167,21 +175,30 @@ export class TestDatabases { if (envVarName) { const connectionString = process.env[envVarName]; if (connectionString) { - const databaseManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - knexConfig: properties.driver.includes('sqlite') - ? {} - : LARGER_POOL_CONFIG, - client: properties.driver, - connection: connectionString, - }, + const config = new ConfigReader({ + backend: { + database: { + knexConfig: properties.driver.includes('sqlite') + ? {} + : LARGER_POOL_CONFIG, + client: properties.driver, + connection: connectionString, }, - }), - ); + }, + }); + const databaseManager = DatabaseManager.fromConfig(config); + const databaseNames: Array = []; return { + dropDatabases: async () => { + await dropDatabase( + config.getConfig('backend.database'), + ...databaseNames.map( + databaseName => `backstage_plugin_${databaseName}`, + ), + ); + }, databaseManager, + databaseNames, connections: [], }; } @@ -220,10 +237,10 @@ export class TestDatabases { }, }), ); - return { stopContainer: stop, databaseManager, + databaseNames: [], connections: [], }; } @@ -250,6 +267,7 @@ export class TestDatabases { return { stopContainer: stop, databaseManager, + databaseNames: [], connections: [], }; } @@ -267,9 +285,9 @@ export class TestDatabases { }, }), ); - return { databaseManager, + databaseNames: [], connections: [], }; } @@ -278,7 +296,12 @@ export class TestDatabases { const instances = [...this.instanceById.values()]; this.instanceById.clear(); - for (const { stopContainer, connections, databaseManager } of instances) { + for (const { + stopContainer, + dropDatabases, + connections, + databaseManager, + } of instances) { for (const connection of connections) { try { await connection.destroy(); @@ -290,6 +313,15 @@ export class TestDatabases { } } + // If the database is not running in docker then drop the databases + try { + await dropDatabases?.(); + } catch (error) { + console.warn(`TestDatabases: Failed to drop databases`, { + error, + }); + } + try { await stopContainer?.(); } catch (error) { diff --git a/packages/backend-test-utils/src/database/types.ts b/packages/backend-test-utils/src/database/types.ts index a7bfdeb12d..77a0c4e960 100644 --- a/packages/backend-test-utils/src/database/types.ts +++ b/packages/backend-test-utils/src/database/types.ts @@ -24,6 +24,8 @@ import { getDockerImageForName } from '../util/getDockerImageForName'; * @public */ export type TestDatabaseId = + | 'POSTGRES_16' + | 'POSTGRES_15' | 'POSTGRES_14' | 'POSTGRES_13' | 'POSTGRES_12' @@ -41,12 +43,28 @@ export type TestDatabaseProperties = { export type Instance = { stopContainer?: () => Promise; + dropDatabases?: () => Promise; databaseManager: DatabaseManager; connections: Array; + databaseNames: Array; }; export const allDatabases: Record = Object.freeze({ + POSTGRES_16: { + name: 'Postgres 16.x', + driver: 'pg', + dockerImageName: getDockerImageForName('postgres:16'), + connectionStringEnvironmentVariableName: + 'BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING', + }, + POSTGRES_15: { + name: 'Postgres 15.x', + driver: 'pg', + dockerImageName: getDockerImageForName('postgres:15'), + connectionStringEnvironmentVariableName: + 'BACKSTAGE_TEST_DATABASE_POSTGRES15_CONNECTION_STRING', + }, POSTGRES_14: { name: 'Postgres 14.x', driver: 'pg', 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 b127712927..968237f11d 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts @@ -33,8 +33,8 @@ beforeAll(async () => { await startTestBackend({ features: [ createBackendModule({ - moduleId: 'test.module', pluginId: 'test', + moduleId: 'test-module', register(env) { env.registerInit({ deps: { lifecycle: coreServices.lifecycle }, @@ -128,8 +128,8 @@ describe('TestBackend', () => { }); const testModule = createBackendModule({ - moduleId: 'test.module', pluginId: 'test', + moduleId: 'test-module', register(env) { env.registerInit({ deps: { @@ -153,8 +153,8 @@ describe('TestBackend', () => { const shutdownSpy = jest.fn(); const testModule = createBackendModule({ - moduleId: 'test.module', pluginId: 'test', + moduleId: 'test-module', register(env) { env.registerInit({ deps: { @@ -307,7 +307,7 @@ describe('TestBackend', () => { ], }), ).rejects.toThrow( - "Extension point registered for plugin 'testA' may not be used by module for plugin 'testB'", + "Illegal dependency: Module 'test' for plugin 'testB' attempted to depend on extension point 'a' for plugin 'testA'. Extension points can only be used within their plugin's scope.", ); }); diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 21ae6159c9..14e8920390 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -156,7 +156,7 @@ function createExtensionPointTestModules( modules.push( createBackendModule({ pluginId, - moduleId: 'testExtensionPointRegistration', + moduleId: 'test-extension-point-registration', register(reg) { for (const id of pluginExtensionPointIds) { const tuple = extensionPointMap.get(id)!; diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 11ddf672a8..4b90e2631e 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,523 @@ # example-backend +## 0.2.92-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-rails@0.4.27-next.0 + - @backstage/plugin-azure-devops-backend@0.5.2-next.0 + - @backstage/plugin-explore-backend@0.0.20-next.0 + - @backstage/plugin-auth-backend@0.20.4-next.0 + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-kubernetes-backend@0.14.2-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.11-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/plugin-search-backend@1.5.0-next.0 + - @backstage/plugin-todo-backend@0.3.8-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-signals-backend@0.0.1-next.0 + - @backstage/plugin-signals-node@0.0.1-next.0 + - @backstage/plugin-scaffolder-backend@1.21.0-next.0 + - @backstage/plugin-app-backend@0.3.58-next.0 + - example-app@0.2.92-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-badges-backend@0.3.7-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.7-next.0 + - @backstage/plugin-events-backend@0.2.19-next.0 + - @backstage/plugin-linguist-backend@0.5.7-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/plugin-playlist-backend@0.3.14-next.0 + - @backstage/plugin-proxy-backend@0.4.8-next.0 + - @backstage/plugin-rollbar-backend@0.1.55-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.14-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.14-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.19-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.14-next.0 + - @backstage/plugin-tech-insights-backend@0.5.24-next.0 + - @backstage/plugin-techdocs-backend@1.9.3-next.0 + - @backstage/plugin-adr-backend@0.4.7-next.0 + - @backstage/plugin-azure-sites-backend@0.1.20-next.0 + - @backstage/plugin-code-coverage-backend@0.2.24-next.0 + - @backstage/plugin-devtools-backend@0.2.7-next.0 + - @backstage/plugin-jenkins-backend@0.3.4-next.0 + - @backstage/plugin-kafka-backend@0.3.8-next.0 + - @backstage/plugin-lighthouse-backend@0.4.2-next.0 + - @backstage/plugin-nomad-backend@0.1.12-next.0 + - @backstage/plugin-permission-backend@0.5.33-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.13-next.0 + - @backstage/plugin-search-backend-node@1.2.14-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.42-next.0 + - @backstage/plugin-tech-insights-node@0.4.16-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.7-next.0 + - @backstage/plugin-events-node@0.2.19-next.0 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.91 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.3 + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-backend@1.20.0 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10 + - @backstage/plugin-events-backend@0.2.18 + - @backstage/plugin-search-backend-module-techdocs@0.1.13 + - @backstage/plugin-search-backend-module-catalog@0.1.13 + - @backstage/plugin-search-backend-module-explore@0.1.13 + - @backstage/plugin-azure-devops-backend@0.5.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41 + - @backstage/plugin-entity-feedback-backend@0.2.6 + - @backstage/plugin-code-coverage-backend@0.2.23 + - @backstage/plugin-azure-sites-backend@0.1.19 + - @backstage/plugin-tech-insights-node@0.4.15 + - @backstage/plugin-devtools-backend@0.2.6 + - @backstage/plugin-linguist-backend@0.5.6 + - @backstage/plugin-playlist-backend@0.3.13 + - @backstage/plugin-techdocs-backend@1.9.2 + - @backstage/plugin-explore-backend@0.0.19 + - @backstage/plugin-jenkins-backend@0.3.3 + - @backstage/plugin-badges-backend@0.3.6 + - @backstage/plugin-search-backend@1.4.9 + - @backstage/plugin-kafka-backend@0.3.7 + - @backstage/plugin-nomad-backend@0.1.11 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-todo-backend@0.3.7 + - @backstage/plugin-adr-backend@0.4.6 + - @backstage/plugin-app-backend@0.3.57 + - @backstage/plugin-permission-backend@0.5.32 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-catalog-backend@1.16.1 + - example-app@0.2.91 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/plugin-kubernetes-backend@0.14.1 + - @backstage/plugin-lighthouse-backend@0.4.1 + - @backstage/plugin-proxy-backend@0.4.7 + - @backstage/plugin-rollbar-backend@0.1.54 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.26 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.12 + - @backstage/plugin-search-backend-module-pg@0.5.18 + - @backstage/plugin-search-backend-node@1.2.13 + - @backstage/plugin-tech-insights-backend@0.5.23 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6 + - @backstage/plugin-events-node@0.2.18 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.91-next.2 + +### Patch Changes + +- Updated dependencies + - example-app@0.2.91-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-adr-backend@0.4.6-next.2 + - @backstage/plugin-app-backend@0.3.57-next.2 + - @backstage/plugin-auth-backend@0.20.3-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-azure-devops-backend@0.5.1-next.2 + - @backstage/plugin-badges-backend@0.3.6-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-code-coverage-backend@0.2.23-next.2 + - @backstage/plugin-devtools-backend@0.2.6-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.2 + - @backstage/plugin-events-backend@0.2.18-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/plugin-jenkins-backend@0.3.3-next.2 + - @backstage/plugin-kafka-backend@0.3.7-next.2 + - @backstage/plugin-kubernetes-backend@0.14.1-next.2 + - @backstage/plugin-lighthouse-backend@0.4.1-next.2 + - @backstage/plugin-linguist-backend@0.5.6-next.2 + - @backstage/plugin-nomad-backend@0.1.11-next.2 + - @backstage/plugin-permission-backend@0.5.32-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-playlist-backend@0.3.13-next.2 + - @backstage/plugin-proxy-backend@0.4.7-next.2 + - @backstage/plugin-scaffolder-backend@1.19.3-next.2 + - @backstage/plugin-search-backend@1.4.9-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.18-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.2 + - @backstage/plugin-search-backend-node@1.2.13-next.2 + - @backstage/plugin-techdocs-backend@1.9.2-next.2 + - @backstage/plugin-todo-backend@0.3.7-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/plugin-azure-sites-backend@0.1.19-next.2 + - @backstage/plugin-explore-backend@0.0.19-next.2 + - @backstage/plugin-rollbar-backend@0.1.54-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.2 + - @backstage/plugin-tech-insights-backend@0.5.23-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.2 + - @backstage/plugin-tech-insights-node@0.4.15-next.2 + +## 0.2.91-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.1 + - example-app@0.2.91-next.1 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-app-backend@0.3.57-next.1 + - @backstage/plugin-devtools-backend@0.2.6-next.1 + - @backstage/plugin-proxy-backend@0.4.7-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-kubernetes-backend@0.14.1-next.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-adr-backend@0.4.6-next.1 + - @backstage/plugin-auth-backend@0.20.3-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-azure-devops-backend@0.5.1-next.1 + - @backstage/plugin-azure-sites-backend@0.1.19-next.1 + - @backstage/plugin-badges-backend@0.3.6-next.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/plugin-code-coverage-backend@0.2.23-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.1 + - @backstage/plugin-events-backend@0.2.18-next.1 + - @backstage/plugin-explore-backend@0.0.19-next.1 + - @backstage/plugin-jenkins-backend@0.3.3-next.1 + - @backstage/plugin-kafka-backend@0.3.7-next.1 + - @backstage/plugin-lighthouse-backend@0.4.1-next.1 + - @backstage/plugin-linguist-backend@0.5.6-next.1 + - @backstage/plugin-nomad-backend@0.1.11-next.1 + - @backstage/plugin-permission-backend@0.5.32-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-playlist-backend@0.3.13-next.1 + - @backstage/plugin-rollbar-backend@0.1.54-next.1 + - @backstage/plugin-scaffolder-backend@1.19.3-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.1 + - @backstage/plugin-search-backend@1.4.9-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.18-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.1 + - @backstage/plugin-search-backend-node@1.2.13-next.1 + - @backstage/plugin-tech-insights-backend@0.5.23-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.1 + - @backstage/plugin-tech-insights-node@0.4.15-next.1 + - @backstage/plugin-techdocs-backend@1.9.2-next.1 + - @backstage/plugin-todo-backend@0.3.7-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-search-common@1.2.9 + +## 0.2.91-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.3-next.0 + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-scaffolder-backend@1.19.3-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.13-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.0 + - @backstage/plugin-azure-devops-backend@0.5.1-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.10-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.41-next.0 + - @backstage/plugin-entity-feedback-backend@0.2.6-next.0 + - @backstage/plugin-code-coverage-backend@0.2.23-next.0 + - @backstage/plugin-azure-sites-backend@0.1.19-next.0 + - @backstage/plugin-tech-insights-node@0.4.15-next.0 + - @backstage/plugin-devtools-backend@0.2.6-next.0 + - @backstage/plugin-linguist-backend@0.5.6-next.0 + - @backstage/plugin-playlist-backend@0.3.13-next.0 + - @backstage/plugin-techdocs-backend@1.9.2-next.0 + - @backstage/plugin-explore-backend@0.0.19-next.0 + - @backstage/plugin-jenkins-backend@0.3.3-next.0 + - @backstage/plugin-badges-backend@0.3.6-next.0 + - @backstage/plugin-search-backend@1.4.9-next.0 + - @backstage/plugin-kafka-backend@0.3.7-next.0 + - @backstage/plugin-nomad-backend@0.1.11-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/plugin-todo-backend@0.3.7-next.0 + - @backstage/plugin-adr-backend@0.4.6-next.0 + - @backstage/plugin-app-backend@0.3.57-next.0 + - example-app@0.2.91-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.6-next.0 + - @backstage/plugin-events-backend@0.2.18-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + - @backstage/plugin-kubernetes-backend@0.14.1-next.0 + - @backstage/plugin-lighthouse-backend@0.4.1-next.0 + - @backstage/plugin-permission-backend@0.5.32-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-proxy-backend@0.4.7-next.0 + - @backstage/plugin-rollbar-backend@0.1.54-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.26-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.12-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.18-next.0 + - @backstage/plugin-search-backend-node@1.2.13-next.0 + - @backstage/plugin-search-common@1.2.9 + - @backstage/plugin-tech-insights-backend@0.5.23-next.0 + +## 0.2.90 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.1 + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-techdocs-backend@1.9.1 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-azure-devops-backend@0.5.0 + - @backstage/plugin-scaffolder-backend@1.19.2 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-lighthouse-backend@0.4.0 + - @backstage/plugin-kubernetes-backend@0.14.0 + - @backstage/integration@1.8.0 + - @backstage/plugin-azure-sites-backend@0.1.18 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-backend@0.5.31 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-playlist-backend@0.3.12 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/plugin-search-backend@1.4.8 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5 + - @backstage/plugin-search-backend-module-techdocs@0.1.12 + - @backstage/plugin-search-backend-module-catalog@0.1.12 + - @backstage/plugin-search-backend-module-explore@0.1.12 + - @backstage/plugin-search-backend-module-pg@0.5.17 + - @backstage/plugin-events-backend@0.2.17 + - example-app@0.2.90 + - @backstage/plugin-adr-backend@0.4.5 + - @backstage/plugin-app-backend@0.3.56 + - @backstage/plugin-badges-backend@0.3.5 + - @backstage/plugin-code-coverage-backend@0.2.22 + - @backstage/plugin-devtools-backend@0.2.5 + - @backstage/plugin-entity-feedback-backend@0.2.5 + - @backstage/plugin-explore-backend@0.0.18 + - @backstage/plugin-jenkins-backend@0.3.2 + - @backstage/plugin-kafka-backend@0.3.6 + - @backstage/plugin-linguist-backend@0.5.5 + - @backstage/plugin-nomad-backend@0.1.10 + - @backstage/plugin-proxy-backend@0.4.6 + - @backstage/plugin-rollbar-backend@0.1.53 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25 + - @backstage/plugin-search-backend-node@1.2.12 + - @backstage/plugin-tech-insights-backend@0.5.22 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40 + - @backstage/plugin-tech-insights-node@0.4.14 + - @backstage/plugin-todo-backend@0.3.6 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17 + - @backstage/plugin-search-common@1.2.9 + +## 0.2.90-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-azure-devops-backend@0.5.0-next.3 + - @backstage/plugin-scaffolder-backend@1.19.2-next.3 + - @backstage/backend-common@0.20.0-next.3 + - example-app@0.2.90-next.4 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-adr-backend@0.4.5-next.3 + - @backstage/plugin-app-backend@0.3.56-next.3 + - @backstage/plugin-auth-backend@0.20.1-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-azure-sites-backend@0.1.18-next.3 + - @backstage/plugin-badges-backend@0.3.5-next.3 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-code-coverage-backend@0.2.22-next.3 + - @backstage/plugin-devtools-backend@0.2.5-next.3 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.3 + - @backstage/plugin-events-backend@0.2.17-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + - @backstage/plugin-explore-backend@0.0.18-next.3 + - @backstage/plugin-jenkins-backend@0.3.2-next.3 + - @backstage/plugin-kafka-backend@0.3.6-next.3 + - @backstage/plugin-kubernetes-backend@0.14.0-next.3 + - @backstage/plugin-lighthouse-backend@0.4.0-next.3 + - @backstage/plugin-linguist-backend@0.5.5-next.3 + - @backstage/plugin-nomad-backend@0.1.10-next.3 + - @backstage/plugin-permission-backend@0.5.31-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-playlist-backend@0.3.12-next.3 + - @backstage/plugin-proxy-backend@0.4.6-next.3 + - @backstage/plugin-rollbar-backend@0.1.53-next.3 + - @backstage/plugin-search-backend@1.4.8-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.3 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.3 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.3 + - @backstage/plugin-search-backend-module-pg@0.5.17-next.3 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.3 + - @backstage/plugin-search-backend-node@1.2.12-next.3 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-tech-insights-backend@0.5.22-next.3 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.3 + - @backstage/plugin-tech-insights-node@0.4.14-next.3 + - @backstage/plugin-techdocs-backend@1.9.1-next.3 + - @backstage/plugin-todo-backend@0.3.6-next.3 + +## 0.2.90-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/plugin-auth-backend@0.20.1-next.2 + - @backstage/plugin-lighthouse-backend@0.4.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.17-next.2 + - @backstage/plugin-events-backend@0.2.17-next.2 + - example-app@0.2.90-next.3 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-adr-backend@0.4.5-next.2 + - @backstage/plugin-app-backend@0.3.56-next.2 + - @backstage/plugin-azure-devops-backend@0.5.0-next.2 + - @backstage/plugin-azure-sites-backend@0.1.18-next.2 + - @backstage/plugin-badges-backend@0.3.5-next.2 + - @backstage/plugin-code-coverage-backend@0.2.22-next.2 + - @backstage/plugin-devtools-backend@0.2.5-next.2 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.2 + - @backstage/plugin-events-node@0.2.17-next.2 + - @backstage/plugin-explore-backend@0.0.18-next.2 + - @backstage/plugin-jenkins-backend@0.3.2-next.2 + - @backstage/plugin-kafka-backend@0.3.6-next.2 + - @backstage/plugin-kubernetes-backend@0.14.0-next.2 + - @backstage/plugin-linguist-backend@0.5.5-next.2 + - @backstage/plugin-nomad-backend@0.1.10-next.2 + - @backstage/plugin-permission-backend@0.5.31-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-playlist-backend@0.3.12-next.2 + - @backstage/plugin-proxy-backend@0.4.6-next.2 + - @backstage/plugin-rollbar-backend@0.1.53-next.2 + - @backstage/plugin-scaffolder-backend@1.19.2-next.2 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.2 + - @backstage/plugin-search-backend@1.4.8-next.2 + - @backstage/plugin-search-backend-node@1.2.12-next.2 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-tech-insights-backend@0.5.22-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.2 + - @backstage/plugin-tech-insights-node@0.4.14-next.2 + - @backstage/plugin-techdocs-backend@1.9.1-next.2 + - @backstage/plugin-todo-backend@0.3.6-next.2 + +## 0.2.90-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.1-next.1 + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/plugin-azure-devops-backend@0.5.0-next.1 + - @backstage/plugin-kubernetes-backend@0.14.0-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-azure-sites-backend@0.1.18-next.1 + - @backstage/backend-common@0.20.0-next.1 + - example-app@0.2.90-next.2 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-adr-backend@0.4.5-next.1 + - @backstage/plugin-app-backend@0.3.56-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-badges-backend@0.3.5-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.3.5-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-code-coverage-backend@0.2.22-next.1 + - @backstage/plugin-devtools-backend@0.2.5-next.1 + - @backstage/plugin-entity-feedback-backend@0.2.5-next.1 + - @backstage/plugin-events-backend@0.2.17-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + - @backstage/plugin-explore-backend@0.0.18-next.1 + - @backstage/plugin-jenkins-backend@0.3.2-next.1 + - @backstage/plugin-kafka-backend@0.3.6-next.1 + - @backstage/plugin-lighthouse-backend@0.3.5-next.1 + - @backstage/plugin-linguist-backend@0.5.5-next.1 + - @backstage/plugin-nomad-backend@0.1.10-next.1 + - @backstage/plugin-permission-backend@0.5.31-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-playlist-backend@0.3.12-next.1 + - @backstage/plugin-proxy-backend@0.4.6-next.1 + - @backstage/plugin-rollbar-backend@0.1.53-next.1 + - @backstage/plugin-scaffolder-backend@1.19.2-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.9-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.25-next.1 + - @backstage/plugin-search-backend@1.4.8-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.3.11-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.17-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.12-next.1 + - @backstage/plugin-search-backend-node@1.2.12-next.1 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-tech-insights-backend@0.5.22-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.40-next.1 + - @backstage/plugin-tech-insights-node@0.4.14-next.1 + - @backstage/plugin-techdocs-backend@1.9.1-next.1 + - @backstage/plugin-todo-backend@0.3.6-next.1 + ## 0.2.90-next.0 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index fc964dfdbc..f76a40a8f0 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.90-next.0", + "version": "0.2.92-next.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -38,6 +38,7 @@ "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-azure-devops-backend": "workspace:^", "@backstage/plugin-azure-sites-backend": "workspace:^", + "@backstage/plugin-azure-sites-common": "workspace:^", "@backstage/plugin-badges-backend": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^", @@ -72,6 +73,8 @@ "@backstage/plugin-search-backend-module-techdocs": "workspace:^", "@backstage/plugin-search-backend-node": "workspace:^", "@backstage/plugin-search-common": "workspace:^", + "@backstage/plugin-signals-backend": "workspace:^", + "@backstage/plugin-signals-node": "workspace:^", "@backstage/plugin-tech-insights-backend": "workspace:^", "@backstage/plugin-tech-insights-backend-module-jsonfc": "workspace:^", "@backstage/plugin-tech-insights-node": "workspace:^", @@ -80,9 +83,9 @@ "@gitbeaker/node": "^35.1.0", "@octokit/rest": "^19.0.3", "@opentelemetry/api": "^1.4.1", - "@opentelemetry/exporter-prometheus": "^0.45.0", + "@opentelemetry/exporter-prometheus": "^0.47.0", "@opentelemetry/sdk-metrics": "^1.13.0", - "azure-devops-node-api": "^11.0.1", + "azure-devops-node-api": "^12.0.0", "better-sqlite3": "^9.0.0", "dockerode": "^3.3.1", "example-app": "link:../app", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 8df01b050e..323fc27005 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -26,21 +26,22 @@ import Router from 'express-promise-router'; import { CacheManager, createServiceBuilder, + DatabaseManager, getRootLogger, + HostDiscovery, loadBackendConfig, notFoundHandler, - DatabaseManager, - HostDiscovery, + ServerTokenManager, UrlReaders, useHotMemoize, - ServerTokenManager, } from '@backstage/backend-common'; import { TaskScheduler } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import healthcheck from './plugins/healthcheck'; -import { metricsInit, metricsHandler } from './metrics'; +import { metricsHandler, metricsInit } from './metrics'; import auth from './plugins/auth'; import azureDevOps from './plugins/azure-devops'; +import azureSites from './plugins/azure-sites'; import catalog from './plugins/catalog'; import codeCoverage from './plugins/codecoverage'; import entityFeedback from './plugins/entityFeedback'; @@ -65,6 +66,7 @@ import lighthouse from './plugins/lighthouse'; import linguist from './plugins/linguist'; import devTools from './plugins/devtools'; import nomad from './plugins/nomad'; +import signals from './plugins/signals'; import { PluginEnvironment } from './types'; import { ServerPermissionClient } from '@backstage/plugin-permission-node'; import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; @@ -72,6 +74,7 @@ import { DefaultEventBroker } from '@backstage/plugin-events-backend'; import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'; import { MeterProvider } from '@opentelemetry/sdk-metrics'; import { metrics } from '@opentelemetry/api'; +import { DefaultSignalService } from '@backstage/plugin-signals-node'; // Expose opentelemetry metrics using a Prometheus exporter on // http://localhost:9464/metrics . See prometheus.yml in packages/backend for @@ -98,6 +101,9 @@ function makeCreateEnv(config: Config) { }); const eventBroker = new DefaultEventBroker(root.child({ type: 'plugin' })); + const signalService = DefaultSignalService.create({ + eventBroker, + }); root.info(`Created UrlReader ${reader}`); @@ -119,6 +125,7 @@ function makeCreateEnv(config: Config) { permissions, scheduler, identity, + signalService, }; }; } @@ -139,6 +146,7 @@ async function main() { const createEnv = makeCreateEnv(config); + const azureSitesEnv = useHotMemoize(module, () => createEnv('azure-sites')); const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck')); const catalogEnv = useHotMemoize(module, () => createEnv('catalog')); const codeCoverageEnv = useHotMemoize(module, () => @@ -172,6 +180,7 @@ async function main() { const linguistEnv = useHotMemoize(module, () => createEnv('linguist')); const devToolsEnv = useHotMemoize(module, () => createEnv('devtools')); const nomadEnv = useHotMemoize(module, () => createEnv('nomad')); + const signalsEnv = useHotMemoize(module, () => createEnv('signals')); const apiRouter = Router(); apiRouter.use('/catalog', await catalog(catalogEnv)); @@ -182,6 +191,7 @@ async function main() { apiRouter.use('/tech-insights', await techInsights(techInsightsEnv)); apiRouter.use('/auth', await auth(authEnv)); apiRouter.use('/azure-devops', await azureDevOps(azureDevOpsEnv)); + apiRouter.use('/azure-sites', await azureSites(azureSitesEnv)); apiRouter.use('/search', await search(searchEnv)); apiRouter.use('/techdocs', await techdocs(techdocsEnv)); apiRouter.use('/todo', await todo(todoEnv)); @@ -198,6 +208,7 @@ async function main() { apiRouter.use('/linguist', await linguist(linguistEnv)); apiRouter.use('/devtools', await devTools(devToolsEnv)); apiRouter.use('/nomad', await nomad(nomadEnv)); + apiRouter.use('/signals', await signals(signalsEnv)); apiRouter.use(notFoundHandler()); await lighthouse(lighthouseEnv); diff --git a/packages/backend/src/plugins/azure-sites.ts b/packages/backend/src/plugins/azure-sites.ts new file mode 100644 index 0000000000..54c7746cb2 --- /dev/null +++ b/packages/backend/src/plugins/azure-sites.ts @@ -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. + */ +import { + createRouter, + AzureSitesApi, +} from '@backstage/plugin-azure-sites-backend'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; +import { CatalogClient } from '@backstage/catalog-client'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + const catalogApi = new CatalogClient({ discoveryApi: env.discovery }); + return await createRouter({ + logger: env.logger, + permissions: env.permissions, + azureSitesApi: AzureSitesApi.fromConfig(env.config), + catalogApi, + }); +} diff --git a/packages/backend/src/plugins/signals.ts b/packages/backend/src/plugins/signals.ts new file mode 100644 index 0000000000..477cea1938 --- /dev/null +++ b/packages/backend/src/plugins/signals.ts @@ -0,0 +1,28 @@ +/* + * 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 { Router } from 'express'; +import { createRouter } from '@backstage/plugin-signals-backend'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + return await createRouter({ + logger: env.logger, + eventBroker: env.eventBroker, + identity: env.identity, + }); +} diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index ab1baf0c95..3dad2f739b 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -27,6 +27,7 @@ import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { EventBroker } from '@backstage/plugin-events-node'; +import { DefaultSignalService } from '@backstage/plugin-signals-node'; export type PluginEnvironment = { logger: Logger; @@ -40,4 +41,5 @@ export type PluginEnvironment = { scheduler: PluginTaskScheduler; identity: IdentityApi; eventBroker: EventBroker; + signalService: DefaultSignalService; }; diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 7a2618f0e0..0d1e91dc99 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,69 @@ # @backstage/catalog-client +## 1.6.0-next.0 + +### Minor Changes + +- 04907c3: Updates the OpenAPI specification title to plugin ID instead of package name. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 1.5.2 + +### Patch Changes + +- 883782e: Fix a bug in `getLocationByRef` that led to invalid backend calls +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 1.5.2-next.0 + +### Patch Changes + +- 883782e: Fix a bug in `getLocationByRef` that led to invalid backend calls +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 1.5.0 + +### Minor Changes + +- 3834067: The internals of `CatalogClient` are now auto-generated using the `backstage-repo-tools schema openapi generate-client` command. + +### Patch Changes + +- 82fa88b: Fixes a bug where some query parameters were double URL encoded. +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 1.5.0-next.1 + +### Patch Changes + +- 82fa88b: Fixes a bug where some query parameters were double URL encoded. +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 1.5.0-next.0 + +### Minor Changes + +- 38340678c3: The internals of `CatalogClient` are now auto-generated using the `backstage-repo-tools schema openapi generate-client` command. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + ## 1.4.6 ### Patch Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 02c30ee19b..da9611e8d4 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-client", "description": "An isomorphic client for the catalog backend", - "version": "1.4.6", + "version": "1.6.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,7 +35,8 @@ "dependencies": { "@backstage/catalog-model": "workspace:^", "@backstage/errors": "workspace:^", - "cross-fetch": "^4.0.0" + "cross-fetch": "^4.0.0", + "uri-template": "^2.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/packages/catalog-client/src/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts index f5c221569d..b8212fde96 100644 --- a/packages/catalog-client/src/CatalogClient.test.ts +++ b/packages/catalog-client/src/CatalogClient.test.ts @@ -86,9 +86,12 @@ describe('CatalogClient', () => { server.use( rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => { - expect(req.url.search).toBe( - '?filter=a=1,b=2,b=3,%C3%B6=%3D&filter=a=2&filter=c', - ); + const queryParams = new URLSearchParams(req.url.search); + expect(queryParams.getAll('filter')).toEqual([ + 'a=1,b=2,b=3,ö==', + 'a=2', + 'c', + ]); return res(ctx.json([])); }), ); @@ -120,7 +123,8 @@ describe('CatalogClient', () => { server.use( rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => { - expect(req.url.search).toBe('?filter=a=1,b=2,b=3,%C3%B6=%3D,c'); + const queryParams = new URLSearchParams(req.url.search); + expect(queryParams.getAll('filter')).toEqual(['a=1,b=2,b=3,ö==,c']); return res(ctx.json([])); }), ); @@ -140,12 +144,45 @@ describe('CatalogClient', () => { expect(response.items).toEqual([]); }); + it('builds search filters property even those with URL unsafe values', async () => { + const mockedEndpoint = jest + .fn() + .mockImplementation((_req, res, ctx) => + res(ctx.json({ items: [], totalItems: 0 })), + ); + + server.use(rest.get(`${mockBaseUrl}/entities/by-query`, mockedEndpoint)); + + const response = await client.queryEntities( + { + filter: [ + { + '!@#$%': 't?i=1&a:2', + '^&*(){}[]': ['t%^url*encoded2', 'url'], + }, + ], + }, + { token }, + ); + + expect(response).toEqual({ items: [], totalItems: 0 }); + expect(mockedEndpoint).toHaveBeenCalledTimes(1); + + const queryParams = new URLSearchParams( + mockedEndpoint.mock.calls[0][0].url.search, + ); + expect(queryParams.getAll('filter')).toEqual([ + '!@#$%=t?i=1&a:2,^&*(){}[]=t%^url*encoded2,^&*(){}[]=url', + ]); + }); + it('builds entity field selectors properly', async () => { expect.assertions(2); server.use( rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => { - expect(req.url.search).toBe('?fields=a.b,%C3%B6'); + const queryParams = new URLSearchParams(req.url.search); + expect(queryParams.getAll('fields')).toEqual(['a.b,ö']); return res(ctx.json([])); }), ); @@ -185,7 +222,7 @@ describe('CatalogClient', () => { server.use( rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => { - expect(req.url.search).toBe('?offset=1&limit=2&after=%3D'); + expect(req.url.search).toBe('?limit=2&offset=1&after=%3D'); return res(ctx.json([])); }), ); @@ -203,9 +240,11 @@ describe('CatalogClient', () => { server.use( rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => { - expect(req.url.search).toBe( - '?order=asc:kind&order=desc:metadata.name', - ); + const queryParams = new URLSearchParams(req.url.search); + expect(queryParams.getAll('order')).toEqual([ + 'asc:kind', + 'desc:metadata.name', + ]); return res(ctx.json([])); }), ); @@ -326,9 +365,46 @@ describe('CatalogClient', () => { expect(response).toEqual({ items: [], totalItems: 0 }); expect(mockedEndpoint).toHaveBeenCalledTimes(1); - expect(mockedEndpoint.mock.calls[0][0].url.search).toBe( - '?filter=a=1,b=2,b=3,%C3%B6=%3D&filter=a=2&filter=c', + + const queryParams = new URLSearchParams( + mockedEndpoint.mock.calls[0][0].url.search, ); + expect(queryParams.getAll('filter')).toEqual([ + 'a=1,b=2,b=3,ö==', + 'a=2', + 'c', + ]); + }); + + it('builds search filters property even those with URL unsafe values', async () => { + const mockedEndpoint = jest + .fn() + .mockImplementation((_req, res, ctx) => + res(ctx.json({ items: [], totalItems: 0 })), + ); + + server.use(rest.get(`${mockBaseUrl}/entities/by-query`, mockedEndpoint)); + + const response = await client.queryEntities( + { + filter: [ + { + '!@#$%': 't?i=1&a:2', + '^&*(){}[]': ['t%^url*encoded2', 'url'], + }, + ], + }, + { token }, + ); + + expect(response).toEqual({ items: [], totalItems: 0 }); + expect(mockedEndpoint).toHaveBeenCalledTimes(1); + const queryParams = new URLSearchParams( + mockedEndpoint.mock.calls[0][0].url.search, + ); + expect(queryParams.getAll('filter')).toEqual([ + '!@#$%=t?i=1&a:2,^&*(){}[]=t%^url*encoded2,^&*(){}[]=url', + ]); }); it('should send query params correctly on initial request', async () => { @@ -351,9 +427,17 @@ describe('CatalogClient', () => { { field: 'metadata.uid', order: 'desc' }, ], }); - expect(mockedEndpoint.mock.calls[0][0].url.search).toBe( - '?limit=100&orderField=metadata.name,asc&orderField=metadata.uid,desc&fields=a,b&fullTextFilterTerm=query', + + const queryParams = new URLSearchParams( + mockedEndpoint.mock.calls[0][0].url.search, ); + expect(queryParams.getAll('fields')).toEqual(['a,b']); + expect(queryParams.getAll('limit')).toEqual(['100']); + expect(queryParams.getAll('fullTextFilterTerm')).toEqual(['query']); + expect(queryParams.getAll('orderField')).toEqual([ + 'metadata.name,asc', + 'metadata.uid,desc', + ]); }); it('should ignore initial query params if cursor is passed', async () => { @@ -375,7 +459,7 @@ describe('CatalogClient', () => { cursor: 'cursor', }); expect(mockedEndpoint.mock.calls[0][0].url.search).toBe( - '?cursor=cursor&limit=100&fields=a,b', + '?fields=a,b&limit=100&cursor=cursor', ); }); diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index c388519e05..0d6bd6b926 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -22,7 +22,6 @@ import { stringifyLocationRef, } from '@backstage/catalog-model'; import { ResponseError } from '@backstage/errors'; -import crossFetch from 'cross-fetch'; import { CATALOG_FILTER_EXISTS, AddLocationRequest, @@ -40,12 +39,11 @@ import { GetEntitiesByRefsRequest, GetEntitiesByRefsResponse, QueryEntitiesRequest, - QueryEntitiesResponse, EntityFilterQuery, + QueryEntitiesResponse, } from './types/api'; -import { DiscoveryApi } from './types/discovery'; -import { FetchApi } from './types/fetch'; import { isQueryEntitiesInitialRequest } from './utils'; +import { DefaultApiClient, TypedResponse } from './generated'; /** * A frontend and backend compatible client for communicating with the Backstage @@ -54,15 +52,13 @@ import { isQueryEntitiesInitialRequest } from './utils'; * @public */ export class CatalogClient implements CatalogApi { - private readonly discoveryApi: DiscoveryApi; - private readonly fetchApi: FetchApi; + private readonly apiClient: DefaultApiClient; constructor(options: { discoveryApi: { getBaseUrl(pluginId: string): Promise }; fetchApi?: { fetch: typeof fetch }; }) { - this.discoveryApi = options.discoveryApi; - this.fetchApi = options.fetchApi || { fetch: crossFetch }; + this.apiClient = new DefaultApiClient(options); } /** @@ -72,13 +68,11 @@ export class CatalogClient implements CatalogApi { request: GetEntityAncestorsRequest, options?: CatalogRequestOptions, ): Promise { - const { kind, namespace, name } = parseEntityRef(request.entityRef); return await this.requestRequired( - 'GET', - `/entities/by-name/${encodeURIComponent(kind)}/${encodeURIComponent( - namespace, - )}/${encodeURIComponent(name)}/ancestry`, - options, + await this.apiClient.getEntityAncestryByName( + { path: parseEntityRef(request.entityRef) }, + options, + ), ); } @@ -90,9 +84,7 @@ export class CatalogClient implements CatalogApi { options?: CatalogRequestOptions, ): Promise { return await this.requestOptional( - 'GET', - `/locations/${encodeURIComponent(id)}`, - options, + await this.apiClient.getLocation({ path: { id } }, options), ); } @@ -111,39 +103,29 @@ export class CatalogClient implements CatalogApi { limit, after, } = request ?? {}; - const params = this.getParams(filter); - - if (fields.length) { - params.push(`fields=${fields.map(encodeURIComponent).join(',')}`); - } - + const encodedOrder = []; if (order) { for (const directive of [order].flat()) { if (directive) { - params.push( - `order=${encodeURIComponent(directive.order)}:${encodeURIComponent( - directive.field, - )}`, - ); + encodedOrder.push(`${directive.order}:${directive.field}`); } } } - if (offset !== undefined) { - params.push(`offset=${offset}`); - } - if (limit !== undefined) { - params.push(`limit=${limit}`); - } - if (after !== undefined) { - params.push(`after=${encodeURIComponent(after)}`); - } - - const query = params.length ? `?${params.join('&')}` : ''; - const entities: Entity[] = await this.requestRequired( - 'GET', - `/entities${query}`, - options, + const entities = await this.requestRequired( + await this.apiClient.getEntities( + { + query: { + fields, + limit, + filter: this.getFilterValue(filter), + offset, + after, + order: order ? encodedOrder : undefined, + }, + }, + options, + ), ); const refCompare = (a: Entity, b: Entity) => { @@ -178,22 +160,12 @@ export class CatalogClient implements CatalogApi { request: GetEntitiesByRefsRequest, options?: CatalogRequestOptions, ): Promise { - const body: any = { entityRefs: request.entityRefs }; - if (request.fields?.length) { - body.fields = request.fields; - } - - const baseUrl = await this.discoveryApi.getBaseUrl('catalog'); - const url = `${baseUrl}/entities/by-refs`; - - const response = await this.fetchApi.fetch(url, { - headers: { - 'Content-Type': 'application/json', - ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + const response = await this.apiClient.getEntitiesByRefs( + { + body: request, }, - method: 'POST', - body: JSON.stringify(body), - }); + options, + ); if (!response.ok) { throw await ResponseError.fromResponse(response); @@ -212,8 +184,10 @@ export class CatalogClient implements CatalogApi { async queryEntities( request: QueryEntitiesRequest = {}, options?: CatalogRequestOptions, - ) { - const params: string[] = []; + ): Promise { + const params: Partial< + Parameters[0]['query'] + > = {}; if (isQueryEntitiesInitialRequest(request)) { const { @@ -223,45 +197,42 @@ export class CatalogClient implements CatalogApi { orderFields, fullTextFilter, } = request; - params.push(...this.getParams(filter)); + params.filter = this.getFilterValue(filter); if (limit !== undefined) { - params.push(`limit=${limit}`); + params.limit = limit; } if (orderFields !== undefined) { - (Array.isArray(orderFields) ? orderFields : [orderFields]).forEach( - ({ field, order }) => params.push(`orderField=${field},${order}`), - ); + params.orderField = ( + Array.isArray(orderFields) ? orderFields : [orderFields] + ).map(({ field, order }) => `${field},${order}`); } if (fields.length) { - params.push(`fields=${fields.map(encodeURIComponent).join(',')}`); + params.fields = fields; } const normalizedFullTextFilterTerm = fullTextFilter?.term?.trim(); if (normalizedFullTextFilterTerm) { - params.push(`fullTextFilterTerm=${normalizedFullTextFilterTerm}`); + params.fullTextFilterTerm = normalizedFullTextFilterTerm; } if (fullTextFilter?.fields?.length) { - params.push(`fullTextFilterFields=${fullTextFilter.fields.join(',')}`); + params.fullTextFilterFields = fullTextFilter.fields; } } else { const { fields = [], limit, cursor } = request; - params.push(`cursor=${cursor}`); + params.cursor = cursor; if (limit !== undefined) { - params.push(`limit=${limit}`); + params.limit = limit; } if (fields.length) { - params.push(`fields=${fields.map(encodeURIComponent).join(',')}`); + params.fields = fields; } } - const query = params.length ? `?${params.join('&')}` : ''; - return this.requestRequired( - 'GET', - `/entities/by-query${query}`, - options, - ); + return this.apiClient + .getEntitiesByQuery({ query: params }, options) + .then(r => r.json()); } /** @@ -271,13 +242,13 @@ export class CatalogClient implements CatalogApi { entityRef: string | CompoundEntityRef, options?: CatalogRequestOptions, ): Promise { - const { kind, namespace, name } = parseEntityRef(entityRef); return this.requestOptional( - 'GET', - `/entities/by-name/${encodeURIComponent(kind)}/${encodeURIComponent( - namespace, - )}/${encodeURIComponent(name)}`, - options, + await this.apiClient.getEntityByName( + { + path: parseEntityRef(entityRef), + }, + options, + ), ); } @@ -294,11 +265,10 @@ export class CatalogClient implements CatalogApi { ): Promise { const { kind, namespace = 'default', name } = compoundName; return this.requestOptional( - 'GET', - `/entities/by-name/${encodeURIComponent(kind)}/${encodeURIComponent( - namespace, - )}/${encodeURIComponent(name)}`, - options, + await this.apiClient.getEntityByName( + { path: { kind, namespace, name } }, + options, + ), ); } @@ -306,16 +276,9 @@ export class CatalogClient implements CatalogApi { * {@inheritdoc CatalogApi.refreshEntity} */ async refreshEntity(entityRef: string, options?: CatalogRequestOptions) { - const response = await this.fetchApi.fetch( - `${await this.discoveryApi.getBaseUrl('catalog')}/refresh`, - { - headers: { - 'Content-Type': 'application/json', - ...(options?.token && { Authorization: `Bearer ${options?.token}` }), - }, - method: 'POST', - body: JSON.stringify({ entityRef }), - }, + const response = await this.apiClient.refreshEntity( + { body: { entityRef } }, + options, ); if (response.status !== 200) { @@ -331,14 +294,14 @@ export class CatalogClient implements CatalogApi { options?: CatalogRequestOptions, ): Promise { const { filter = [], facets } = request; - const params = this.getParams(filter); - - for (const facet of facets) { - params.push(`facet=${encodeURIComponent(facet)}`); - } - - const query = params.length ? `?${params.join('&')}` : ''; - return await this.requestOptional('GET', `/entity-facets${query}`, options); + return await this.requestOptional( + await this.apiClient.getEntityFacets( + { + query: { facet: facets, filter: this.getFilterValue(filter) }, + }, + options, + ), + ); } /** @@ -350,18 +313,12 @@ export class CatalogClient implements CatalogApi { ): Promise { const { type = 'url', target, dryRun } = request; - const response = await this.fetchApi.fetch( - `${await this.discoveryApi.getBaseUrl('catalog')}/locations${ - dryRun ? '?dryRun=true' : '' - }`, + const response = await this.apiClient.createLocation( { - headers: { - 'Content-Type': 'application/json', - ...(options?.token && { Authorization: `Bearer ${options?.token}` }), - }, - method: 'POST', - body: JSON.stringify({ type, target }), + body: { type, target }, + query: { dryRun: dryRun ? 'true' : undefined }, }, + options, ); if (response.status !== 201) { @@ -388,10 +345,8 @@ export class CatalogClient implements CatalogApi { locationRef: string, options?: CatalogRequestOptions, ): Promise { - const all: { data: Location }[] = await this.requestRequired( - 'GET', - '/locations', - options, + const all = await this.requestRequired( + await this.apiClient.getLocations({}, options), ); return all .map(r => r.data) @@ -406,9 +361,7 @@ export class CatalogClient implements CatalogApi { options?: CatalogRequestOptions, ): Promise { await this.requestIgnored( - 'DELETE', - `/locations/${encodeURIComponent(id)}`, - options, + await this.apiClient.deleteLocation({ path: { id } }, options), ); } @@ -420,9 +373,7 @@ export class CatalogClient implements CatalogApi { options?: CatalogRequestOptions, ): Promise { await this.requestIgnored( - 'DELETE', - `/entities/by-uid/${encodeURIComponent(uid)}`, - options, + await this.apiClient.deleteEntityByUid({ path: { uid } }, options), ); } @@ -434,16 +385,9 @@ export class CatalogClient implements CatalogApi { locationRef: string, options?: CatalogRequestOptions, ): Promise { - const response = await this.fetchApi.fetch( - `${await this.discoveryApi.getBaseUrl('catalog')}/validate-entity`, - { - headers: { - 'Content-Type': 'application/json', - ...(options?.token && { Authorization: `Bearer ${options?.token}` }), - }, - method: 'POST', - body: JSON.stringify({ entity, location: locationRef }), - }, + const response = await this.apiClient.validateEntity( + { body: { entity, location: locationRef } }, + options, ); if (response.ok) { @@ -456,7 +400,7 @@ export class CatalogClient implements CatalogApi { throw await ResponseError.fromResponse(response); } - const { errors = [] } = await response.json(); + const { errors = [] } = (await response.json()) as any; return { valid: false, @@ -468,33 +412,13 @@ export class CatalogClient implements CatalogApi { // Private methods // - private async requestIgnored( - method: string, - path: string, - options?: CatalogRequestOptions, - ): Promise { - const url = `${await this.discoveryApi.getBaseUrl('catalog')}${path}`; - const headers: Record = options?.token - ? { Authorization: `Bearer ${options.token}` } - : {}; - const response = await this.fetchApi.fetch(url, { method, headers }); - + private async requestIgnored(response: Response): Promise { if (!response.ok) { throw await ResponseError.fromResponse(response); } } - private async requestRequired( - method: string, - path: string, - options?: CatalogRequestOptions, - ): Promise { - const url = `${await this.discoveryApi.getBaseUrl('catalog')}${path}`; - const headers: Record = options?.token - ? { Authorization: `Bearer ${options.token}` } - : {}; - const response = await this.fetchApi.fetch(url, { method, headers }); - + private async requestRequired(response: TypedResponse): Promise { if (!response.ok) { throw await ResponseError.fromResponse(response); } @@ -502,17 +426,7 @@ export class CatalogClient implements CatalogApi { return response.json(); } - private async requestOptional( - method: string, - path: string, - options?: CatalogRequestOptions, - ): Promise { - const url = `${await this.discoveryApi.getBaseUrl('catalog')}${path}`; - const headers: Record = options?.token - ? { Authorization: `Bearer ${options.token}` } - : {}; - const response = await this.fetchApi.fetch(url, { method, headers }); - + private async requestOptional(response: Response): Promise { if (!response.ok) { if (response.status === 404) { return undefined; @@ -523,8 +437,8 @@ export class CatalogClient implements CatalogApi { return await response.json(); } - private getParams(filter: EntityFilterQuery = []) { - const params: string[] = []; + private getFilterValue(filter: EntityFilterQuery = []) { + const filters: string[] = []; // filter param can occur multiple times, for example // /api/catalog/entities?filter=metadata.name=wayback-search,kind=component&filter=metadata.name=www-artist,kind=component' // the "outer array" defined by `filter` occurrences corresponds to "anyOf" filters @@ -534,19 +448,17 @@ export class CatalogClient implements CatalogApi { for (const [key, value] of Object.entries(filterItem)) { for (const v of [value].flat()) { if (v === CATALOG_FILTER_EXISTS) { - filterParts.push(encodeURIComponent(key)); + filterParts.push(key); } else if (typeof v === 'string') { - filterParts.push( - `${encodeURIComponent(key)}=${encodeURIComponent(v)}`, - ); + filterParts.push(`${key}=${v}`); } } } if (filterParts.length) { - params.push(`filter=${filterParts.join(',')}`); + filters.push(filterParts.join(',')); } } - return params; + return filters; } } diff --git a/packages/catalog-client/src/generated/apis/DefaultApi.client.ts b/packages/catalog-client/src/generated/apis/DefaultApi.client.ts new file mode 100644 index 0000000000..65b8699370 --- /dev/null +++ b/packages/catalog-client/src/generated/apis/DefaultApi.client.ts @@ -0,0 +1,540 @@ +/* + * 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 { DiscoveryApi } from '../types/discovery'; +import { FetchApi } from '../types/fetch'; +import crossFetch from 'cross-fetch'; +import { pluginId } from '../pluginId'; +import * as parser from 'uri-template'; + +import { AnalyzeLocationRequest } from '../models/AnalyzeLocationRequest.model'; +import { AnalyzeLocationResponse } from '../models/AnalyzeLocationResponse.model'; +import { CreateLocation201Response } from '../models/CreateLocation201Response.model'; +import { CreateLocationRequest } from '../models/CreateLocationRequest.model'; +import { EntitiesBatchResponse } from '../models/EntitiesBatchResponse.model'; +import { EntitiesQueryResponse } from '../models/EntitiesQueryResponse.model'; +import { Entity } from '../models/Entity.model'; +import { EntityAncestryResponse } from '../models/EntityAncestryResponse.model'; +import { EntityFacetsResponse } from '../models/EntityFacetsResponse.model'; +import { GetEntitiesByRefsRequest } from '../models/GetEntitiesByRefsRequest.model'; +import { GetLocations200ResponseInner } from '../models/GetLocations200ResponseInner.model'; +import { Location } from '../models/Location.model'; +import { RefreshEntityRequest } from '../models/RefreshEntityRequest.model'; +import { ValidateEntityRequest } from '../models/ValidateEntityRequest.model'; + +/** + * Wraps the Response type to convey a type on the json call. + * + * @public + */ +export type TypedResponse = Omit & { + json: () => Promise; +}; + +/** + * Options you can pass into a request for additional information. + * + * @public + */ +export interface RequestOptions { + token?: string; +} + +/** + * no description + */ +export class DefaultApiClient { + private readonly discoveryApi: DiscoveryApi; + private readonly fetchApi: FetchApi; + + constructor(options: { + discoveryApi: { getBaseUrl(pluginId: string): Promise }; + fetchApi?: { fetch: typeof fetch }; + }) { + this.discoveryApi = options.discoveryApi; + this.fetchApi = options.fetchApi || { fetch: crossFetch }; + } + + /** + * Validate a given location. + * @param analyzeLocationRequest + */ + public async analyzeLocation( + // @ts-ignore + request: { + body: AnalyzeLocationRequest; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/analyze-location`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + + /** + * Create a location for a given target. + * @param createLocationRequest + * @param dryRun + */ + public async createLocation( + // @ts-ignore + request: { + body: CreateLocationRequest; + query: { + dryRun?: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/locations{?dryRun}`; + + const uri = parser.parse(uriTemplate).expand({ + ...request.query, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + + /** + * Delete a single entity by UID. + * @param uid + */ + public async deleteEntityByUid( + // @ts-ignore + request: { + path: { + uid: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-uid/{uid}`; + + const uri = parser.parse(uriTemplate).expand({ + uid: request.path.uid, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'DELETE', + }); + } + + /** + * Delete a location by id. + * @param id + */ + public async deleteLocation( + // @ts-ignore + request: { + path: { + id: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/locations/{id}`; + + const uri = parser.parse(uriTemplate).expand({ + id: request.path.id, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'DELETE', + }); + } + + /** + * Get all entities matching a given filter. + * @param fields Restrict to just these fields in the response. + * @param limit Number of records to return in the response. + * @param filter Filter for just the entities defined by this filter. + * @param offset Number of records to skip in the query page. + * @param after Pointer to the previous page of results. + * @param order + */ + public async getEntities( + // @ts-ignore + request: { + query: { + fields?: Array; + limit?: number; + filter?: Array; + offset?: number; + after?: string; + order?: Array; + }; + }, + options?: RequestOptions, + ): Promise>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities{?fields,limit,filter*,offset,after,order*}`; + + const uri = parser.parse(uriTemplate).expand({ + ...request.query, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Search for entities by a given query. + * @param fields Restrict to just these fields in the response. + * @param limit Number of records to return in the response. + * @param orderField The fields to sort returned results by. + * @param cursor Cursor to a set page of results. + * @param filter Filter for just the entities defined by this filter. + * @param fullTextFilterTerm Text search term. + * @param fullTextFilterFields A comma separated list of fields to sort returned results by. + */ + public async getEntitiesByQuery( + // @ts-ignore + request: { + query: { + fields?: Array; + limit?: number; + orderField?: Array; + cursor?: string; + filter?: Array; + fullTextFilterTerm?: string; + fullTextFilterFields?: Array; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-query{?fields,limit,orderField*,cursor,filter*,fullTextFilterTerm,fullTextFilterFields}`; + + const uri = parser.parse(uriTemplate).expand({ + ...request.query, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get a batch set of entities given an array of entityRefs. + * @param getEntitiesByRefsRequest + */ + public async getEntitiesByRefs( + // @ts-ignore + request: { + body: GetEntitiesByRefsRequest; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-refs`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + + /** + * Get an entity's ancestry by entity ref. + * @param kind + * @param namespace + * @param name + */ + public async getEntityAncestryByName( + // @ts-ignore + request: { + path: { + kind: string; + namespace: string; + name: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-name/{kind}/{namespace}/{name}/ancestry`; + + const uri = parser.parse(uriTemplate).expand({ + kind: request.path.kind, + namespace: request.path.namespace, + name: request.path.name, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get an entity by an entity ref. + * @param kind + * @param namespace + * @param name + */ + public async getEntityByName( + // @ts-ignore + request: { + path: { + kind: string; + namespace: string; + name: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-name/{kind}/{namespace}/{name}`; + + const uri = parser.parse(uriTemplate).expand({ + kind: request.path.kind, + namespace: request.path.namespace, + name: request.path.name, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get a single entity by the UID. + * @param uid + */ + public async getEntityByUid( + // @ts-ignore + request: { + path: { + uid: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entities/by-uid/{uid}`; + + const uri = parser.parse(uriTemplate).expand({ + uid: request.path.uid, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get all entity facets that match the given filters. + * @param facet + * @param filter Filter for just the entities defined by this filter. + */ + public async getEntityFacets( + // @ts-ignore + request: { + query: { + facet: Array; + filter?: Array; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/entity-facets{?facet*,filter*}`; + + const uri = parser.parse(uriTemplate).expand({ + ...request.query, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get a location by id. + * @param id + */ + public async getLocation( + // @ts-ignore + request: { + path: { + id: string; + }; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/locations/{id}`; + + const uri = parser.parse(uriTemplate).expand({ + id: request.path.id, + }); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Get all locations + */ + public async getLocations( + // @ts-ignore + request: {}, + options?: RequestOptions, + ): Promise>> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/locations`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'GET', + }); + } + + /** + * Refresh the entity related to entityRef. + * @param refreshEntityRequest + */ + public async refreshEntity( + // @ts-ignore + request: { + body: RefreshEntityRequest; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/refresh`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } + + /** + * Validate that a passed in entity has no errors in schema. + * @param validateEntityRequest + */ + public async validateEntity( + // @ts-ignore + request: { + body: ValidateEntityRequest; + }, + options?: RequestOptions, + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `/validate-entity`; + + const uri = parser.parse(uriTemplate).expand({}); + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: 'POST', + body: JSON.stringify(request.body), + }); + } +} diff --git a/plugins/scaffolder/src/legacy/TemplateEditorPage/index.ts b/packages/catalog-client/src/generated/apis/index.ts similarity index 91% rename from plugins/scaffolder/src/legacy/TemplateEditorPage/index.ts rename to packages/catalog-client/src/generated/apis/index.ts index 7de0d3c679..ad2f72c5b2 100644 --- a/plugins/scaffolder/src/legacy/TemplateEditorPage/index.ts +++ b/packages/catalog-client/src/generated/apis/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { TemplateEditorPage } from './TemplateEditorPage'; + +export * from './DefaultApi.client'; diff --git a/packages/catalog-client/src/generated/index.ts b/packages/catalog-client/src/generated/index.ts new file mode 100644 index 0000000000..0cf3e9cc49 --- /dev/null +++ b/packages/catalog-client/src/generated/index.ts @@ -0,0 +1,18 @@ +/* + * 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 * from './apis'; +export * from './models'; diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts new file mode 100644 index 0000000000..a1c1e1f669 --- /dev/null +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationEntityField.model.ts @@ -0,0 +1,36 @@ +/* + * 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 interface AnalyzeLocationEntityField { + /** + * A text to show to the user to inform about the choices made. Like, it could say \"Found a CODEOWNERS file that covers this target, so we suggest leaving this field empty; which would currently make it owned by X\" where X is taken from the codeowners file. + */ + description: string; + value: string | null; + /** + * The outcome of the analysis for this particular field + */ + state: AnalyzeLocationEntityFieldStateEnum; + /** + * e.g. \"spec.owner\"? The frontend needs to know how to \"inject\" the field into the entity again if the user wants to change it + */ + field: string; +} + +export type AnalyzeLocationEntityFieldStateEnum = + | 'analysisSuggestedValue' + | 'analysisSuggestedNoValue' + | 'needsUserInput'; diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts new file mode 100644 index 0000000000..5ede0fb60b --- /dev/null +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationExistingEntity.model.ts @@ -0,0 +1,27 @@ +/* + * 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 { Entity } from '../models/Entity.model'; +import { LocationSpec } from '../models/LocationSpec.model'; + +/** + * If the folder pointed to already contained catalog info yaml files, they are read and emitted like this so that the frontend can inform the user that it located them and can make sure to register them as well if they weren't already + */ +export interface AnalyzeLocationExistingEntity { + entity: Entity; + isRegistered: boolean; + location: LocationSpec; +} diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts new file mode 100644 index 0000000000..886aac05d9 --- /dev/null +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationGenerateEntity.model.ts @@ -0,0 +1,26 @@ +/* + * 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 { AnalyzeLocationEntityField } from '../models/AnalyzeLocationEntityField.model'; +import { RecursivePartialEntity } from '../models/RecursivePartialEntity.model'; + +/** + * This is some form of representation of what the analyzer could deduce. We should probably have a chat about how this can best be conveyed to the frontend. It'll probably contain a (possibly incomplete) entity, plus enough info for the frontend to know what form data to show to the user for overriding/completing the info. + */ +export interface AnalyzeLocationGenerateEntity { + fields: Array; + entity: RecursivePartialEntity; +} diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts new file mode 100644 index 0000000000..e8e9542f5a --- /dev/null +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationRequest.model.ts @@ -0,0 +1,22 @@ +/* + * 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 { LocationInput } from '../models/LocationInput.model'; + +export interface AnalyzeLocationRequest { + catalogFileName?: string; + location: LocationInput; +} diff --git a/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts b/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.ts new file mode 100644 index 0000000000..bf60aa8018 --- /dev/null +++ b/packages/catalog-client/src/generated/models/AnalyzeLocationResponse.model.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. + */ + +import { AnalyzeLocationExistingEntity } from '../models/AnalyzeLocationExistingEntity.model'; +import { AnalyzeLocationGenerateEntity } from '../models/AnalyzeLocationGenerateEntity.model'; + +export interface AnalyzeLocationResponse { + generateEntities: Array; + existingEntityFiles: Array; +} diff --git a/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts b/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts new file mode 100644 index 0000000000..d090d80bf2 --- /dev/null +++ b/packages/catalog-client/src/generated/models/CreateLocation201Response.model.ts @@ -0,0 +1,24 @@ +/* + * 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 { Entity } from '../models/Entity.model'; +import { Location } from '../models/Location.model'; + +export interface CreateLocation201Response { + exists?: boolean; + entities: Array; + location: Location; +} diff --git a/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts b/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts new file mode 100644 index 0000000000..6ef950072e --- /dev/null +++ b/packages/catalog-client/src/generated/models/CreateLocationRequest.model.ts @@ -0,0 +1,20 @@ +/* + * 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 interface CreateLocationRequest { + target: string; + type: string; +} diff --git a/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts b/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts new file mode 100644 index 0000000000..d09b2cc086 --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntitiesBatchResponse.model.ts @@ -0,0 +1,24 @@ +/* + * 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 { NullableEntity } from '../models/NullableEntity.model'; + +export interface EntitiesBatchResponse { + /** + * The list of entities, in the same order as the refs in the request. Entries that are null signify that no entity existed with that ref. + */ + items: Array; +} diff --git a/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts b/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts new file mode 100644 index 0000000000..10d4747caf --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntitiesQueryResponse.model.ts @@ -0,0 +1,27 @@ +/* + * 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 { EntitiesQueryResponsePageInfo } from '../models/EntitiesQueryResponsePageInfo.model'; +import { Entity } from '../models/Entity.model'; + +export interface EntitiesQueryResponse { + /** + * The list of entities paginated by a specific filter. + */ + items: Array; + totalItems: number; + pageInfo: EntitiesQueryResponsePageInfo; +} diff --git a/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts b/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts new file mode 100644 index 0000000000..8a7d5b39bb --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntitiesQueryResponsePageInfo.model.ts @@ -0,0 +1,26 @@ +/* + * 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 interface EntitiesQueryResponsePageInfo { + /** + * The cursor for the next batch of entities. + */ + nextCursor?: string; + /** + * The cursor for the previous batch of entities. + */ + prevCursor?: string; +} diff --git a/packages/catalog-client/src/generated/models/Entity.model.ts b/packages/catalog-client/src/generated/models/Entity.model.ts new file mode 100644 index 0000000000..59ef9bc741 --- /dev/null +++ b/packages/catalog-client/src/generated/models/Entity.model.ts @@ -0,0 +1,41 @@ +/* + * 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 { EntityMeta } from '../models/EntityMeta.model'; +import { EntityRelation } from '../models/EntityRelation.model'; + +/** + * The parts of the format that's common to all versions/kinds of entity. + */ +export interface Entity { + /** + * The relations that this entity has with other entities. + */ + relations?: Array; + /** + * A type representing all allowed JSON object values. + */ + spec?: { [key: string]: any }; + metadata: EntityMeta; + /** + * The high level entity type being described. + */ + kind: string; + /** + * The version of specification format for this particular entity that this is written against. + */ + apiVersion: string; +} diff --git a/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts b/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts new file mode 100644 index 0000000000..08076fcf36 --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityAncestryResponse.model.ts @@ -0,0 +1,22 @@ +/* + * 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 { EntityAncestryResponseItemsInner } from '../models/EntityAncestryResponseItemsInner.model'; + +export interface EntityAncestryResponse { + items: Array; + rootEntityRef: string; +} diff --git a/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts b/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts new file mode 100644 index 0000000000..7713c21295 --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityAncestryResponseItemsInner.model.ts @@ -0,0 +1,22 @@ +/* + * 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 { Entity } from '../models/Entity.model'; + +export interface EntityAncestryResponseItemsInner { + parentEntityRefs: Array; + entity: Entity; +} diff --git a/plugins/catalog-backend/src/catalog/index.ts b/packages/catalog-client/src/generated/models/EntityFacet.model.ts similarity index 84% rename from plugins/catalog-backend/src/catalog/index.ts rename to packages/catalog-client/src/generated/models/EntityFacet.model.ts index ba34673bba..3872091d77 100644 --- a/plugins/catalog-backend/src/catalog/index.ts +++ b/packages/catalog-client/src/generated/models/EntityFacet.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. @@ -14,4 +14,7 @@ * limitations under the License. */ -export type { EntitiesSearchFilter, EntityFilter } from './types'; +export interface EntityFacet { + value: string; + count: number; +} diff --git a/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts b/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts new file mode 100644 index 0000000000..0ff37e4f2f --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityFacetsResponse.model.ts @@ -0,0 +1,21 @@ +/* + * 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 { EntityFacet } from '../models/EntityFacet.model'; + +export interface EntityFacetsResponse { + facets: { [key: string]: Array }; +} diff --git a/packages/catalog-client/src/generated/models/EntityLink.model.ts b/packages/catalog-client/src/generated/models/EntityLink.model.ts new file mode 100644 index 0000000000..f52f9f2df0 --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityLink.model.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +/** + * A link to external information that is related to the entity. + */ +export interface EntityLink { + /** + * An optional value to categorize links into specific groups + */ + type?: string; + /** + * An optional semantic key that represents a visual icon. + */ + icon?: string; + /** + * An optional descriptive title for the link. + */ + title?: string; + /** + * The url to the external site, document, etc. + */ + url: string; +} diff --git a/packages/catalog-client/src/generated/models/EntityMeta.model.ts b/packages/catalog-client/src/generated/models/EntityMeta.model.ts new file mode 100644 index 0000000000..cb0fd28fdc --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityMeta.model.ts @@ -0,0 +1,64 @@ +/* + * 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 { EntityLink } from '../models/EntityLink.model'; + +/** + * Metadata fields common to all versions/kinds of entity. + */ +export interface EntityMeta { + [key: string]: any; + /** + * A list of external hyperlinks related to the entity. + */ + links?: Array; + /** + * A list of single-valued strings, to for example classify catalog entities in various ways. + */ + tags?: Array; + /** + * Construct a type with a set of properties K of type T + */ + annotations?: { [key: string]: string }; + /** + * Construct a type with a set of properties K of type T + */ + labels?: { [key: string]: string }; + /** + * A short (typically relatively few words, on one line) description of the entity. + */ + description?: string; + /** + * A display name of the entity, to be presented in user interfaces instead of the `name` property above, when available. This field is sometimes useful when the `name` is cumbersome or ends up being perceived as overly technical. The title generally does not have as stringent format requirements on it, so it may contain special characters and be more explanatory. Do keep it very short though, and avoid situations where a title can be confused with the name of another entity, or where two entities share a title. Note that this is only for display purposes, and may be ignored by some parts of the code. Entity references still always make use of the `name` property, not the title. + */ + title?: string; + /** + * The namespace that the entity belongs to. + */ + namespace?: string; + /** + * The name of the entity. Must be unique within the catalog at any given point in time, for any given namespace + kind pair. This value is part of the technical identifier of the entity, and as such it will appear in URLs, database tables, entity references, and similar. It is subject to restrictions regarding what characters are allowed. If you want to use a different, more human readable string with fewer restrictions on it in user interfaces, see the `title` field below. + */ + name: string; + /** + * An opaque string that changes for each update operation to any part of the entity, including metadata. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, and the server will then reject the operation if it does not match the current stored value. + */ + etag?: string; + /** + * A globally unique ID for the entity. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, but the server is free to reject requests that do so in such a way that it breaks semantics. + */ + uid?: string; +} diff --git a/packages/catalog-client/src/generated/models/EntityRelation.model.ts b/packages/catalog-client/src/generated/models/EntityRelation.model.ts new file mode 100644 index 0000000000..1107f7563c --- /dev/null +++ b/packages/catalog-client/src/generated/models/EntityRelation.model.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/** + * A relation of a specific type to another entity in the catalog. + */ +export interface EntityRelation { + /** + * The entity ref of the target of this relation. + */ + targetRef: string; + /** + * The type of the relation. + */ + type: string; +} diff --git a/packages/catalog-client/src/generated/models/ErrorError.model.ts b/packages/catalog-client/src/generated/models/ErrorError.model.ts new file mode 100644 index 0000000000..9dbfa22813 --- /dev/null +++ b/packages/catalog-client/src/generated/models/ErrorError.model.ts @@ -0,0 +1,22 @@ +/* + * 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 interface ErrorError { + name: string; + message: string; + stack?: string; + code?: string; +} diff --git a/packages/catalog-client/src/generated/models/ErrorRequest.model.ts b/packages/catalog-client/src/generated/models/ErrorRequest.model.ts new file mode 100644 index 0000000000..e6306b87f5 --- /dev/null +++ b/packages/catalog-client/src/generated/models/ErrorRequest.model.ts @@ -0,0 +1,20 @@ +/* + * 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 interface ErrorRequest { + method: string; + url: string; +} diff --git a/packages/catalog-client/src/generated/models/ErrorResponse.model.ts b/packages/catalog-client/src/generated/models/ErrorResponse.model.ts new file mode 100644 index 0000000000..1f55c34b4a --- /dev/null +++ b/packages/catalog-client/src/generated/models/ErrorResponse.model.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 interface ErrorResponse { + statusCode: number; +} diff --git a/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts b/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts new file mode 100644 index 0000000000..bdda7c2fa6 --- /dev/null +++ b/packages/catalog-client/src/generated/models/GetEntitiesByRefsRequest.model.ts @@ -0,0 +1,20 @@ +/* + * 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 interface GetEntitiesByRefsRequest { + entityRefs: Array; + fields?: Array; +} diff --git a/plugins/scaffolder/src/alpha.ts b/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts similarity index 82% rename from plugins/scaffolder/src/alpha.ts rename to packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts index 92ec945de4..134306cce7 100644 --- a/plugins/scaffolder/src/alpha.ts +++ b/packages/catalog-client/src/generated/models/GetLocations200ResponseInner.model.ts @@ -14,10 +14,8 @@ * limitations under the License. */ -export { - type FormProps, - type TemplateListPageProps, - type TemplateWizardPageProps, -} from './next'; +import { Location } from '../models/Location.model'; -export * from './legacy'; +export interface GetLocations200ResponseInner { + data: Location; +} diff --git a/packages/catalog-client/src/generated/models/Location.model.ts b/packages/catalog-client/src/generated/models/Location.model.ts new file mode 100644 index 0000000000..27e47a5a25 --- /dev/null +++ b/packages/catalog-client/src/generated/models/Location.model.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * Entity location for a specific entity. + */ +export interface Location { + target: string; + type: string; + id: string; +} diff --git a/packages/catalog-client/src/generated/models/LocationInput.model.ts b/packages/catalog-client/src/generated/models/LocationInput.model.ts new file mode 100644 index 0000000000..8de7e3e6c9 --- /dev/null +++ b/packages/catalog-client/src/generated/models/LocationInput.model.ts @@ -0,0 +1,20 @@ +/* + * 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 interface LocationInput { + type: string; + target: string; +} diff --git a/packages/catalog-client/src/generated/models/LocationSpec.model.ts b/packages/catalog-client/src/generated/models/LocationSpec.model.ts new file mode 100644 index 0000000000..80db2cadf2 --- /dev/null +++ b/packages/catalog-client/src/generated/models/LocationSpec.model.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. + */ + +/** + * Holds the entity location information. + */ +export interface LocationSpec { + target: string; + type: string; +} diff --git a/packages/catalog-client/src/generated/models/ModelError.model.ts b/packages/catalog-client/src/generated/models/ModelError.model.ts new file mode 100644 index 0000000000..989e755c9a --- /dev/null +++ b/packages/catalog-client/src/generated/models/ModelError.model.ts @@ -0,0 +1,26 @@ +/* + * 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 { ErrorError } from '../models/ErrorError.model'; +import { ErrorRequest } from '../models/ErrorRequest.model'; +import { ErrorResponse } from '../models/ErrorResponse.model'; + +export interface ModelError { + [key: string]: any; + error: ErrorError; + request?: ErrorRequest; + response: ErrorResponse; +} diff --git a/packages/catalog-client/src/generated/models/NullableEntity.model.ts b/packages/catalog-client/src/generated/models/NullableEntity.model.ts new file mode 100644 index 0000000000..36aff86ba8 --- /dev/null +++ b/packages/catalog-client/src/generated/models/NullableEntity.model.ts @@ -0,0 +1,41 @@ +/* + * 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 { EntityMeta } from '../models/EntityMeta.model'; +import { EntityRelation } from '../models/EntityRelation.model'; + +/** + * The parts of the format that's common to all versions/kinds of entity. + */ +export interface NullableEntity { + /** + * The relations that this entity has with other entities. + */ + relations?: Array; + /** + * A type representing all allowed JSON object values. + */ + spec?: { [key: string]: any }; + metadata: EntityMeta; + /** + * The high level entity type being described. + */ + kind: string; + /** + * The version of specification format for this particular entity that this is written against. + */ + apiVersion: string; +} diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts new file mode 100644 index 0000000000..8de7824550 --- /dev/null +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntity.model.ts @@ -0,0 +1,41 @@ +/* + * 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 { RecursivePartialEntityMeta } from '../models/RecursivePartialEntityMeta.model'; +import { RecursivePartialEntityRelation } from '../models/RecursivePartialEntityRelation.model'; + +/** + * Makes all keys of an entire hierarchy optional. + */ +export interface RecursivePartialEntity { + /** + * The version of specification format for this particular entity that this is written against. + */ + apiVersion?: string; + /** + * The high level entity type being described. + */ + kind?: string; + metadata?: RecursivePartialEntityMeta; + /** + * A type representing all allowed JSON object values. + */ + spec?: { [key: string]: any }; + /** + * The relations that this entity has with other entities. + */ + relations?: Array; +} diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts new file mode 100644 index 0000000000..b21cc1bb21 --- /dev/null +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntityMeta.model.ts @@ -0,0 +1,60 @@ +/* + * 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 { EntityLink } from '../models/EntityLink.model'; + +export interface RecursivePartialEntityMeta { + /** + * A list of external hyperlinks related to the entity. + */ + links?: Array; + /** + * A list of single-valued strings, to for example classify catalog entities in various ways. + */ + tags?: Array; + /** + * Construct a type with a set of properties K of type T + */ + annotations?: { [key: string]: string }; + /** + * Construct a type with a set of properties K of type T + */ + labels?: { [key: string]: string }; + /** + * A short (typically relatively few words, on one line) description of the entity. + */ + description?: string; + /** + * A display name of the entity, to be presented in user interfaces instead of the `name` property above, when available. This field is sometimes useful when the `name` is cumbersome or ends up being perceived as overly technical. The title generally does not have as stringent format requirements on it, so it may contain special characters and be more explanatory. Do keep it very short though, and avoid situations where a title can be confused with the name of another entity, or where two entities share a title. Note that this is only for display purposes, and may be ignored by some parts of the code. Entity references still always make use of the `name` property, not the title. + */ + title?: string; + /** + * The namespace that the entity belongs to. + */ + namespace?: string; + /** + * The name of the entity. Must be unique within the catalog at any given point in time, for any given namespace + kind pair. This value is part of the technical identifier of the entity, and as such it will appear in URLs, database tables, entity references, and similar. It is subject to restrictions regarding what characters are allowed. If you want to use a different, more human readable string with fewer restrictions on it in user interfaces, see the `title` field below. + */ + name?: string; + /** + * An opaque string that changes for each update operation to any part of the entity, including metadata. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, and the server will then reject the operation if it does not match the current stored value. + */ + etag?: string; + /** + * A globally unique ID for the entity. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, but the server is free to reject requests that do so in such a way that it breaks semantics. + */ + uid?: string; +} diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts new file mode 100644 index 0000000000..5111cd7f9f --- /dev/null +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntityMetaAllOf.model.ts @@ -0,0 +1,63 @@ +/* + * 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 { EntityLink } from '../models/EntityLink.model'; + +/** + * Metadata fields common to all versions/kinds of entity. + */ +export interface RecursivePartialEntityMetaAllOf { + /** + * A list of external hyperlinks related to the entity. + */ + links?: Array; + /** + * A list of single-valued strings, to for example classify catalog entities in various ways. + */ + tags?: Array; + /** + * Construct a type with a set of properties K of type T + */ + annotations?: { [key: string]: string }; + /** + * Construct a type with a set of properties K of type T + */ + labels?: { [key: string]: string }; + /** + * A short (typically relatively few words, on one line) description of the entity. + */ + description?: string; + /** + * A display name of the entity, to be presented in user interfaces instead of the `name` property above, when available. This field is sometimes useful when the `name` is cumbersome or ends up being perceived as overly technical. The title generally does not have as stringent format requirements on it, so it may contain special characters and be more explanatory. Do keep it very short though, and avoid situations where a title can be confused with the name of another entity, or where two entities share a title. Note that this is only for display purposes, and may be ignored by some parts of the code. Entity references still always make use of the `name` property, not the title. + */ + title?: string; + /** + * The namespace that the entity belongs to. + */ + namespace?: string; + /** + * The name of the entity. Must be unique within the catalog at any given point in time, for any given namespace + kind pair. This value is part of the technical identifier of the entity, and as such it will appear in URLs, database tables, entity references, and similar. It is subject to restrictions regarding what characters are allowed. If you want to use a different, more human readable string with fewer restrictions on it in user interfaces, see the `title` field below. + */ + name?: string; + /** + * An opaque string that changes for each update operation to any part of the entity, including metadata. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, and the server will then reject the operation if it does not match the current stored value. + */ + etag?: string; + /** + * A globally unique ID for the entity. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations. The field can (optionally) be specified when performing update or delete operations, but the server is free to reject requests that do so in such a way that it breaks semantics. + */ + uid?: string; +} diff --git a/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts b/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts new file mode 100644 index 0000000000..50635b69f4 --- /dev/null +++ b/packages/catalog-client/src/generated/models/RecursivePartialEntityRelation.model.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/** + * A relation of a specific type to another entity in the catalog. + */ +export interface RecursivePartialEntityRelation { + /** + * The entity ref of the target of this relation. + */ + targetRef?: string; + /** + * The type of the relation. + */ + type?: string; +} diff --git a/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts b/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts new file mode 100644 index 0000000000..573523385b --- /dev/null +++ b/packages/catalog-client/src/generated/models/RefreshEntityRequest.model.ts @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/** + * Options for requesting a refresh of entities in the catalog. + */ +export interface RefreshEntityRequest { + authorizationToken?: string; + /** + * The reference to a single entity that should be refreshed + */ + entityRef: string; +} diff --git a/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts b/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts new file mode 100644 index 0000000000..0031b2edc2 --- /dev/null +++ b/packages/catalog-client/src/generated/models/ValidateEntity400Response.model.ts @@ -0,0 +1,21 @@ +/* + * 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 { ValidateEntity400ResponseErrorsInner } from '../models/ValidateEntity400ResponseErrorsInner.model'; + +export interface ValidateEntity400Response { + errors: Array; +} diff --git a/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts b/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts new file mode 100644 index 0000000000..6223df27af --- /dev/null +++ b/packages/catalog-client/src/generated/models/ValidateEntity400ResponseErrorsInner.model.ts @@ -0,0 +1,21 @@ +/* + * 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 interface ValidateEntity400ResponseErrorsInner { + [key: string]: any; + name: string; + message: string; +} diff --git a/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts b/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts new file mode 100644 index 0000000000..d5ce1be5c9 --- /dev/null +++ b/packages/catalog-client/src/generated/models/ValidateEntityRequest.model.ts @@ -0,0 +1,20 @@ +/* + * 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 interface ValidateEntityRequest { + location: string; + entity: { [key: string]: any }; +} diff --git a/packages/catalog-client/src/generated/models/index.ts b/packages/catalog-client/src/generated/models/index.ts new file mode 100644 index 0000000000..5a93dbb33a --- /dev/null +++ b/packages/catalog-client/src/generated/models/index.ts @@ -0,0 +1,52 @@ +/* + * 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 * from '../models/AnalyzeLocationEntityField.model'; +export * from '../models/AnalyzeLocationExistingEntity.model'; +export * from '../models/AnalyzeLocationGenerateEntity.model'; +export * from '../models/AnalyzeLocationRequest.model'; +export * from '../models/AnalyzeLocationResponse.model'; +export * from '../models/CreateLocation201Response.model'; +export * from '../models/CreateLocationRequest.model'; +export * from '../models/EntitiesBatchResponse.model'; +export * from '../models/EntitiesQueryResponse.model'; +export * from '../models/EntitiesQueryResponsePageInfo.model'; +export * from '../models/Entity.model'; +export * from '../models/EntityAncestryResponse.model'; +export * from '../models/EntityAncestryResponseItemsInner.model'; +export * from '../models/EntityFacet.model'; +export * from '../models/EntityFacetsResponse.model'; +export * from '../models/EntityLink.model'; +export * from '../models/EntityMeta.model'; +export * from '../models/EntityRelation.model'; +export * from '../models/ErrorError.model'; +export * from '../models/ErrorRequest.model'; +export * from '../models/ErrorResponse.model'; +export * from '../models/GetEntitiesByRefsRequest.model'; +export * from '../models/GetLocations200ResponseInner.model'; +export * from '../models/Location.model'; +export * from '../models/LocationInput.model'; +export * from '../models/LocationSpec.model'; +export * from '../models/ModelError.model'; +export * from '../models/NullableEntity.model'; +export * from '../models/RecursivePartialEntity.model'; +export * from '../models/RecursivePartialEntityMeta.model'; +export * from '../models/RecursivePartialEntityMetaAllOf.model'; +export * from '../models/RecursivePartialEntityRelation.model'; +export * from '../models/RefreshEntityRequest.model'; +export * from '../models/ValidateEntity400Response.model'; +export * from '../models/ValidateEntity400ResponseErrorsInner.model'; +export * from '../models/ValidateEntityRequest.model'; diff --git a/packages/catalog-client/src/generated/pluginId.ts b/packages/catalog-client/src/generated/pluginId.ts new file mode 100644 index 0000000000..c8c8ce1ca3 --- /dev/null +++ b/packages/catalog-client/src/generated/pluginId.ts @@ -0,0 +1,17 @@ +/* + * 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 const pluginId = 'catalog'; diff --git a/packages/catalog-client/src/generated/types/discovery.ts b/packages/catalog-client/src/generated/types/discovery.ts new file mode 100644 index 0000000000..a7f87d3780 --- /dev/null +++ b/packages/catalog-client/src/generated/types/discovery.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * This is a copy of the DiscoveryApi, to avoid importing core-plugin-api. + */ +export type DiscoveryApi = { + getBaseUrl(pluginId: string): Promise; +}; diff --git a/packages/catalog-client/src/generated/types/fetch.ts b/packages/catalog-client/src/generated/types/fetch.ts new file mode 100644 index 0000000000..3de56c028e --- /dev/null +++ b/packages/catalog-client/src/generated/types/fetch.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * This is a copy of FetchApi, to avoid importing core-plugin-api. + */ +export type FetchApi = { + fetch: typeof fetch; +}; diff --git a/packages/catalog-model/alpha-api-report.md b/packages/catalog-model/api-report-alpha.md similarity index 100% rename from packages/catalog-model/alpha-api-report.md rename to packages/catalog-model/api-report-alpha.md diff --git a/packages/catalog-model/examples/acme/backstage-group.yaml b/packages/catalog-model/examples/acme/backstage-group.yaml index f83587532c..0372eedab7 100644 --- a/packages/catalog-model/examples/acme/backstage-group.yaml +++ b/packages/catalog-model/examples/acme/backstage-group.yaml @@ -8,6 +8,6 @@ spec: profile: displayName: Backstage email: backstage@example.com - picture: https://avatars.dicebear.com/api/identicon/backstage@example.com.svg?background=%23fff&margin=25 + picture: https://api.dicebear.com/7.x/identicon/svg?seed=Fluffy&backgroundColor=ffdfbf parent: infrastructure children: [team-a, team-b] diff --git a/packages/catalog-model/examples/acme/org.yaml b/packages/catalog-model/examples/acme/org.yaml index 9a0d690b57..96dd3a4e9e 100644 --- a/packages/catalog-model/examples/acme/org.yaml +++ b/packages/catalog-model/examples/acme/org.yaml @@ -13,7 +13,7 @@ spec: profile: displayName: ACME Corp email: info@example.com - picture: https://avatars.dicebear.com/api/identicon/info@example.com.svg?background=%23fff&margin=25 + picture: https://api.dicebear.com/7.x/identicon/svg?seed=Maggie&flip=true&backgroundColor=ffdfbf children: [infrastructure] --- apiVersion: backstage.io/v1alpha1 diff --git a/packages/catalog-model/examples/acme/team-a-group.yaml b/packages/catalog-model/examples/acme/team-a-group.yaml index e343209d5f..7fe0e7b3f3 100644 --- a/packages/catalog-model/examples/acme/team-a-group.yaml +++ b/packages/catalog-model/examples/acme/team-a-group.yaml @@ -8,7 +8,7 @@ spec: profile: # Intentional no displayName for testing email: team-a@example.com - picture: https://avatars.dicebear.com/api/identicon/team-a@example.com.svg?background=%23fff&margin=25 + picture: https://api.dicebear.com/7.x/identicon/svg?seed=Fluffy&backgroundType=solid,gradientLinear&backgroundColor=ffd5dc,b6e3f4 parent: backstage children: [] --- @@ -20,7 +20,7 @@ spec: profile: # Intentional no displayName for testing email: breanna-davison@example.com - picture: https://avatars.dicebear.com/api/avataaars/breanna-davison@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Luna&backgroundColor=transparent memberOf: [team-a] --- apiVersion: backstage.io/v1alpha1 @@ -31,7 +31,7 @@ spec: profile: displayName: Janelle Dawe email: janelle-dawe@example.com - picture: https://avatars.dicebear.com/api/avataaars/janelle-dawe@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Leo&backgroundColor=transparent memberOf: [team-a] --- apiVersion: backstage.io/v1alpha1 @@ -42,7 +42,7 @@ spec: profile: displayName: Nigel Manning email: nigel-manning@example.com - picture: https://avatars.dicebear.com/api/avataaars/nigel-manning@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Midnight&backgroundColor=transparent memberOf: [team-a] --- # This user is added as an example, to make it more easy for the "Guest" @@ -56,5 +56,4 @@ spec: profile: displayName: Guest User email: guest@example.com - picture: https://avatars.dicebear.com/api/avataaars/guest@example.com.svg?background=%23fff memberOf: [team-a] diff --git a/packages/catalog-model/examples/acme/team-b-group.yaml b/packages/catalog-model/examples/acme/team-b-group.yaml index 20ab8721ea..63dd000fb4 100644 --- a/packages/catalog-model/examples/acme/team-b-group.yaml +++ b/packages/catalog-model/examples/acme/team-b-group.yaml @@ -8,7 +8,7 @@ spec: profile: displayName: Team B email: team-b@example.com - picture: https://avatars.dicebear.com/api/identicon/team-b@example.com.svg?background=%23fff&margin=25 + picture: https://api.dicebear.com/7.x/identicon/svg?seed=Abby&backgroundType=solid,gradientLinear&backgroundColor=ffd5dc,b6e3f4 parent: backstage children: [] --- @@ -20,7 +20,7 @@ spec: profile: displayName: Amelia Park email: amelia-park@example.com - picture: https://avatars.dicebear.com/api/avataaars/amelia-park@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Gizmo&backgroundColor=transparent memberOf: [team-b] --- apiVersion: backstage.io/v1alpha1 @@ -31,7 +31,7 @@ spec: profile: displayName: Colette Brock email: colette-brock@example.com - picture: https://avatars.dicebear.com/api/avataaars/colette-brock@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Bailey&backgroundColor=transparent memberOf: [team-b] --- apiVersion: backstage.io/v1alpha1 @@ -42,7 +42,7 @@ spec: profile: displayName: Jenny Doe email: jenny-doe@example.com - picture: https://avatars.dicebear.com/api/avataaars/jenny-doe@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Mimi&backgroundColor=transparent memberOf: [team-b] --- apiVersion: backstage.io/v1alpha1 @@ -53,7 +53,7 @@ spec: profile: displayName: Jonathon Page email: jonathon-page@example.com - picture: https://avatars.dicebear.com/api/avataaars/jonathon-page@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Baby&backgroundColor=transparent memberOf: [team-b] --- apiVersion: backstage.io/v1alpha1 @@ -64,5 +64,5 @@ spec: profile: displayName: Justine Barrow email: justine-barrow@example.com - picture: https://avatars.dicebear.com/api/avataaars/justine-barrow@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Sassy&backgroundColor=transparent memberOf: [team-b] diff --git a/packages/catalog-model/examples/acme/team-c-group.yaml b/packages/catalog-model/examples/acme/team-c-group.yaml index 639b591d61..2daaa1daba 100644 --- a/packages/catalog-model/examples/acme/team-c-group.yaml +++ b/packages/catalog-model/examples/acme/team-c-group.yaml @@ -8,7 +8,7 @@ spec: profile: displayName: Team C email: team-c@example.com - picture: https://avatars.dicebear.com/api/identicon/team-c@example.com.svg?background=%23fff&margin=25 + picture: https://api.dicebear.com/7.x/identicon/svg?seed=Loki&backgroundType=gradientLinear&backgroundColor=b6e3f4,ffd5dc,ffdfbf,c0aede parent: boxoffice children: [] --- @@ -20,7 +20,7 @@ spec: profile: displayName: Calum Leavy email: calum-leavy@example.com - picture: https://avatars.dicebear.com/api/avataaars/calum-leavy@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Bear&backgroundColor=transparent memberOf: [team-c] --- apiVersion: backstage.io/v1alpha1 @@ -31,7 +31,7 @@ spec: profile: displayName: Frank Tiernan email: frank-tiernan@example.com - picture: https://avatars.dicebear.com/api/avataaars/frank-tiernan@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Bandit&backgroundColor=transparent memberOf: [team-c] --- apiVersion: backstage.io/v1alpha1 @@ -42,7 +42,7 @@ spec: profile: displayName: Peadar MacMahon email: peadar-macmahon@example.com - picture: https://avatars.dicebear.com/api/avataaars/peadar-macmahon@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Sophie memberOf: [team-c] --- apiVersion: backstage.io/v1alpha1 @@ -53,7 +53,7 @@ spec: profile: displayName: Sarah Gilroy email: sarah-gilroy@example.com - picture: https://avatars.dicebear.com/api/avataaars/sarah-gilroy@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Rascal memberOf: [team-c] --- apiVersion: backstage.io/v1alpha1 @@ -64,5 +64,5 @@ spec: profile: displayName: Tara MacGovern email: tara-macgovern@example.com - picture: https://avatars.dicebear.com/api/avataaars/tara-macgovern@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Jasmine memberOf: [team-c] diff --git a/packages/catalog-model/examples/acme/team-d-group.yaml b/packages/catalog-model/examples/acme/team-d-group.yaml index 898ed8be6c..ac6d70772e 100644 --- a/packages/catalog-model/examples/acme/team-d-group.yaml +++ b/packages/catalog-model/examples/acme/team-d-group.yaml @@ -8,7 +8,7 @@ spec: profile: displayName: Team D email: team-d@example.com - picture: https://avatars.dicebear.com/api/identicon/team-d@example.com.svg?background=%23fff&margin=25 + picture: https://api.dicebear.com/7.x/identicon/svg?seed=Bailey&backgroundType=gradientLinear&backgroundColor=b6e3f4,ffd5dc,ffdfbf,c0aede parent: boxoffice children: [] --- @@ -20,7 +20,7 @@ spec: profile: displayName: Eva MacDowell email: eva-macdowell@example.com - picture: https://avatars.dicebear.com/api/avataaars/eva-macdowell@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Cuddles memberOf: [team-d] --- apiVersion: backstage.io/v1alpha1 @@ -31,5 +31,5 @@ spec: profile: displayName: Lucy Sheehan email: lucy-sheehan@example.com - picture: https://avatars.dicebear.com/api/avataaars/lucy-sheehan@example.com.svg?background=%23fff + picture: https://api.dicebear.com/7.x/avataaars/svg?seed=Snickers memberOf: [team-d] diff --git a/packages/cli-node/CHANGELOG.md b/packages/cli-node/CHANGELOG.md index d13b9593cd..176785b2db 100644 --- a/packages/cli-node/CHANGELOG.md +++ b/packages/cli-node/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/cli-node +## 0.2.2 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.2-next.0 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.1 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + ## 0.2.0 ### Minor Changes diff --git a/packages/cli-node/package.json b/packages/cli-node/package.json index 36368fd4a9..60ba687ddc 100644 --- a/packages/cli-node/package.json +++ b/packages/cli-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli-node", "description": "Node.js library for Backstage CLIs", - "version": "0.2.0", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,11 +35,11 @@ "@yarnpkg/parsers": "^3.0.0-rc.4", "fs-extra": "10.1.0", "semver": "^7.5.3", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "devDependencies": { - "@backstage/cli": "workspace:^", - "mock-fs": "^5.2.0" + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^" }, "files": [ "dist" diff --git a/packages/cli-node/src/monorepo/isMonorepo.test.ts b/packages/cli-node/src/monorepo/isMonorepo.test.ts index eb466a6ac8..de0dae0893 100644 --- a/packages/cli-node/src/monorepo/isMonorepo.test.ts +++ b/packages/cli-node/src/monorepo/isMonorepo.test.ts @@ -15,15 +15,17 @@ */ import { isMonoRepo } from './isMonoRepo'; -import mockFs from 'mock-fs'; +import { createMockDirectory } from '@backstage/backend-test-utils'; + +const mockDir = createMockDirectory(); + +jest.mock('../paths', () => ({ + paths: { resolveTargetRoot: (...args: string[]) => mockDir.resolve(...args) }, +})); describe('isMonoRepo', () => { - afterEach(() => { - mockFs.restore(); - }); - it('should detect a monorepo', async () => { - mockFs({ + mockDir.setContent({ 'package.json': JSON.stringify({ name: 'foo', workspaces: { @@ -35,7 +37,7 @@ describe('isMonoRepo', () => { }); it('should detect a non- monorepo', async () => { - mockFs({ + mockDir.setContent({ 'package.json': JSON.stringify({ name: 'foo', }), @@ -44,7 +46,7 @@ describe('isMonoRepo', () => { }); it('should return false if package.json is missing', async () => { - mockFs({}); + mockDir.setContent({}); await expect(isMonoRepo()).resolves.toBe(false); }); }); diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index ec80b61dc4..1d79e73dfb 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,180 @@ # @backstage/cli +## 0.25.2-next.0 + +### Patch Changes + +- c624938: Add experimental support for optional `auth` app entry point. +- acd2860: Updated dependency `vite-plugin-node-polyfills` to `^0.19.0`. +- 35725e2: Updated dependencies in frontend plugin templates +- c7259dc: Updated the backend module template to make the module instance the package default export. +- Updated dependencies + - @backstage/eslint-plugin@0.1.5-next.0 + - @backstage/cli-node@0.2.2 + - @backstage/config-loader@1.6.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 0.25.1 + +### Patch Changes + +- b6b15b2: Use sha256 instead of md5 in build script cache key calculation + + Makes it possible to build on FIPS nodejs. + +- Updated dependencies + - @backstage/config-loader@1.6.1 + - @backstage/cli-node@0.2.2 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/eslint-plugin@0.1.4 + - @backstage/integration@1.8.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 0.25.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.1-next.0 + - @backstage/cli-node@0.2.2-next.0 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/release-manifests@0.0.11 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/eslint-plugin@0.1.4 + - @backstage/types@1.1.1 + +## 0.25.1-next.0 + +### Patch Changes + +- b6b15b2: Use sha256 instead of md5 in build script cache key calculation + + Makes it possible to build on FIPS nodejs. + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.1 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0 + - @backstage/errors@1.2.3 + - @backstage/eslint-plugin@0.1.4 + - @backstage/integration@1.8.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 0.25.0 + +### Minor Changes + +- 3834067: Updates the ESLint config to ignore issues created by generated files in `**/src/generated/**`. + +### Patch Changes + +- 32018ff: Enable the `tsx` loader to work on Node 18.19 and up +- 0ffee55: Toned down the warning message when git is not found +- c6f3743: Added a warning when starting a standalone backend plugin that hasn't been updated to the new backend system. +- 3e358b0: Added deprecation warning for React Router v6 beta, please make sure you have migrated your apps to use React Router v6 stable as support for the beta version will be removed. See the [migration tutorial](https://backstage.io/docs/tutorials/react-router-stable-migration) for more information. +- 219d7f0: Updating template generation for scaffolder module +- 8cda3c7: Tweaked Node.js version check for when to use the new module register API with the new backend `package start` command. +- a3edc18: Updated dependency `vite-plugin-node-polyfills` to `^0.17.0`. +- 627554e: Updated dependency `@rollup/plugin-node-resolve` to `^15.0.0`. +- c07cee5: Updated dependency `@rollup/plugin-json` to `^6.0.0`. +- bd586a5: Updated dependency `bfj` to `^8.0.0`. +- 8056425: Updated dependency `@typescript-eslint/eslint-plugin` to `6.12.0`. +- 017c425: Updated dependency `@typescript-eslint/eslint-plugin` to `6.11.0`. +- 2565cc8: Updated dependency `@rollup/plugin-commonjs` to `^25.0.0`. +- 33e96e5: Switched the `@typescript-eslint/eslint-plugin` dependency back to using a `^` version range. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/eslint-plugin@0.1.4 + - @backstage/config-loader@1.6.0 + - @backstage/integration@1.8.0 + - @backstage/cli-node@0.2.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 0.25.0-next.3 + +### Patch Changes + +- 219d7f0: Updating template generation for scaffolder module +- bd586a5: Updated dependency `bfj` to `^8.0.0`. +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0-next.0 + - @backstage/errors@1.2.3 + - @backstage/eslint-plugin@0.1.4-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 0.25.0-next.2 + +### Patch Changes + +- 32018ff: Enable the `tsx` loader to work on Node 18.19 and up +- a3edc18: Updated dependency `vite-plugin-node-polyfills` to `^0.17.0`. +- 627554e: Updated dependency `@rollup/plugin-node-resolve` to `^15.0.0`. +- c07cee5: Updated dependency `@rollup/plugin-json` to `^6.0.0`. +- 2565cc8: Updated dependency `@rollup/plugin-commonjs` to `^25.0.0`. +- Updated dependencies + - @backstage/config-loader@1.6.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/eslint-plugin@0.1.4-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 0.25.0-next.1 + +### Minor Changes + +- 38340678c3: Updates the ESLint config to ignore issues created by generated files in `**/src/generated/**`. + +### Patch Changes + +- 0ffee55010: Toned down the warning message when git is not found +- c6f3743172: Added a warning when starting a standalone backend plugin that hasn't been updated to the new backend system. +- 3e358b0dff: Added deprecation warning for React Router v6 beta, please make sure you have migrated your apps to use React Router v6 stable as support for the beta version will be removed. See the [migration tutorial](https://backstage.io/docs/tutorials/react-router-stable-migration) for more information. +- 8056425e09: Updated dependency `@typescript-eslint/eslint-plugin` to `6.12.0`. +- 33e96e59e7: Switched the `@typescript-eslint/eslint-plugin` dependency back to using a `^` version range. +- Updated dependencies + - @backstage/eslint-plugin@0.1.4-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.5.3 + - @backstage/errors@1.2.3 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + ## 0.24.1-next.0 ### Patch Changes diff --git a/packages/cli/config/eslint-factory.js b/packages/cli/config/eslint-factory.js index 58a9362051..3717943427 100644 --- a/packages/cli/config/eslint-factory.js +++ b/packages/cli/config/eslint-factory.js @@ -66,7 +66,7 @@ function createConfig(dir, extraConfig = {}) { ...(extraExtends ?? []), ], parser: '@typescript-eslint/parser', - plugins: ['import', ...(plugins ?? [])], + plugins: ['import', 'unused-imports', ...(plugins ?? [])], env: { jest: true, ...env, @@ -165,6 +165,23 @@ function createConfig(dir, extraConfig = {}) { ], }, }, + { + files: ['**/src/generated/**/*.ts'], + rules: { + ...tsRules, + 'no-unused-vars': 'off', + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'warn', + { + vars: 'all', + varsIgnorePattern: '^_', + args: 'none', + argsIgnorePattern: '^_', + }, + ], + }, + }, ...(overrides ?? []), ], }; diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 4a8bf9694f..584a31c672 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -219,7 +219,7 @@ async function getProjectConfig(targetPath, extraConfig) { // If no explicit id was configured, generated one based on the configuration. if (!config.id) { const configHash = crypto - .createHash('md5') + .createHash('sha256') .update(version) .update(Buffer.alloc(1)) .update(JSON.stringify(config.transform)) diff --git a/packages/cli/config/jestSucraseTransform.js b/packages/cli/config/jestSucraseTransform.js index 64e7307cab..349b6a1d58 100644 --- a/packages/cli/config/jestSucraseTransform.js +++ b/packages/cli/config/jestSucraseTransform.js @@ -70,7 +70,7 @@ function createTransformer(config) { }; const getCacheKey = sourceText => { - return createHash('md5') + return createHash('sha256') .update(sourceText) .update(Buffer.alloc(1)) .update(sucrasePkg.version) diff --git a/packages/cli/config/jestYamlTransform.js b/packages/cli/config/jestYamlTransform.js index 1235f8a4b7..84d1d9838c 100644 --- a/packages/cli/config/jestYamlTransform.js +++ b/packages/cli/config/jestYamlTransform.js @@ -25,7 +25,7 @@ function createTransformer(config) { const getCacheKey = sourceText => { return crypto - .createHash('md5') + .createHash('sha256') .update(sourceText) .update(Buffer.alloc(1)) .update(JSON.stringify(config)) diff --git a/packages/cli/package.json b/packages/cli/package.json index 490325296c..5ca7d675d3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.24.1-next.0", + "version": "0.25.2-next.0", "publishConfig": { "access": "public" }, @@ -46,9 +46,9 @@ "@octokit/oauth-app": "^4.2.0", "@octokit/request": "^6.0.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", - "@rollup/plugin-commonjs": "^23.0.0", - "@rollup/plugin-json": "^5.0.0", - "@rollup/plugin-node-resolve": "^13.0.6", + "@rollup/plugin-commonjs": "^25.0.0", + "@rollup/plugin-json": "^6.0.0", + "@rollup/plugin-node-resolve": "^15.0.0", "@rollup/plugin-yaml": "^4.0.0", "@spotify/eslint-config-base": "^14.0.0", "@spotify/eslint-config-react": "^14.0.0", @@ -64,11 +64,11 @@ "@swc/jest": "^0.2.22", "@types/jest": "^29.0.0", "@types/webpack-env": "^1.15.2", - "@typescript-eslint/eslint-plugin": "6.11.0", + "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.7.2", "@yarnpkg/lockfile": "^1.1.0", "@yarnpkg/parsers": "^3.0.0-rc.4", - "bfj": "^7.0.2", + "bfj": "^8.0.0", "buffer": "^6.0.3", "chalk": "^4.0.0", "chokidar": "^3.3.1", @@ -89,6 +89,7 @@ "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", + "eslint-plugin-unused-imports": "^3.0.0", "eslint-webpack-plugin": "^3.1.1", "express": "^4.17.1", "fork-ts-checker-webpack-plugin": "^7.0.0-alpha.8", @@ -129,7 +130,7 @@ "swc-loader": "^0.2.3", "tar": "^6.1.12", "terser-webpack-plugin": "^5.1.3", - "tsx": "^3.14.0", + "tsx": "^4.0.0", "util": "^0.12.3", "webpack": "^5.70.0", "webpack-dev-server": "^4.7.3", @@ -137,7 +138,7 @@ "yaml": "^2.0.0", "yml-loader": "^2.1.0", "yn": "^4.0.0", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-common": "workspace:^", @@ -176,7 +177,7 @@ "@vitejs/plugin-react": "^4.0.4", "vite": "^4.4.9", "vite-plugin-html": "^3.2.0", - "vite-plugin-node-polyfills": "^0.16.0" + "vite-plugin-node-polyfills": "^0.19.0" }, "peerDependenciesMeta": { "@vitejs/plugin-react": { diff --git a/packages/cli/src/commands/start/command.ts b/packages/cli/src/commands/start/command.ts index 48122d5ac0..427f8b6efb 100644 --- a/packages/cli/src/commands/start/command.ts +++ b/packages/cli/src/commands/start/command.ts @@ -16,7 +16,7 @@ import { OptionValues } from 'commander'; import { findRoleFromCommand } from '../../lib/role'; -import { startBackend } from './startBackend'; +import { startBackend, startBackendPlugin } from './startBackend'; import { startFrontend } from './startFrontend'; export async function command(opts: OptionValues): Promise { @@ -31,10 +31,11 @@ export async function command(opts: OptionValues): Promise { switch (role) { case 'backend': + return startBackend(options); case 'backend-plugin': case 'backend-plugin-module': case 'node-library': - return startBackend(options); + return startBackendPlugin(options); case 'frontend': return startFrontend({ ...options, diff --git a/packages/cli/src/commands/start/startBackend.ts b/packages/cli/src/commands/start/startBackend.ts index 967a1108a0..25758a0939 100644 --- a/packages/cli/src/commands/start/startBackend.ts +++ b/packages/cli/src/commands/start/startBackend.ts @@ -26,17 +26,7 @@ interface StartBackendOptions { } export async function startBackend(options: StartBackendOptions) { - const hasDev = await fs.pathExists(paths.resolveTarget('dev')); - if (hasDev) { - const waitForExit = await startBackendExperimental({ - entry: 'dev/index', - checksEnabled: false, // not supported - inspectEnabled: options.inspectEnabled, - inspectBrkEnabled: options.inspectBrkEnabled, - }); - - await waitForExit(); - } else if (!process.env.LEGACY_BACKEND_START) { + if (!process.env.LEGACY_BACKEND_START) { const waitForExit = await startBackendExperimental({ entry: 'src/index', checksEnabled: false, // not supported @@ -46,12 +36,7 @@ export async function startBackend(options: StartBackendOptions) { 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')); - - const waitForExit = await serveBackend({ + const waitForExit = await cleanDistAndServeBackend({ entry: 'src/index', checksEnabled: options.checksEnabled, inspectEnabled: options.inspectEnabled, @@ -61,3 +46,63 @@ export async function startBackend(options: StartBackendOptions) { await waitForExit(); } } + +export async function startBackendPlugin(options: StartBackendOptions) { + if (!process.env.LEGACY_BACKEND_START) { + const hasDevIndexEntry = await fs.pathExists( + paths.resolveTarget('dev', 'index.ts'), + ); + const hasSrcIndexEntry = await fs.pathExists( + paths.resolveTarget('src', 'run.ts'), + ); + + if (!hasDevIndexEntry && !hasSrcIndexEntry) { + console.warn( + hasSrcIndexEntry + ? `The 'dev' directory is missing. The plugin might not be updated for the new backend system. To run, use "LEGACY_BACKEND_START=1 yarn start".` + : `The 'dev' directory is missing. Please create a proper dev/index.ts in order to start the plugin.`, + ); + return; + } + + const waitForExit = await startBackendExperimental({ + entry: 'dev/index', + checksEnabled: false, // not supported + inspectEnabled: options.inspectEnabled, + inspectBrkEnabled: options.inspectBrkEnabled, + }); + + await waitForExit(); + } else { + const hasEntry = await fs.pathExists(paths.resolveTarget('src', 'run.ts')); + if (!hasEntry) { + console.warn( + `src/run.ts is missing. Please create the file or run the command without LEGACY_BACKEND_START`, + ); + return; + } + + const waitForExit = await cleanDistAndServeBackend({ + entry: 'src/run', + checksEnabled: options.checksEnabled, + inspectEnabled: options.inspectEnabled, + inspectBrkEnabled: options.inspectBrkEnabled, + }); + + await waitForExit(); + } +} + +async function cleanDistAndServeBackend(options: { + entry: string; + checksEnabled: boolean; + inspectEnabled: boolean; + inspectBrkEnabled: boolean; +}) { + // 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')); + + return serveBackend(options); +} diff --git a/packages/cli/src/lib/bundler/bundle.ts b/packages/cli/src/lib/bundler/bundle.ts index 11e18320f1..d0624c93ff 100644 --- a/packages/cli/src/lib/bundler/bundle.ts +++ b/packages/cli/src/lib/bundler/bundle.ts @@ -25,7 +25,7 @@ import { import formatWebpackMessages from 'react-dev-utils/formatWebpackMessages'; import { createConfig, resolveBaseUrl } from './config'; import { BuildOptions } from './types'; -import { resolveBundlingPaths } from './paths'; +import { resolveBundlingPaths, resolveOptionalBundlingPaths } from './paths'; import chalk from 'chalk'; import { createDetectedModulesEntryPoint } from './packageDetection'; @@ -47,18 +47,44 @@ export async function buildBundle(options: BuildOptions) { targetPath: paths.targetPath, }); - const config = await createConfig(paths, { + const commonConfigOptions = { ...options, checksEnabled: false, isDev: false, baseUrl: resolveBaseUrl(options.frontendConfig), getFrontendAppConfigs: () => options.frontendAppConfigs, - additionalEntryPoints: detectedModulesEntryPoint, + }; + const configs = [ + await createConfig(paths, { + ...commonConfigOptions, + additionalEntryPoints: detectedModulesEntryPoint, + }), + ]; + + const publicPaths = await resolveOptionalBundlingPaths({ + entry: 'src/index-public-experimental', + dist: 'dist/public', }); + if (publicPaths) { + console.log( + chalk.yellow( + `⚠️ WARNING: The app /auth entry point is an experimental feature that may receive immediate breaking changes.`, + ), + ); + configs.push( + await createConfig(publicPaths, { + ...commonConfigOptions, + publicSubPath: '/public', + }), + ); + } const isCi = yn(process.env.CI, { default: false }); const previousFileSizes = await measureFileSizesBeforeBuild(paths.targetDist); + const previousAuthSizes = publicPaths + ? await measureFileSizesBeforeBuild(publicPaths.targetDist) + : undefined; await fs.emptyDir(paths.targetDist); if (paths.targetPublic) { @@ -76,33 +102,43 @@ export async function buildBundle(options: BuildOptions) { ); } - const { stats } = await build(config, isCi); + const { stats } = await build(configs, isCi); if (!stats) { throw new Error('No stats returned'); } + const [mainStats, authStats] = stats.stats; if (statsJsonEnabled) { // No @types/bfj await require('bfj').write( resolvePath(paths.targetDist, 'bundle-stats.json'), - stats.toJson(), + mainStats.toJson(), ); } printFileSizesAfterBuild( - stats, + mainStats, previousFileSizes, paths.targetDist, WARN_AFTER_BUNDLE_GZIP_SIZE, WARN_AFTER_CHUNK_GZIP_SIZE, ); + if (publicPaths && previousAuthSizes) { + printFileSizesAfterBuild( + authStats, + previousAuthSizes, + publicPaths.targetDist, + WARN_AFTER_BUNDLE_GZIP_SIZE, + WARN_AFTER_CHUNK_GZIP_SIZE, + ); + } } -async function build(config: webpack.Configuration, isCi: boolean) { - const stats = await new Promise( +async function build(configs: webpack.Configuration[], isCi: boolean) { + const stats = await new Promise( (resolve, reject) => { - webpack(config, (err, buildStats) => { + webpack(configs, (err, buildStats) => { if (err) { if (err.message) { const { errors } = formatWebpackMessages({ diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 542cc83ce9..7c72dc81f7 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -55,18 +55,24 @@ export function resolveBaseUrl(config: Config): URL { async function readBuildInfo() { const timestamp = Date.now(); - let commit = 'unknown'; + let commit: string | undefined; try { commit = await runPlain('git', 'rev-parse', 'HEAD'); } catch (error) { - console.warn(`WARNING: Failed to read git commit, ${error}`); + // ignore, see below } - let gitVersion = 'unknown'; + let gitVersion: string | undefined; try { gitVersion = await runPlain('git', 'describe', '--always'); } catch (error) { - console.warn(`WARNING: Failed to describe git version, ${error}`); + // ignore, see below + } + + if (commit === undefined || gitVersion === undefined) { + console.info( + 'NOTE: Did not compute git version or commit hash, could not execute the git command line utility', + ); } const { version: packageVersion } = await fs.readJson( @@ -75,10 +81,10 @@ async function readBuildInfo() { return { cliVersion: version, - gitVersion, + gitVersion: gitVersion ?? 'unknown', packageVersion, timestamp, - commit, + commit: commit ?? 'unknown', }; } @@ -86,7 +92,7 @@ export async function createConfig( paths: BundlingPaths, options: BundlingOptions, ): Promise { - const { checksEnabled, isDev, frontendConfig } = options; + const { checksEnabled, isDev, frontendConfig, publicSubPath = '' } = options; const { plugins, loaders } = transforms(options); // Any package that is part of the monorepo but outside the monorepo root dir need @@ -96,7 +102,11 @@ export async function createConfig( const baseUrl = frontendConfig.getString('app.baseUrl'); const validBaseUrl = new URL(baseUrl); - const publicPath = validBaseUrl.pathname.replace(/\/$/, ''); + let publicPath = validBaseUrl.pathname.replace(/\/$/, ''); + if (publicSubPath) { + publicPath = `${publicPath}${publicSubPath}`.replace('//', '/'); + } + if (checksEnabled) { plugins.push( new ForkTsCheckerWebpackPlugin({ diff --git a/packages/cli/src/lib/bundler/paths.ts b/packages/cli/src/lib/bundler/paths.ts index 3d4925c66b..572a96337f 100644 --- a/packages/cli/src/lib/bundler/paths.ts +++ b/packages/cli/src/lib/bundler/paths.ts @@ -23,6 +23,8 @@ export type BundlingPathsOptions = { entry: string; // Target directory, defaulting to paths.targetDir targetDir?: string; + // Relative dist directory, defaulting to 'dist' + dist?: string; }; export function resolveBundlingPaths(options: BundlingPathsOptions) { @@ -60,7 +62,7 @@ export function resolveBundlingPaths(options: BundlingPathsOptions) { targetPublic, targetPath: resolvePath(targetDir, '.'), targetRunFile: runFileExists ? targetRunFile : undefined, - targetDist: resolvePath(targetDir, 'dist'), + targetDist: resolvePath(targetDir, options.dist ?? 'dist'), targetAssets: resolvePath(targetDir, 'assets'), targetSrc: resolvePath(targetDir, 'src'), targetDev: resolvePath(targetDir, 'dev'), @@ -72,4 +74,14 @@ export function resolveBundlingPaths(options: BundlingPathsOptions) { }; } +export async function resolveOptionalBundlingPaths( + options: BundlingPathsOptions, +) { + const resolvedPaths = resolveBundlingPaths(options); + if (await fs.pathExists(resolvedPaths.targetEntry)) { + return resolvedPaths; + } + return undefined; +} + export type BundlingPaths = ReturnType; diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index bd70b2b074..f1eb2e923d 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -32,7 +32,7 @@ import { loadCliConfig } from '../config'; import { Lockfile } from '../versioning'; import { createConfig, resolveBaseUrl } from './config'; import { createDetectedModulesEntryPoint } from './packageDetection'; -import { resolveBundlingPaths } from './paths'; +import { resolveBundlingPaths, resolveOptionalBundlingPaths } from './paths'; import { ServeOptions } from './types'; import { hasReactDomClient } from './hasReactDomClient'; @@ -72,6 +72,20 @@ export async function serveBundle(options: ServeOptions) { ), ); } + + if ( + targetPkg.dependencies?.['react-router']?.includes('beta') || + targetPkg.dependencies?.['react-router-dom']?.includes('beta') + ) { + // eslint-disable-next-line no-console + console.warn( + chalk.yellow(` +DEPRECATION WARNING: React Router Beta is deprecated and support for it will be removed in a future release. + Please migrate to use React Router v6 stable. + See https://backstage.io/docs/tutorials/react-router-stable-migration +`), + ); + } } checkReactVersion(); @@ -133,7 +147,7 @@ export async function serveBundle(options: ServeOptions) { }, }); - const config = await createConfig(paths, { + const commonConfigOptions = { ...options, checksEnabled: options.checksEnabled, isDev: true, @@ -142,6 +156,10 @@ export async function serveBundle(options: ServeOptions) { getFrontendAppConfigs: () => { return latestFrontendAppConfigs; }, + }; + + const config = await createConfig(paths, { + ...commonConfigOptions, additionalEntryPoints: detectedModulesEntryPoint, }); @@ -185,7 +203,26 @@ export async function serveBundle(options: ServeOptions) { root: paths.targetPath, }); } else { - const compiler = webpack(config); + const publicPaths = await resolveOptionalBundlingPaths({ + entry: 'src/index-public-experimental', + dist: 'dist/public', + }); + if (publicPaths) { + console.log( + chalk.yellow( + `⚠️ WARNING: The app /public entry point is an experimental feature that may receive immediate breaking changes.`, + ), + ); + } + const compiler = publicPaths + ? webpack([ + config, + await createConfig(publicPaths, { + ...commonConfigOptions, + publicSubPath: '/public', + }), + ]) + : webpack(config); webpackServer = new WebpackDevServer( { diff --git a/packages/cli/src/lib/bundler/types.ts b/packages/cli/src/lib/bundler/types.ts index 414414fa81..4a9424dedb 100644 --- a/packages/cli/src/lib/bundler/types.ts +++ b/packages/cli/src/lib/bundler/types.ts @@ -26,6 +26,8 @@ export type BundlingOptions = { baseUrl: URL; parallelism?: number; additionalEntryPoints?: string[]; + // Path to append to the detected public path, e.g. '/public' + publicSubPath?: string; }; export type ServeOptions = BundlingPathsOptions & { diff --git a/packages/cli/src/lib/experimental/startBackendExperimental.ts b/packages/cli/src/lib/experimental/startBackendExperimental.ts index 6238b2277b..0ca3b182b5 100644 --- a/packages/cli/src/lib/experimental/startBackendExperimental.ts +++ b/packages/cli/src/lib/experimental/startBackendExperimental.ts @@ -29,7 +29,9 @@ import spawn from 'cross-spawn'; const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(Number); const supportsModuleLoaderRegister = - nodeMajor > 20 || (nodeMajor === 20 && nodeMinor >= 6); + nodeMajor > 20 || + (nodeMajor === 20 && nodeMinor >= 6) || + (nodeMajor === 18 && nodeMinor >= 19); const loaderArgs = [ '--require', diff --git a/packages/cli/src/lib/version.ts b/packages/cli/src/lib/version.ts index 96b42fcfb7..9c19a3a8c4 100644 --- a/packages/cli/src/lib/version.ts +++ b/packages/cli/src/lib/version.ts @@ -44,8 +44,8 @@ import { version as coreComponents } from '../../../../packages/core-components/ import { version as corePluginApi } from '../../../../packages/core-plugin-api/package.json'; import { version as devUtils } from '../../../../packages/dev-utils/package.json'; import { version as testUtils } from '../../../../packages/test-utils/package.json'; +import { version as scaffolderNode } from '../../../../plugins/scaffolder-node/package.json'; import { version as theme } from '../../../../packages/theme/package.json'; -import { version as scaffolderBackend } from '../../../../plugins/scaffolder-backend/package.json'; export const packageVersions: Record = { '@backstage/backend-common': backendCommon, @@ -59,7 +59,7 @@ export const packageVersions: Record = { '@backstage/dev-utils': devUtils, '@backstage/test-utils': testUtils, '@backstage/theme': theme, - '@backstage/plugin-scaffolder-backend': scaffolderBackend, + '@backstage/plugin-scaffolder-node': scaffolderNode, }; export function findVersion() { diff --git a/packages/cli/templates/default-backend-module/src/index.ts.hbs b/packages/cli/templates/default-backend-module/src/index.ts.hbs index 0cf1f00d42..7075598e13 100644 --- a/packages/cli/templates/default-backend-module/src/index.ts.hbs +++ b/packages/cli/templates/default-backend-module/src/index.ts.hbs @@ -5,4 +5,4 @@ * @packageDocumentation */ -export { {{moduleVar}} } from './module'; +export { {{moduleVar}} as default } from './module'; diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index 011a50ba37..fb86c7e792 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -38,15 +38,15 @@ "react-use": "{{versionQuery 'react-use' '17.2.4'}}" }, "peerDependencies": { - "react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0'}}" + "react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}" }, "devDependencies": { "@backstage/cli": "{{versionQuery '@backstage/cli'}}", "@backstage/core-app-api": "{{versionQuery '@backstage/core-app-api'}}", "@backstage/dev-utils": "{{versionQuery '@backstage/dev-utils'}}", "@backstage/test-utils": "{{versionQuery '@backstage/test-utils'}}", - "@testing-library/jest-dom": "{{versionQuery '@testing-library/jest-dom' '5.10.1'}}", - "@testing-library/react": "{{versionQuery '@testing-library/react' '12.1.3'}}", + "@testing-library/jest-dom": "{{versionQuery '@testing-library/jest-dom' '6.0.0'}}", + "@testing-library/react": "{{versionQuery '@testing-library/react' '14.0.0'}}", "@testing-library/user-event": "{{versionQuery '@testing-library/user-event' '14.0.0'}}", "msw": "{{versionQuery 'msw' '1.0.0'}}" }, diff --git a/packages/cli/templates/default-react-plugin-package/package.json.hbs b/packages/cli/templates/default-react-plugin-package/package.json.hbs index 230ecc5f03..c5e92eb7d6 100644 --- a/packages/cli/templates/default-react-plugin-package/package.json.hbs +++ b/packages/cli/templates/default-react-plugin-package/package.json.hbs @@ -34,13 +34,13 @@ "@material-ui/core": "{{versionQuery '@material-ui/core' '4.12.2'}}" }, "peerDependencies": { - "react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0'}}" + "react": "{{versionQuery 'react' '^16.13.1 || ^17.0.0 || ^18.0.0'}}" }, "devDependencies": { "@backstage/cli": "{{versionQuery '@backstage/cli'}}", "@backstage/test-utils": "{{versionQuery '@backstage/test-utils'}}", - "@testing-library/jest-dom": "{{versionQuery '@testing-library/jest-dom' '5.10.1'}}", - "@testing-library/react": "{{versionQuery '@testing-library/react' '12.1.3'}}" + "@testing-library/jest-dom": "{{versionQuery '@testing-library/jest-dom' '6.0.0'}}", + "@testing-library/react": "{{versionQuery '@testing-library/react' '14.0.0'}}" }, "files": [ "dist" diff --git a/packages/cli/templates/scaffolder-module/package.json.hbs b/packages/cli/templates/scaffolder-module/package.json.hbs index 618e953b74..0ebe5dd5f4 100644 --- a/packages/cli/templates/scaffolder-module/package.json.hbs +++ b/packages/cli/templates/scaffolder-module/package.json.hbs @@ -29,7 +29,7 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/plugin-scaffolder-backend": "{{versionQuery '@backstage/plugin-scaffolder-backend'}}" + "@backstage/plugin-scaffolder-node": "{{versionQuery '@backstage/plugin-scaffolder-node'}}" }, "devDependencies": { "@backstage/backend-common": "{{versionQuery '@backstage/backend-common'}}", diff --git a/packages/cli/templates/scaffolder-module/src/actions/example/example.ts b/packages/cli/templates/scaffolder-module/src/actions/example/example.ts index c04132e755..2b05c7fe38 100644 --- a/packages/cli/templates/scaffolder-module/src/actions/example/example.ts +++ b/packages/cli/templates/scaffolder-module/src/actions/example/example.ts @@ -1,4 +1,4 @@ -import { createTemplateAction } from '@backstage/plugin-scaffolder-backend'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; /** * Creates an `acme:example` Scaffolder action. diff --git a/packages/cli/templates/web-library-package/package.json.hbs b/packages/cli/templates/web-library-package/package.json.hbs index f9f703f523..271417a525 100644 --- a/packages/cli/templates/web-library-package/package.json.hbs +++ b/packages/cli/templates/web-library-package/package.json.hbs @@ -30,7 +30,7 @@ }, "devDependencies": { "@backstage/cli": "{{versionQuery '@backstage/cli'}}", - "@testing-library/jest-dom": "{{versionQuery '@testing-library/jest-dom' '5.10.1'}}" + "@testing-library/jest-dom": "{{versionQuery '@testing-library/jest-dom' '6.0.0'}}" }, "files": [ "dist" diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index 169e2e45df..24f7a3addc 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/config-loader +## 1.6.1 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.6.1-next.0 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.6.0 + +### Minor Changes + +- 24f5a85: Add "path" to `TransformFunc` context + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 1.6.0-next.0 + +### Minor Changes + +- 24f5a85: Add "path" to `TransformFunc` context + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + ## 1.5.3 ### Patch Changes diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index 9f55a27bf7..66572dd960 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -274,6 +274,7 @@ export type TransformFunc = ( value: T, context: { visibility: ConfigVisibility; + path: string; }, ) => T | undefined; ``` diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 8a41d7548f..e26a54559c 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "1.5.3", + "version": "1.6.1", "publishConfig": { "access": "public", "main": "dist/index.cjs.js", @@ -54,7 +54,6 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/json-schema-merge-allof": "^0.6.0", - "@types/mock-fs": "^4.10.0", "msw": "^1.0.0", "zen-observable": "^0.10.0" }, diff --git a/packages/config-loader/src/schema/filtering.ts b/packages/config-loader/src/schema/filtering.ts index 1b7bbf43c1..9c1c2fbda5 100644 --- a/packages/config-loader/src/schema/filtering.ts +++ b/packages/config-loader/src/schema/filtering.ts @@ -68,7 +68,7 @@ export function filterByVisibility( if (typeof jsonVal !== 'object') { if (isVisible) { if (transformFunc) { - return transformFunc(jsonVal, { visibility }); + return transformFunc(jsonVal, { visibility, path: filterPath }); } return jsonVal; } diff --git a/packages/config-loader/src/schema/types.ts b/packages/config-loader/src/schema/types.ts index dc8da64038..8f679c93fc 100644 --- a/packages/config-loader/src/schema/types.ts +++ b/packages/config-loader/src/schema/types.ts @@ -105,11 +105,22 @@ export type ValidationFunc = (configs: AppConfig[]) => ValidationResult; /** * A function used to transform primitive configuration values. * + * The "path" in the context is a JQ-style path to the current value from + * within the original object passed to filterByVisibility(). + * For example, "field.list[2]" would refer to: + * \{ + * field: [ + * "foo", + * "bar", + * "baz" -- this one + * ] + * \} + * * @public */ export type TransformFunc = ( value: T, - context: { visibility: ConfigVisibility }, + context: { visibility: ConfigVisibility; path: string }, ) => T | undefined; /** diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index f77c607e3f..9e32db5016 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/core-app-api +## 1.11.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.11.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.11.2 + +### Patch Changes + +- 3e358b0: Added deprecation warning for React Router v6 beta, please make sure you have migrated your apps to use React Router v6 stable as support for the beta version will be removed. See the [migration tutorial](https://backstage.io/docs/tutorials/react-router-stable-migration) for more information. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.11.2-next.1 + +### Patch Changes + +- 3e358b0dff: Added deprecation warning for React Router v6 beta, please make sure you have migrated your apps to use React Router v6 stable as support for the beta version will be removed. See the [migration tutorial](https://backstage.io/docs/tutorials/react-router-stable-migration) for more information. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + ## 1.11.2-next.0 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 0efa8b3bb8..f0654f7596 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "1.11.2-next.0", + "version": "1.11.3", "publishConfig": { "access": "public" }, @@ -54,7 +54,7 @@ "prop-types": "^15.7.2", "react-use": "^17.2.4", "zen-observable": "^0.10.0", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 37407283b5..c8cdfd7c23 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -240,11 +240,21 @@ export class AppManager implements BackstageApp { ); const { routing, featureFlags, routeBindings } = useMemo(() => { + const usesReactRouterBeta = isReactRouterBeta(); + if (usesReactRouterBeta) { + // eslint-disable-next-line no-console + console.warn(` +DEPRECATION WARNING: React Router Beta is deprecated and support for it will be removed in a future release. + Please migrate to use React Router v6 stable. + See https://backstage.io/docs/tutorials/react-router-stable-migration +`); + } + const result = traverseElementTree({ root: children, discoverers: [childDiscoverer, routeElementDiscoverer], collectors: { - routing: isReactRouterBeta() + routing: usesReactRouterBeta ? routingV1Collector : routingV2Collector, collectedPlugins: pluginCollector, diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index 017e3ec785..a8abcbc8fc 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,118 @@ # @backstage/core-compat-api +## 0.1.2-next.0 + +### Patch Changes + +- 1fa5041: The backwards compatibility provider will now use the new `ComponentsApi` and `IconsApi` when implementing the old `AppContext`. +- 7155c30: Added `convertLegacyRouteRefs` for bulk conversion of plugin routes. +- 2f2a1d2: Plugins converted by `convertLegacyApp` now have their `routes` and `externalRoutes` included as well, allowing them to be used to bind external routes in configuration. +- 1184990: collectLegacyRoutes throws in case invalid element is found +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/version-bridge@1.0.7 + +## 0.1.1 + +### Patch Changes + +- 4c1f50c: Make `convertLegacyApp` wrap discovered routes with `compatWrapper`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-plugin-api@1.8.2 + - @backstage/core-app-api@1.11.3 + - @backstage/version-bridge@1.0.7 + +## 0.1.1-next.2 + +### Patch Changes + +- 4c1f50c: Make `convertLegacyApp` wrap discovered routes with `compatWrapper`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/version-bridge@1.0.7 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/version-bridge@1.0.7 + +## 0.1.0 + +### Minor Changes + +- cf5cc4c: Discover plugins and routes recursively beneath the root routes in `collectLecacyRoutes` +- af7bc3e: Switched all core extensions to instead use the namespace `'app'`. +- f63dd72: The `collectLegacyRoutes` has been removed and is replaced by `convertLegacyApp` now being able to convert a `FlatRoutes` element directly. + +### Patch Changes + +- 03d0b6d: Added `convertLegacyRouteRef` utility to convert existing route refs to be used with the new experimental packages. +- a379243: Leverage the new `FrontendFeature` type to simplify interfaces +- 8226442: Added `compatWrapper`, which can be used to wrap any React element to provide bi-directional interoperability between the `@backstage/core-*-api` and `@backstage/frontend-*-api` APIs. +- 8f5d6c1: Updates to match the new extension input wrapping. +- c219b16: Made package public so it can be published +- b7adf24: Delete alpha DI compatibility helper for components, migrating components should be simple without a helper. +- 046e443: Updates for compatibility with the new extension IDs. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-app-api@1.11.2 + - @backstage/version-bridge@1.0.7 + +## 0.1.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/version-bridge@1.0.7 + +## 0.1.0-next.2 + +### Minor Changes + +- cf5cc4c: Discover plugins and routes recursively beneath the root routes in `collectLecacyRoutes` + +### Patch Changes + +- 8226442: Added `compatWrapper`, which can be used to wrap any React element to provide bi-directional interoperability between the `@backstage/core-*-api` and `@backstage/frontend-*-api` APIs. +- 8f5d6c1: Updates to match the new extension input wrapping. +- b7adf24: Delete alpha DI compatibility helper for components, migrating components should be simple without a helper. +- 046e443: Updates for compatibility with the new extension IDs. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/version-bridge@1.0.7 + +## 0.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/core-app-api@1.11.2-next.1 + ## 0.0.1-next.0 ### Patch Changes diff --git a/packages/core-compat-api/api-report.md b/packages/core-compat-api/api-report.md index 877aefa24f..a7aae2b5b9 100644 --- a/packages/core-compat-api/api-report.md +++ b/packages/core-compat-api/api-report.md @@ -3,28 +3,24 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -/// - import { AnyRouteRefParams } from '@backstage/core-plugin-api'; -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; -import { ExtensionOverrides } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { ExternalRouteRef as ExternalRouteRef_2 } from '@backstage/frontend-plugin-api'; +import { FrontendFeature } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; +import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api'; import { SubRouteRef } from '@backstage/core-plugin-api'; import { SubRouteRef as SubRouteRef_2 } from '@backstage/frontend-plugin-api'; -// @public (undocumented) -export function collectLegacyRoutes( - flatRoutesElement: JSX.Element, -): BackstagePlugin[]; +// @public +export function compatWrapper(element: ReactNode): React_2.JSX.Element; // @public (undocumented) export function convertLegacyApp( rootElement: React_2.JSX.Element, -): (ExtensionOverrides | BackstagePlugin)[]; +): FrontendFeature[]; // @public export function convertLegacyRouteRef( @@ -44,5 +40,26 @@ export function convertLegacyRouteRef< ref: ExternalRouteRef, ): ExternalRouteRef_2; +// @public +export function convertLegacyRouteRefs< + TRefs extends { + [name in string]: RouteRef | SubRouteRef | ExternalRouteRef; + }, +>( + refs: TRefs, +): { + [KName in keyof TRefs]: ToNewRouteRef; +}; + +// @public +export type ToNewRouteRef = + T extends RouteRef + ? RouteRef_2 + : T extends SubRouteRef + ? SubRouteRef_2 + : T extends ExternalRouteRef + ? ExternalRouteRef_2 + : never; + // (No @packageDocumentation comment for this package) ``` diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index f887193616..275c872661 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-compat-api", - "version": "0.0.1-next.0", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,24 +22,29 @@ "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack" }, - "devDependencies": { - "@backstage/cli": "workspace:^", - "@backstage/plugin-puppetdb": "workspace:^", - "@backstage/plugin-stackstorm": "workspace:^", - "@oriflame/backstage-plugin-score-card": "^0.7.0", - "@testing-library/jest-dom": "^6.0.0" - }, - "files": [ - "dist" - ], - "peerDependencies": { - "react": "^16.13.1 || ^17.0.0 || ^18.0.0", - "react-router-dom": "6.0.0-beta.0 || ^6.3.0" - }, "dependencies": { "@backstage/core-app-api": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", + "@backstage/version-bridge": "workspace:^", "@types/react": "^16.13.1 || ^17.0.0" - } + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-router-dom": "6.0.0-beta.0 || ^6.3.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/frontend-app-api": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", + "@backstage/plugin-catalog": "workspace:^", + "@backstage/plugin-puppetdb": "workspace:^", + "@backstage/plugin-stackstorm": "workspace:^", + "@oriflame/backstage-plugin-score-card": "^0.7.0", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^14.0.0" + }, + "files": [ + "dist" + ] } diff --git a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx index 85fed83e8c..2c8288b878 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx @@ -15,13 +15,31 @@ */ import { FlatRoutes } from '@backstage/core-app-api'; +import { + CatalogEntityPage, + CatalogIndexPage, + EntityAboutCard, + EntityLayout, + EntitySwitch, + isKind, +} from '@backstage/plugin-catalog'; import { PuppetDbPage } from '@backstage/plugin-puppetdb'; import { StackstormPage } from '@backstage/plugin-stackstorm'; import { ScoreBoardPage } from '@oriflame/backstage-plugin-score-card'; -import React from 'react'; -import { Route } from 'react-router-dom'; +import React, { Fragment } from 'react'; +import { Navigate, Route, Routes } from 'react-router-dom'; import { collectLegacyRoutes } from './collectLegacyRoutes'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalBackstagePlugin } from '../../frontend-plugin-api/src/wiring/createPlugin'; +import { + createPlugin, + createRoutableExtension, + createRouteRef, + useApp, +} from '@backstage/core-plugin-api'; +import { createSpecializedApp } from '@backstage/frontend-app-api'; +import { render, screen } from '@testing-library/react'; describe('collectLegacyRoutes', () => { it('should collect legacy routes', () => { @@ -37,7 +55,7 @@ describe('collectLegacyRoutes', () => { expect( collected.map(p => ({ id: p.id, - extensions: p.extensions.map(e => ({ + extensions: toInternalBackstagePlugin(p).extensions.map(e => ({ id: e.id, attachTo: e.attachTo, disabled: e.disabled, @@ -49,14 +67,14 @@ describe('collectLegacyRoutes', () => { id: 'score-card', extensions: [ { - id: 'plugin.score-card.page', - attachTo: { id: 'core.routes', input: 'routes' }, + id: 'page:score-card', + attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, defaultConfig: { path: 'score-board' }, }, { - id: 'apis.plugin.scoringdata.service', - attachTo: { id: 'core', input: 'apis' }, + id: 'api:plugin.scoringdata.service', + attachTo: { id: 'app', input: 'apis' }, disabled: false, }, ], @@ -65,14 +83,14 @@ describe('collectLegacyRoutes', () => { id: 'stackstorm', extensions: [ { - id: 'plugin.stackstorm.page', - attachTo: { id: 'core.routes', input: 'routes' }, + id: 'page:stackstorm', + attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, defaultConfig: { path: 'stackstorm' }, }, { - id: 'apis.plugin.stackstorm.service', - attachTo: { id: 'core', input: 'apis' }, + id: 'api:plugin.stackstorm.service', + attachTo: { id: 'app', input: 'apis' }, disabled: false, }, ], @@ -81,24 +99,278 @@ describe('collectLegacyRoutes', () => { id: 'puppetDb', extensions: [ { - id: 'plugin.puppetDb.page', - attachTo: { id: 'core.routes', input: 'routes' }, + id: 'page:puppetDb', + attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, defaultConfig: { path: 'puppetdb' }, }, { - id: 'plugin.puppetDb.page2', - attachTo: { id: 'core.routes', input: 'routes' }, + id: 'page:puppetDb/1', + attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, defaultConfig: { path: 'puppetdb' }, }, { - id: 'apis.plugin.puppetdb.service', - attachTo: { id: 'core', input: 'apis' }, + id: 'api:plugin.puppetdb.service', + attachTo: { id: 'app', input: 'apis' }, disabled: false, }, ], }, ]); }); + + it('supports recursion into children, including passing through fragments', () => { + const collected = collectLegacyRoutes( + + } /> + } + > + + + + + } + /> + + + + + + + + + + + + + + + + , + ); + + expect( + collected.map(p => ({ + id: p.id, + extensions: toInternalBackstagePlugin(p).extensions.map(e => ({ + id: e.id, + attachTo: e.attachTo, + disabled: e.disabled, + defaultConfig: e.configSchema?.parse({}), + })), + })), + ).toEqual([ + { + id: 'catalog', + extensions: [ + { + id: 'page:catalog', + attachTo: { id: 'app/routes', input: 'routes' }, + disabled: false, + defaultConfig: { path: 'catalog' }, + }, + { + id: 'page:catalog/1', + attachTo: { id: 'app/routes', input: 'routes' }, + defaultConfig: { path: 'catalog/:namespace/:kind/:name' }, + disabled: false, + }, + { + id: 'routing-shim:catalog/2', + attachTo: { + id: 'page:catalog/1', + input: 'childRoutingShims', + }, + defaultConfig: undefined, + disabled: false, + }, + { + id: 'routing-shim:catalog/3', + attachTo: { + id: 'routing-shim:catalog/2', + input: 'childRoutingShims', + }, + defaultConfig: undefined, + disabled: false, + }, + { + id: 'routing-shim:catalog/4', + attachTo: { + id: 'routing-shim:catalog/3', + input: 'childRoutingShims', + }, + defaultConfig: undefined, + disabled: false, + }, + { + id: 'api:plugin.catalog.service', + attachTo: { + id: 'app', + input: 'apis', + }, + defaultConfig: undefined, + disabled: false, + }, + { + id: 'api:catalog-react.starred-entities', + attachTo: { + id: 'app', + input: 'apis', + }, + defaultConfig: undefined, + disabled: false, + }, + { + id: 'api:plugin.catalog.entity-presentation', + attachTo: { + id: 'app', + input: 'apis', + }, + defaultConfig: undefined, + disabled: false, + }, + ], + }, + { + id: 'score-card', + extensions: [ + { + id: 'api:plugin.scoringdata.service', + attachTo: { id: 'app', input: 'apis' }, + disabled: false, + }, + ], + }, + ]); + }); + + it('should make legacy APIs available', async () => { + const plugin = createPlugin({ + id: 'test', + }); + const routeRef = createRouteRef({ id: 'test' }); + const Page = plugin.provide( + createRoutableExtension({ + name: 'Test', + mountPoint: routeRef, + component: () => + Promise.resolve(() => { + const app = useApp(); + return
plugins: {app.getPlugins().map(p => p.getId())}
; + }), + }), + ); + + const features = collectLegacyRoutes( + + } /> + , + ); + + render(createSpecializedApp({ features }).createRoot()); + + await expect( + screen.findByText('plugins: test'), + ).resolves.toBeInTheDocument(); + }); + + it('should throw if invalid Route has been detected', async () => { + const plugin = createPlugin({ + id: 'test', + }); + const routeRef = createRouteRef({ id: 'test' }); + const Page = plugin.provide( + createRoutableExtension({ + name: 'Test', + mountPoint: routeRef, + component: async () => () => { + const app = useApp(); + return
plugins: {app.getPlugins().map(p => p.getId())}
; + }, + }), + ); + + expect(() => + collectLegacyRoutes( + + } /> + } /> +
+ , + ), + ).toThrow( + /Invalid element inside FlatRoutes, expected Route but found div./, + ); + }); + + it('should throw if invalid element has been detected', async () => { + const plugin = createPlugin({ + id: 'test', + }); + const routeRef = createRouteRef({ id: 'test' }); + const Page = plugin.provide( + createRoutableExtension({ + name: 'Test', + mountPoint: routeRef, + component: async () => () => { + const app = useApp(); + return
plugins: {app.getPlugins().map(p => p.getId())}
; + }, + }), + ); + + expect(() => + collectLegacyRoutes( + + } />a string + , + ), + ).toThrow( + /Invalid element inside FlatRoutes, expected Route but found element of type string./, + ); + }); + + it('should throw if has no path', async () => { + const plugin = createPlugin({ + id: 'test', + }); + const routeRef = createRouteRef({ id: 'test' }); + const Page = plugin.provide( + createRoutableExtension({ + name: 'Test', + mountPoint: routeRef, + component: () => + Promise.resolve(() => { + const app = useApp(); + return
plugins: {app.getPlugins().map(p => p.getId())}
; + }), + }), + ); + + expect(() => + collectLegacyRoutes( + + } /> + , + ), + ).toThrow(/Route element inside FlatRoutes had no path prop value given/); + }); + + it('should throw if element cannot be converted', async () => { + expect(() => + collectLegacyRoutes( + + } /> + , + ), + ).toThrow( + /Route with path undefined has en element that can not be converted/, + ); + }); }); diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx index 037107bfda..b9f4e5528c 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx @@ -14,21 +14,29 @@ * limitations under the License. */ -import React, { ReactNode } from 'react'; -import { - Extension, - createApiExtension, - createPageExtension, - createPlugin, - BackstagePlugin, -} from '@backstage/frontend-plugin-api'; -import { Route, Routes } from 'react-router-dom'; import { + AnyRouteRefParams, BackstagePlugin as LegacyBackstagePlugin, RouteRef, getComponentData, } from '@backstage/core-plugin-api'; -import { convertLegacyRouteRef } from './convertLegacyRouteRef'; +import { + BackstagePlugin, + ExtensionDefinition, + coreExtensionData, + createApiExtension, + createExtension, + createExtensionInput, + createPageExtension, + createPlugin, +} from '@backstage/frontend-plugin-api'; +import React, { Children, ReactNode, isValidElement } from 'react'; +import { Route, Routes } from 'react-router-dom'; +import { + convertLegacyRouteRef, + convertLegacyRouteRefs, +} from './convertLegacyRouteRef'; +import { compatWrapper } from './compatWrapper'; /* @@ -58,85 +66,203 @@ Existing tasks: */ -/** @public */ +// Creates a shim extension whose purpose is to build up the tree (anchored at +// the root page) of paths/routeRefs so that the app can bind them properly. +function makeRoutingShimExtension(options: { + name: string; + parentExtensionId: string; + routePath?: string; + routeRef?: RouteRef; +}) { + const { name, parentExtensionId, routePath, routeRef } = options; + return createExtension({ + kind: 'routing-shim', + name, + attachTo: { id: parentExtensionId, input: 'childRoutingShims' }, + inputs: { + childRoutingShims: createExtensionInput({ + routePath: coreExtensionData.routePath.optional(), + routeRef: coreExtensionData.routeRef.optional(), + }), + }, + output: { + routePath: coreExtensionData.routePath.optional(), + routeRef: coreExtensionData.routeRef.optional(), + }, + factory: () => ({ + routePath, + routeRef: routeRef ? convertLegacyRouteRef(routeRef) : undefined, + }), + }); +} + +function visitRouteChildren(options: { + children: ReactNode; + parentExtensionId: string; + context: { + pluginId: string; + extensions: ExtensionDefinition[]; + getUniqueName: () => string; + discoverPlugin: (plugin: LegacyBackstagePlugin) => void; + }; +}): void { + const { children, parentExtensionId, context } = options; + const { pluginId, extensions, getUniqueName, discoverPlugin } = context; + + Children.forEach(children, node => { + if (!isValidElement(node)) { + return; + } + + const plugin = getComponentData(node, 'core.plugin'); + const routeRef = getComponentData>( + node, + 'core.mountPoint', + ); + const routePath: string | undefined = node.props?.path; + + if (plugin) { + // We just mark the plugin as discovered, but don't change the context + discoverPlugin(plugin); + } + + let nextParentExtensionId = parentExtensionId; + if (routeRef || routePath) { + const nextParentExtensionName = getUniqueName(); + nextParentExtensionId = `routing-shim:${pluginId}/${nextParentExtensionName}`; + extensions.push( + makeRoutingShimExtension({ + name: nextParentExtensionName, + parentExtensionId, + routePath, + routeRef, + }), + ); + } + + visitRouteChildren({ + children: node.props.children, + parentExtensionId: nextParentExtensionId, + context, + }); + }); +} + +/** @internal */ export function collectLegacyRoutes( flatRoutesElement: JSX.Element, ): BackstagePlugin[] { - const createdPluginIds = new Map< + const pluginExtensions = new Map< LegacyBackstagePlugin, - Extension[] + ExtensionDefinition[] >(); + const getUniqueName = (() => { + let currentIndex = 1; + return () => String(currentIndex++); + })(); + + const getPluginExtensions = (plugin: LegacyBackstagePlugin) => { + let extensions = pluginExtensions.get(plugin); + if (!extensions) { + extensions = []; + pluginExtensions.set(plugin, extensions); + } + return extensions; + }; + React.Children.forEach( flatRoutesElement.props.children, (route: ReactNode) => { - if (!React.isValidElement(route)) { - return; - } - // TODO(freben): Handle feature flag and permissions framework wrapper elements - if (route.type !== Route) { - return; + if (!React.isValidElement(route)) { + throw new Error( + `Invalid element inside FlatRoutes, expected Route but found element of type ${typeof route}.`, + ); + } + if (route.type !== Route) { + throw new Error( + `Invalid element inside FlatRoutes, expected Route but found ${route.type}.`, + ); } - const routeElement = route.props.element; - - // TODO: to support deeper extension component, e.g. hidden within , use https://github.com/backstage/backstage/blob/518a34646b79ec2028cc0ed6bc67d4366c51c4d6/packages/core-app-api/src/routing/collectors.tsx#L69 + const path: string | undefined = route.props.path; const plugin = getComponentData( routeElement, 'core.plugin', ); - if (!plugin) { - return; - } - const routeRef = getComponentData( routeElement, 'core.mountPoint', ); + if (!plugin) { + throw new Error( + // TODO(vinzscam): add See for more info + `Route with path ${path} has en element that can not be converted as it does not belong to a plugin. Make sure that the top-level React element of the element prop is an extension from a Backstage plugin, or remove the Route completely.`, + ); + } + if (path === undefined) { + throw new Error( + `Route element inside FlatRoutes had no path prop value given`, + ); + } - const pluginId = plugin.getId(); + const extensions = getPluginExtensions(plugin); + const pageExtensionName = extensions.length ? getUniqueName() : undefined; + const pageExtensionId = `page:${plugin.getId()}${ + pageExtensionName ? `/${pageExtensionName}` : pageExtensionName + }`; - const detectedExtensions = - createdPluginIds.get(plugin) ?? new Array>(); - createdPluginIds.set(plugin, detectedExtensions); - - const path: string = route.props.path; - - detectedExtensions.push( + extensions.push( createPageExtension({ - id: `plugin.${pluginId}.page${ - detectedExtensions.length ? detectedExtensions.length + 1 : '' - }`, + name: pageExtensionName, defaultPath: path[0] === '/' ? path.slice(1) : path, routeRef: routeRef ? convertLegacyRouteRef(routeRef) : undefined, - + inputs: { + childRoutingShims: createExtensionInput({ + routePath: coreExtensionData.routePath.optional(), + routeRef: coreExtensionData.routeRef.optional(), + }), + }, loader: async () => - route.props.children ? ( - - - - - - ) : ( - routeElement + compatWrapper( + route.props.children ? ( + + + + + + ) : ( + routeElement + ), ), }), ); + + visitRouteChildren({ + children: route.props.children, + parentExtensionId: pageExtensionId, + context: { + pluginId: plugin.getId(), + extensions, + getUniqueName, + discoverPlugin: getPluginExtensions, + }, + }); }, ); - return Array.from(createdPluginIds).map(([plugin, extensions]) => + return Array.from(pluginExtensions).map(([plugin, extensions]) => createPlugin({ id: plugin.getId(), extensions: [ ...extensions, ...Array.from(plugin.getApis()).map(factory => - createApiExtension({ - factory, - }), + createApiExtension({ factory }), ), ], + routes: convertLegacyRouteRefs(plugin.routes ?? {}), + externalRoutes: convertLegacyRouteRefs(plugin.externalRoutes ?? {}), }), ); } diff --git a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx new file mode 100644 index 0000000000..288f3f900b --- /dev/null +++ b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx @@ -0,0 +1,207 @@ +/* + * 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, { useMemo } from 'react'; +import { ReactNode } from 'react'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { AppContextProvider } from '../../../core-app-api/src/app/AppContext'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { RouteResolver } from '../../../core-plugin-api/src/routing/useRouteRef'; +import { + createPlugin as createNewPlugin, + BackstagePlugin as NewBackstagePlugin, + appTreeApiRef, + componentsApiRef, + coreComponentRefs, + iconsApiRef, + useApi, + routeResolutionApiRef, +} from '@backstage/frontend-plugin-api'; +import { + AppComponents, + IconComponent, + BackstagePlugin as LegacyBackstagePlugin, + RouteRef, +} from '@backstage/core-plugin-api'; +import { + VersionedValue, + createVersionedContext, + createVersionedValueMap, + getOrCreateGlobalSingleton, +} from '@backstage/version-bridge'; +import { convertLegacyRouteRef } from '../convertLegacyRouteRef'; + +// Make sure that we only convert each new plugin instance to its legacy equivalent once +const legacyPluginStore = getOrCreateGlobalSingleton( + 'legacy-plugin-compatibility-store', + () => new WeakMap(), +); + +function toLegacyPlugin(plugin: NewBackstagePlugin): LegacyBackstagePlugin { + let legacy = legacyPluginStore.get(plugin); + if (legacy) { + return legacy; + } + + const errorMsg = 'Not implemented in legacy plugin compatibility layer'; + const notImplemented = () => { + throw new Error(errorMsg); + }; + + legacy = { + getId(): string { + return plugin.id; + }, + get routes() { + return {}; + }, + get externalRoutes() { + return {}; + }, + getApis: notImplemented, + getFeatureFlags: notImplemented, + provide: notImplemented, + }; + + legacyPluginStore.set(plugin, legacy); + return legacy; +} + +// TODO: Currently a very naive implementation, may need some more work +function toNewPlugin(plugin: LegacyBackstagePlugin): NewBackstagePlugin { + return createNewPlugin({ + id: plugin.getId(), + }); +} + +// Recreates the old AppContext APIs using the various new APIs that replaced it +function LegacyAppContextProvider(props: { children: ReactNode }) { + const appTreeApi = useApi(appTreeApiRef); + const componentsApi = useApi(componentsApiRef); + const iconsApi = useApi(iconsApiRef); + + const appContext = useMemo(() => { + const { tree } = appTreeApi.getTree(); + + let gatheredPlugins: LegacyBackstagePlugin[] | undefined = undefined; + + const ErrorBoundaryFallback = componentsApi.getComponent( + coreComponentRefs.errorBoundaryFallback, + ); + const ErrorBoundaryFallbackWrapper: AppComponents['ErrorBoundaryFallback'] = + ({ plugin, ...rest }) => ( + + ); + + return { + getPlugins(): LegacyBackstagePlugin[] { + if (gatheredPlugins) { + return gatheredPlugins; + } + + const pluginSet = new Set(); + for (const node of tree.nodes.values()) { + const plugin = node.spec.source; + if (plugin) { + pluginSet.add(toLegacyPlugin(plugin)); + } + } + gatheredPlugins = Array.from(pluginSet); + + return gatheredPlugins; + }, + + getSystemIcon(key: string): IconComponent | undefined { + return iconsApi.getIcon(key); + }, + + getSystemIcons(): Record { + return Object.fromEntries( + iconsApi.listIconKeys().map(key => [key, iconsApi.getIcon(key)!]), + ); + }, + + getComponents(): AppComponents { + return { + NotFoundErrorPage: componentsApi.getComponent( + coreComponentRefs.notFoundErrorPage, + ), + BootErrorPage() { + throw new Error( + 'The BootErrorPage app component should not be accessed by plugins', + ); + }, + Progress: componentsApi.getComponent(coreComponentRefs.progress), + Router() { + throw new Error( + 'The Router app component should not be accessed by plugins', + ); + }, + ErrorBoundaryFallback: ErrorBoundaryFallbackWrapper, + }; + }, + }; + }, [appTreeApi, componentsApi, iconsApi]); + + return ( + + {props.children} + + ); +} + +const RoutingContext = createVersionedContext<{ 1: RouteResolver }>( + 'routing-context', +); + +function LegacyRoutingProvider(props: { children: ReactNode }) { + const routeResolutionApi = useApi(routeResolutionApiRef); + + const value = useMemo>(() => { + return createVersionedValueMap({ + 1: { + resolve(anyRouteRef, location) { + const sourcePath = + typeof location === 'string' ? location : location.pathname ?? ''; + + return routeResolutionApi.resolve( + // This removes the requirement to use convertLegacyRouteRef inside plugins, but + // they still need to converted when passed to the plugin instance + convertLegacyRouteRef(anyRouteRef as RouteRef), + { sourcePath }, + ); + }, + }, + }); + }, [routeResolutionApi]); + + return ( + + {props.children} + + ); +} + +export function BackwardsCompatProvider(props: { children: ReactNode }) { + return ( + + {props.children} + + ); +} diff --git a/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx new file mode 100644 index 0000000000..5544862aee --- /dev/null +++ b/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx @@ -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. + */ + +import React from 'react'; +import { ReactNode } from 'react'; + +export function ForwardsCompatProvider(props: { children: ReactNode }) { + // TODO(Rugvip): Implement + return <>{props.children}; +} diff --git a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx new file mode 100644 index 0000000000..74b9f209eb --- /dev/null +++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx @@ -0,0 +1,90 @@ +/* + * 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 { + coreExtensionData, + createExtension, +} from '@backstage/frontend-plugin-api'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import { compatWrapper } from './compatWrapper'; +import { + createRouteRef, + useApp, + useRouteRef, +} from '@backstage/core-plugin-api'; +import { convertLegacyRouteRef } from '../convertLegacyRouteRef'; + +describe('BackwardsCompatProvider', () => { + it('should convert the app context', () => { + // TODO(Rugvip): Replace with the new renderInTestApp once it's available, and have some plugins + createExtensionTester( + createExtension({ + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + element: coreExtensionData.reactElement, + }, + factory() { + function Component() { + const app = useApp(); + return ( +
+ plugins: + {app + .getPlugins() + .map(p => p.getId()) + .join(', ')} + {'\n'} + components: {Object.keys(app.getComponents()).join(', ')} + {'\n'} + icons: {Object.keys(app.getSystemIcons()).join(', ')} +
+ ); + } + + return { + element: compatWrapper(), + }; + }, + }), + ).render(); + + expect(screen.getByTestId('ctx').textContent).toMatchInlineSnapshot(` + "plugins: + components: NotFoundErrorPage, BootErrorPage, Progress, Router, ErrorBoundaryFallback + icons: brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, user, warning" + `); + }); + + it('should convert the routing context', () => { + const routeRef = createRouteRef({ id: 'test' }); + + function Component() { + const link = useRouteRef(routeRef); + return
link: {link()}
; + } + + renderInTestApp(compatWrapper(), { + mountedRoutes: { '/test': convertLegacyRouteRef(routeRef) }, + }); + + expect(screen.getByText('link: /test')).toBeInTheDocument(); + }); +}); diff --git a/packages/core-compat-api/src/compatWrapper/compatWrapper.tsx b/packages/core-compat-api/src/compatWrapper/compatWrapper.tsx new file mode 100644 index 0000000000..cd7378a384 --- /dev/null +++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.tsx @@ -0,0 +1,42 @@ +/* + * 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 { useVersionedContext } from '@backstage/version-bridge'; +import { ReactNode } from 'react'; +import { BackwardsCompatProvider } from './BackwardsCompatProvider'; +import { ForwardsCompatProvider } from './ForwardsCompatProvider'; + +function BidirectionalCompatProvider(props: { children: ReactNode }) { + const isInNewApp = !useVersionedContext<{ 1: unknown }>('app-context'); + + if (isInNewApp) { + return ; + } + + return ; +} + +/** + * Wraps a React element in a bidirectional compatibility provider, allow APIs + * from `@backstage/core-plugin-api` to be used in an app from `@backstage/frontend-app-api`, + * and APIs from `@backstage/frontend-plugin-api` to be used in an app from `@backstage/core-app-api`. + * + * @public + */ +export function compatWrapper(element: ReactNode) { + return {element}; +} diff --git a/packages/core-compat-api/src/compatWrapper/index.ts b/packages/core-compat-api/src/compatWrapper/index.ts new file mode 100644 index 0000000000..42a8ff6dd4 --- /dev/null +++ b/packages/core-compat-api/src/compatWrapper/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { compatWrapper } from './compatWrapper'; diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx index 0f1f6145c9..ef65603c85 100644 --- a/packages/core-compat-api/src/convertLegacyApp.test.tsx +++ b/packages/core-compat-api/src/convertLegacyApp.test.tsx @@ -59,14 +59,14 @@ describe('convertLegacyApp', () => { id: 'score-card', extensions: [ { - id: 'plugin.score-card.page', - attachTo: { id: 'core.routes', input: 'routes' }, + id: 'page:score-card', + attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, defaultConfig: { path: 'score-board' }, }, { - id: 'apis.plugin.scoringdata.service', - attachTo: { id: 'core', input: 'apis' }, + id: 'api:plugin.scoringdata.service', + attachTo: { id: 'app', input: 'apis' }, disabled: false, }, ], @@ -75,14 +75,14 @@ describe('convertLegacyApp', () => { id: 'stackstorm', extensions: [ { - id: 'plugin.stackstorm.page', - attachTo: { id: 'core.routes', input: 'routes' }, + id: 'page:stackstorm', + attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, defaultConfig: { path: 'stackstorm' }, }, { - id: 'apis.plugin.stackstorm.service', - attachTo: { id: 'core', input: 'apis' }, + id: 'api:plugin.stackstorm.service', + attachTo: { id: 'app', input: 'apis' }, disabled: false, }, ], @@ -91,20 +91,20 @@ describe('convertLegacyApp', () => { id: 'puppetDb', extensions: [ { - id: 'plugin.puppetDb.page', - attachTo: { id: 'core.routes', input: 'routes' }, + id: 'page:puppetDb', + attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, defaultConfig: { path: 'puppetdb' }, }, { - id: 'plugin.puppetDb.page2', - attachTo: { id: 'core.routes', input: 'routes' }, + id: 'page:puppetDb/1', + attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, defaultConfig: { path: 'puppetdb' }, }, { - id: 'apis.plugin.puppetdb.service', - attachTo: { id: 'core', input: 'apis' }, + id: 'api:plugin.puppetdb.service', + attachTo: { id: 'app', input: 'apis' }, disabled: false, }, ], @@ -113,17 +113,50 @@ describe('convertLegacyApp', () => { id: undefined, extensions: [ { - id: 'core.layout', - attachTo: { id: 'core', input: 'root' }, + id: 'app/layout', + attachTo: { id: 'app', input: 'root' }, disabled: false, }, { - id: 'core.nav', - attachTo: { id: 'core.layout', input: 'nav' }, + id: 'app/nav', + attachTo: { id: 'app/layout', input: 'nav' }, disabled: true, }, ], }, ]); }); + + it('should find and extract just routes', () => { + const collected = convertLegacyApp( + + } /> + } /> + } /> + } /> + , + ); + + expect( + collected.map((p: any /* TODO */) => ({ + id: p.id, + extensions: p.extensions.map((e: any) => ({ + id: e.id, + attachTo: e.attachTo, + disabled: e.disabled, + defaultConfig: e.configSchema?.parse({}), + })), + })), + ).toEqual([ + expect.objectContaining({ + id: 'score-card', + }), + expect.objectContaining({ + id: 'stackstorm', + }), + expect.objectContaining({ + id: 'puppetDb', + }), + ]); + }); }); diff --git a/packages/core-compat-api/src/convertLegacyApp.ts b/packages/core-compat-api/src/convertLegacyApp.ts index 93521b3955..72ce4b5e61 100644 --- a/packages/core-compat-api/src/convertLegacyApp.ts +++ b/packages/core-compat-api/src/convertLegacyApp.ts @@ -22,8 +22,7 @@ import React, { isValidElement, } from 'react'; import { - BackstagePlugin, - ExtensionOverrides, + FrontendFeature, coreExtensionData, createExtension, createExtensionInput, @@ -61,7 +60,11 @@ function selectChildren( /** @public */ export function convertLegacyApp( rootElement: React.JSX.Element, -): (ExtensionOverrides | BackstagePlugin)[] { +): FrontendFeature[] { + if (getComponentData(rootElement, 'core.type') === 'FlatRoutes') { + return collectLegacyRoutes(rootElement); + } + const appRouterEls = selectChildren( rootElement, el => getComponentData(el, 'core.type') === 'AppRouter', @@ -100,8 +103,9 @@ export function convertLegacyApp( const [routesEl] = routesEls; const CoreLayoutOverride = createExtension({ - id: 'core.layout', - attachTo: { id: 'core', input: 'root' }, + namespace: 'app', + name: 'layout', + attachTo: { id: 'app', input: 'root' }, inputs: { content: createExtensionInput( { @@ -116,13 +120,18 @@ export function convertLegacyApp( factory({ inputs }) { // Clone the root element, this replaces the FlatRoutes declared in the app with out content input return { - element: React.cloneElement(rootEl, undefined, inputs.content.element), + element: React.cloneElement( + rootEl, + undefined, + inputs.content.output.element, + ), }; }, }); const CoreNavOverride = createExtension({ - id: 'core.nav', - attachTo: { id: 'core.layout', input: 'nav' }, + namespace: 'app', + name: 'nav', + attachTo: { id: 'app/layout', input: 'nav' }, output: {}, factory: () => ({}), disabled: true, diff --git a/packages/core-compat-api/src/convertLegacyRouteRef.ts b/packages/core-compat-api/src/convertLegacyRouteRef.ts index 2495f3719c..36311dc081 100644 --- a/packages/core-compat-api/src/convertLegacyRouteRef.ts +++ b/packages/core-compat-api/src/convertLegacyRouteRef.ts @@ -40,6 +40,43 @@ import { toInternalSubRouteRef } from '../../frontend-plugin-api/src/routing/Sub // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalExternalRouteRef } from '../../frontend-plugin-api/src/routing/ExternalRouteRef'; +/** + * Converts a legacy route ref type to the new system. + * + * @public + */ +export type ToNewRouteRef< + T extends LegacyRouteRef | LegacySubRouteRef | LegacyExternalRouteRef, +> = T extends LegacyRouteRef + ? RouteRef + : T extends LegacySubRouteRef + ? SubRouteRef + : T extends LegacyExternalRouteRef + ? ExternalRouteRef + : never; + +/** + * Converts a collection of legacy route refs to the new system. + * This is particularly useful when defining plugin `routes` and `externalRoutes`. + * + * @public + */ +export function convertLegacyRouteRefs< + TRefs extends { + [name in string]: + | LegacyRouteRef + | LegacySubRouteRef + | LegacyExternalRouteRef; + }, +>(refs: TRefs): { [KName in keyof TRefs]: ToNewRouteRef } { + return Object.fromEntries( + Object.entries(refs).map(([name, ref]) => [ + name, + convertLegacyRouteRef(ref as LegacyRouteRef), + ]), + ) as { [KName in keyof TRefs]: ToNewRouteRef }; +} + /** * A temporary helper to convert a legacy route ref to the new system. * @@ -91,6 +128,7 @@ export function convertLegacyRouteRef( if (type === 'absolute') { const legacyRef = ref as LegacyRouteRef; + const legacyRefStr = String(legacyRef); const newRef = toInternalRouteRef( createRouteRef<{ [key in string]: string }>({ params: legacyRef.params as string[], @@ -104,18 +142,19 @@ export function convertLegacyRouteRef( return newRef.getParams(); }, getDescription() { - return newRef.getDescription(); + return legacyRefStr; }, setId(id: string) { newRef.setId(id); }, toString() { - return newRef.toString(); + return legacyRefStr; }, }); } if (type === 'sub') { const legacyRef = ref as LegacySubRouteRef; + const legacyRefStr = String(legacyRef); const newRef = toInternalSubRouteRef( createSubRouteRef({ path: legacyRef.path, @@ -133,15 +172,16 @@ export function convertLegacyRouteRef( return newRef.getParent(); }, getDescription() { - return newRef.getDescription(); + return legacyRefStr; }, toString() { - return newRef.toString(); + return legacyRefStr; }, }); } if (type === 'external') { const legacyRef = ref as LegacyExternalRouteRef; + const legacyRefStr = String(legacyRef); const newRef = toInternalExternalRouteRef( createExternalRouteRef<{ [key in string]: string }>({ params: legacyRef.params as string[], @@ -157,13 +197,13 @@ export function convertLegacyRouteRef( return newRef.getParams(); }, getDescription() { - return newRef.getDescription(); + return legacyRefStr; }, setId(id: string) { newRef.setId(id); }, toString() { - return newRef.toString(); + return legacyRefStr; }, }); } diff --git a/packages/core-compat-api/src/index.ts b/packages/core-compat-api/src/index.ts index 421ced7054..b632bf8a57 100644 --- a/packages/core-compat-api/src/index.ts +++ b/packages/core-compat-api/src/index.ts @@ -13,6 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { collectLegacyRoutes } from './collectLegacyRoutes'; +export * from './compatWrapper'; + export { convertLegacyApp } from './convertLegacyApp'; -export { convertLegacyRouteRef } from './convertLegacyRouteRef'; +export { + convertLegacyRouteRef, + convertLegacyRouteRefs, + type ToNewRouteRef, +} from './convertLegacyRouteRef'; diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index f726aa1b19..c6ffec5679 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,101 @@ # @backstage/core-components +## 0.13.10 + +### Patch Changes + +- d625f66: Fixed bug in Link where it was possible to select and copy a hidden element into clipboard +- 6878b1d: Removed unnecessary `history` and `immer` dependencies. +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/version-bridge@1.0.7 + +## 0.13.10-next.1 + +### Patch Changes + +- 6878b1d: Removed unnecessary `history` and `immer` dependencies. +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/version-bridge@1.0.7 + +## 0.13.10-next.0 + +### Patch Changes + +- d625f66: Fixed bug in Link where it was possible to select and copy a hidden element into clipboard +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/version-bridge@1.0.7 + +## 0.13.9 + +### Patch Changes + +- e8f2ace: Added a new `/testUtils` sub-path that initially exports a `mockBreakpoint` helper. +- 381ed86: Add missing export for IconLinkVertical +- 5c8a3e3: Minor improvements to `Table` component. +- 752df93: Fixes a problem where the `LogViewer` was not able to handle very large logs +- 4d9e3b3: Register component overrides in the global `OverrideComponentNameToClassKeys` provided by `@backstage/theme`. This will in turn will provide component style override types for `createUnifiedTheme`. +- 07dfdf3: Updated dependency `linkifyjs` to `4.1.3`. +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- f291757: Update `linkify-react` to version `4.1.3` +- 175d86b: Fixed an issue where the `onChange` prop within `HeaderTabs` was triggering twice upon tab-switching. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/version-bridge@1.0.7 + +## 0.13.9-next.3 + +### Patch Changes + +- 175d86b: Fixed an issue where the `onChange` prop within `HeaderTabs` was triggering twice upon tab-switching. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/version-bridge@1.0.7 + +## 0.13.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/version-bridge@1.0.7 + +## 0.13.9-next.1 + +### Patch Changes + +- e8f2acef80: Added a new `/testUtils` sub-path that initially exports a `mockBreakpoint` helper. +- 07dfdf3702: Updated dependency `linkifyjs` to `4.1.3`. +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- f291757e70: Update `linkify-react` to version `4.1.3` +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/version-bridge@1.0.7 + ## 0.13.9-next.0 ### Patch Changes diff --git a/packages/core-components/api-report-testUtils.md b/packages/core-components/api-report-testUtils.md new file mode 100644 index 0000000000..93a6bb2a07 --- /dev/null +++ b/packages/core-components/api-report-testUtils.md @@ -0,0 +1,10 @@ +## API Report File for "@backstage/core-components" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +// @public +export function mockBreakpoint(options: { matches: boolean }): void; + +// (No @packageDocumentation comment for this package) +``` diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 50a3bd1463..9dc3e8cb8f 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,11 +1,9 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.13.9-next.0", + "version": "0.13.10", "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "backstage": { "role": "web-library" @@ -22,6 +20,21 @@ "license": "Apache-2.0", "main": "src/index.ts", "types": "src/index.ts", + "exports": { + ".": "./src/index.ts", + "./testUtils": "./src/testUtils.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "testUtils": [ + "src/testUtils.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "sideEffects": false, "scripts": { "build": "backstage-cli package build", @@ -43,7 +56,7 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "@react-hookz/web": "^20.0.0", + "@react-hookz/web": "^23.0.0", "@types/react": "^16.13.1 || ^17.0.0", "@types/react-sparklines": "^1.7.0", "@types/react-text-truncate": "^0.14.0", @@ -53,10 +66,8 @@ "d3-shape": "^3.0.0", "d3-zoom": "^3.0.0", "dagre": "^0.8.5", - "history": "^5.0.0", - "immer": "^9.0.1", - "linkify-react": "4.1.2", - "linkifyjs": "4.1.2", + "linkify-react": "4.1.3", + "linkifyjs": "4.1.3", "lodash": "^4.17.21", "pluralize": "^8.0.0", "qs": "^6.9.4", @@ -73,7 +84,7 @@ "react-window": "^1.8.6", "remark-gfm": "^3.0.1", "zen-observable": "^0.10.0", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", @@ -81,6 +92,7 @@ "react-router-dom": "6.0.0-beta.0 || ^6.3.0" }, "devDependencies": { + "@backstage/app-defaults": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/test-utils": "workspace:^", @@ -101,6 +113,7 @@ "@types/react-window": "^1.8.5", "@types/zen-observable": "^0.8.0", "cross-fetch": "^4.0.0", + "history": "^5.0.0", "msw": "^1.0.0" }, "files": [ diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx index 4d58fdfa77..e957390da8 100644 --- a/packages/core-components/src/components/Link/Link.tsx +++ b/packages/core-components/src/components/Link/Link.tsx @@ -42,6 +42,7 @@ const useStyles = makeStyles( clipPath: 'inset(50%)', overflow: 'hidden', position: 'absolute', + userSelect: 'none', whiteSpace: 'nowrap', height: 1, width: 1, diff --git a/packages/core-components/src/components/LogViewer/AnsiProcessor.ts b/packages/core-components/src/components/LogViewer/AnsiProcessor.ts index d0f835a70e..bcd5af7ced 100644 --- a/packages/core-components/src/components/LogViewer/AnsiProcessor.ts +++ b/packages/core-components/src/components/LogViewer/AnsiProcessor.ts @@ -117,7 +117,7 @@ export class AnsiProcessor { return this.lines; } - if (text.startsWith(this.text)) { + if (this.text && text.startsWith(this.text)) { const lastLineIndex = this.lines.length > 0 ? this.lines.length - 1 : 0; const lastLine = this.lines[lastLineIndex] ?? new AnsiLine(); const lastChunk = lastLine.lastChunk(); @@ -130,7 +130,7 @@ export class AnsiProcessor { lastLine.replaceLastChunk(newLines[0]?.chunks); this.lines[lastLineIndex] = lastLine; - this.lines.push(...newLines.slice(1)); + this.lines = this.lines.concat(newLines.slice(1)); } else { this.lines = this.processLines(text); } diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx index f656696f8a..5aec83bc2b 100644 --- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx +++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx @@ -83,4 +83,16 @@ describe('', () => { expect(rendered.getByText('Alarms')).toBeInTheDocument(); expect(rendered.getByText('three new alarms')).toBeInTheDocument(); }); + + it('should trigger onChange only once', async () => { + const mockOnChange = jest.fn(); + const user = userEvent.setup(); + + const rendered = await renderInTestApp( + , + ); + + await user.click(rendered.getByText('Docs')); + expect(mockOnChange).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx index 704df67374..799eab1eb5 100644 --- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx +++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx @@ -99,7 +99,6 @@ export function HeaderTabs(props: HeaderTabsProps) { return ( { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + const Subject = wrapInTestApp(
authenticated
, { + components: { + SignInPage: props => ( + 'http://example.com/api/auth', + }, + ], + ]} + > + + + ), + }, + }); + + it('should sign in a user', async () => { + worker.use( + rest.get('http://example.com/api/auth/test/refresh', (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + profile: { + email: 'e', + displayName: 'd', + picture: 'p', + }, + backstageIdentity: { + token: 'a.e30.c', + identity: { + type: 'user', + userEntityRef: 'k:ns/ue', + ownershipEntityRefs: ['k:ns/oe'], + }, + }, + }), + ), + ), + ); + + render(Subject); + + await expect( + screen.findByText('authenticated'), + ).resolves.toBeInTheDocument(); + }); + + it('should forward error', async () => { + worker.use( + rest.get('http://example.com/api/auth/test/refresh', (_, res, ctx) => + res( + ctx.status(401), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + error: { name: 'Error', message: 'not-displayed' }, + }), + ), + ), + ); + + render(Subject); + + await expect( + screen.findByText('Request failed with 401 Error'), + ).resolves.toBeInTheDocument(); + }); +}); diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index f9970749d0..b2449075cc 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -34,7 +34,7 @@ import ArrowDropUp from '@material-ui/icons/ArrowDropUp'; import ArrowRightIcon from '@material-ui/icons/ArrowRight'; import SearchIcon from '@material-ui/icons/Search'; import classnames from 'classnames'; -import { Location } from 'history'; +import type { Location } from 'history'; import React, { ComponentProps, ComponentType, diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx index 7012afabf1..c29276a6cf 100644 --- a/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx +++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx @@ -14,7 +14,8 @@ * limitations under the License. */ -import { mockBreakpoint, renderInTestApp } from '@backstage/test-utils'; +import { renderInTestApp } from '@backstage/test-utils'; +import { mockBreakpoint } from '@backstage/core-components/testUtils'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import HomeIcon from '@material-ui/icons/Home'; import LayersIcon from '@material-ui/icons/Layers'; diff --git a/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx index 687140277a..b59395efab 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx @@ -14,7 +14,8 @@ * limitations under the License. */ -import { mockBreakpoint, renderInTestApp } from '@backstage/test-utils'; +import { mockBreakpoint } from '@backstage/core-components/testUtils'; +import { renderInTestApp } from '@backstage/test-utils'; import HomeIcon from '@material-ui/icons/Home'; import LayersIcon from '@material-ui/icons/Layers'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; diff --git a/packages/core-components/src/layout/Sidebar/utils.test.ts b/packages/core-components/src/layout/Sidebar/utils.test.ts index 976108a63d..19fe3e5671 100644 --- a/packages/core-components/src/layout/Sidebar/utils.test.ts +++ b/packages/core-components/src/layout/Sidebar/utils.test.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Location, Path } from 'history'; +import type { Location, Path } from 'history'; import { isLocationMatch } from './utils'; describe('isLocationMatching', () => { diff --git a/packages/core-components/src/layout/Sidebar/utils.ts b/packages/core-components/src/layout/Sidebar/utils.ts index 5dba73f4cd..bf5f77a26f 100644 --- a/packages/core-components/src/layout/Sidebar/utils.ts +++ b/packages/core-components/src/layout/Sidebar/utils.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Location, Path } from 'history'; +import type { Location, Path } from 'history'; import { isEqual, isMatch } from 'lodash'; import qs from 'qs'; diff --git a/packages/core-components/src/layout/TabbedCard/TabbedCard.test.tsx b/packages/core-components/src/layout/TabbedCard/TabbedCard.test.tsx index 1b7c3329a2..707d528867 100644 --- a/packages/core-components/src/layout/TabbedCard/TabbedCard.test.tsx +++ b/packages/core-components/src/layout/TabbedCard/TabbedCard.test.tsx @@ -18,6 +18,7 @@ import { renderInTestApp, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; import { CardTab, TabbedCard } from './TabbedCard'; +import userEvent from '@testing-library/user-event'; const minProps = { title: 'Some title', @@ -96,4 +97,25 @@ describe('', () => { ); expect(screen.getByText('Test Content 2')).toBeInTheDocument(); }); + + it('should trigger onChange only once', async () => { + const mockOnChange = jest.fn(); + const user = userEvent.setup(); + + const rendered = render( + wrapInTestApp( + + + Test Content 1 + + + Test Content 2 + + , + ), + ); + + await user.click(rendered.getByText('Test 2')); + expect(mockOnChange).toHaveBeenCalledTimes(1); + }); }); diff --git a/packages/core-components/src/layout/TabbedCard/TabbedCard.tsx b/packages/core-components/src/layout/TabbedCard/TabbedCard.tsx index 4e75878a7d..088e54c1f3 100644 --- a/packages/core-components/src/layout/TabbedCard/TabbedCard.tsx +++ b/packages/core-components/src/layout/TabbedCard/TabbedCard.tsx @@ -114,7 +114,6 @@ export function TabbedCard(props: PropsWithChildren) { {title && } ({ + matches: options.matches ?? false, + media: query, + onchange: null, + addListener: jest.fn(), // deprecated + removeListener: jest.fn(), // deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), + }); +} diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index d80e3e9f89..4cb681e4aa 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/core-plugin-api +## 1.8.2 + +### Patch Changes + +- 6878b1d: Removed unnecessary `i18next` dependency. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.8.2-next.0 + +### Patch Changes + +- 6878b1d: Removed unnecessary `i18next` dependency. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.8.1 + +### Patch Changes + +- 03d0b6d: Removed the alpha `convertLegacyRouteRef` utility, which as been moved to `@backstage/core-compat-api` +- 0c93dc3: The `createTranslationRef` function from the `/alpha` subpath can now also accept a nested object structure of default translation messages, which will be flatted using `.` separators. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.8.1-next.1 + +### Patch Changes + +- 0c93dc37b2: The `createTranslationRef` function from the `/alpha` subpath can now also accept a nested object structure of default translation messages, which will be flatted using `.` separators. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + ## 1.8.1-next.0 ### Patch Changes diff --git a/packages/core-plugin-api/alpha-api-report.md b/packages/core-plugin-api/api-report-alpha.md similarity index 92% rename from packages/core-plugin-api/alpha-api-report.md rename to packages/core-plugin-api/api-report-alpha.md index ba3583142f..c0f0d02535 100644 --- a/packages/core-plugin-api/alpha-api-report.md +++ b/packages/core-plugin-api/api-report-alpha.md @@ -39,19 +39,17 @@ export function createTranslationMessages< // @alpha (undocumented) export function createTranslationRef< TId extends string, - const TMessages extends { - [key in string]: string; - }, + const TNestedMessages extends AnyNestedMessages, TTranslations extends { [language in string]: () => Promise<{ default: { - [key in keyof TMessages]: string | null; + [key in keyof FlattenedMessages]: string | null; }; }>; }, >( - config: TranslationRefOptions, -): TranslationRef; + config: TranslationRefOptions, +): TranslationRef>; // @alpha (undocumented) export function createTranslationResource< @@ -169,13 +167,11 @@ export interface TranslationRef< // @alpha (undocumented) export interface TranslationRefOptions< TId extends string, - TMessages extends { - [key in string]: string; - }, + TNestedMessages extends AnyNestedMessages, TTranslations extends { [language in string]: () => Promise<{ default: { - [key in keyof TMessages]: string | null; + [key in keyof FlattenedMessages]: string | null; }; }>; }, @@ -183,7 +179,7 @@ export interface TranslationRefOptions< // (undocumented) id: TId; // (undocumented) - messages: TMessages; + messages: TNestedMessages; // (undocumented) translations?: TTranslations; } diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 98286779f8..d676260227 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-plugin-api", "description": "Core API used by Backstage plugins", - "version": "1.8.1-next.0", + "version": "1.8.2", "publishConfig": { "access": "public" }, @@ -50,8 +50,7 @@ "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", "@types/react": "^16.13.1 || ^17.0.0", - "history": "^5.0.0", - "i18next": "^22.4.15" + "history": "^5.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", diff --git a/packages/core-plugin-api/src/translation/TranslationRef.test.ts b/packages/core-plugin-api/src/translation/TranslationRef.test.ts index b73b521774..d1fde0810f 100644 --- a/packages/core-plugin-api/src/translation/TranslationRef.test.ts +++ b/packages/core-plugin-api/src/translation/TranslationRef.test.ts @@ -34,6 +34,40 @@ describe('TranslationRefImpl', () => { expect(internalRef.getDefaultMessages()).toEqual({ key: 'value' }); }); + it('should create a TranslationRef instance with nested messages', () => { + const ref = createTranslationRef({ + id: 'test', + messages: { + key: 'value', + 'nested.conflict1': 'outer conflict1', + nested: { + key: 'nested value', + key2: 'nested value2', + conflict1: 'inner conflict1', + conflict2: 'inner conflict2', + inner: { + key: 'inner value', + }, + }, + 'nested.conflict2': 'outer conflict2', + }, + }); + + const internalRef = toInternalTranslationRef(ref); + + expect(internalRef.$$type).toBe('@backstage/TranslationRef'); + expect(internalRef.version).toBe('v1'); + expect(internalRef.id).toBe('test'); + expect(internalRef.getDefaultMessages()).toEqual({ + key: 'value', + 'nested.key': 'nested value', + 'nested.key2': 'nested value2', + 'nested.conflict1': 'inner conflict1', + 'nested.inner.key': 'inner value', + 'nested.conflict2': 'outer conflict2', + }); + }); + it('should be created with lazy translations', async () => { const ref = createTranslationRef({ id: 'test', diff --git a/packages/core-plugin-api/src/translation/TranslationRef.ts b/packages/core-plugin-api/src/translation/TranslationRef.ts index 345a9c3f65..c2ac7a5ce3 100644 --- a/packages/core-plugin-api/src/translation/TranslationRef.ts +++ b/packages/core-plugin-api/src/translation/TranslationRef.ts @@ -34,6 +34,43 @@ export interface TranslationRef< /** @internal */ type AnyMessages = { [key in string]: string }; +/** @ignore */ +type AnyNestedMessages = { [key in string]: AnyNestedMessages | string }; + +/** + * Flattens a nested message declaration into a flat object with dot-separated keys. + * + * @ignore + */ +type FlattenedMessages = + // Flatten out object keys into a union structure of objects, e.g. { a: 'a', b: 'b' } -> { a: 'a' } | { b: 'b' } + // Any nested object will be flattened into the individual unions, e.g. { a: 'a', b: { x: 'x', y: 'y' } } -> { a: 'a' } | { 'b.x': 'x', 'b.y': 'y' } + // We create this structure by first nesting the desired union types into the original object, and + // then extract them by indexing with `keyof TMessages` to form the union. + // Throughout this the objects are wrapped up in a function parameter, which allows us to have the + // final step of flipping this unions around to an intersection by inferring the function parameter. + { + [TKey in keyof TMessages]: ( + _: TMessages[TKey] extends infer TValue // "local variable" for the value + ? TValue extends AnyNestedMessages + ? FlattenedMessages extends infer TNested // Recurse into nested messages, "local variable" for the result + ? { + [TNestedKey in keyof TNested as `${TKey & string}.${TNestedKey & + string}`]: TNested[TNestedKey]; + } + : never + : { [_ in TKey]: TValue } // Primitive object values are passed through with the same key + : never, + ) => void; + // The `[keyof TMessages]` extracts the object values union from our flattened structure, still wrapped up in function parameters. + // The `extends (_: infer TIntersection) => void` flips the union to an intersection, at which point we have the correct type. + }[keyof TMessages] extends (_: infer TIntersection) => void + ? // This object mapping just expands similar to the Expand<> utility type, providing nicer type hints + { + readonly [TExpandKey in keyof TIntersection]: TIntersection[TExpandKey]; + } + : never; + /** @internal */ export interface InternalTranslationRef< TId extends string = string, @@ -49,31 +86,53 @@ export interface InternalTranslationRef< /** @alpha */ export interface TranslationRefOptions< TId extends string, - TMessages extends { [key in string]: string }, + TNestedMessages extends AnyNestedMessages, TTranslations extends { [language in string]: () => Promise<{ - default: { [key in keyof TMessages]: string | null }; + default: { + [key in keyof FlattenedMessages]: string | null; + }; }>; }, > { id: TId; - messages: TMessages; + messages: TNestedMessages; translations?: TTranslations; } +function flattenMessages(nested: AnyNestedMessages): AnyMessages { + const entries = new Array<[string, string]>(); + + function visit(obj: AnyNestedMessages, prefix: string): void { + for (const [key, value] of Object.entries(obj)) { + if (typeof value === 'string') { + entries.push([prefix + key, value]); + } else { + visit(value, `${prefix}${key}.`); + } + } + } + + visit(nested, ''); + + return Object.fromEntries(entries); +} + /** @internal */ class TranslationRefImpl< TId extends string, - TMessages extends { [key in string]: string }, -> implements InternalTranslationRef + TNestedMessages extends AnyNestedMessages, +> implements InternalTranslationRef> { #id: TId; - #messages: TMessages; + #messages: FlattenedMessages; #resources: TranslationResource | undefined; - constructor(options: TranslationRefOptions) { + constructor(options: TranslationRefOptions) { this.#id = options.id; - this.#messages = options.messages; + this.#messages = flattenMessages( + options.messages, + ) as FlattenedMessages; } $$type = '@backstage/TranslationRef' as const; @@ -108,15 +167,17 @@ class TranslationRefImpl< /** @alpha */ export function createTranslationRef< TId extends string, - const TMessages extends { [key in string]: string }, + const TNestedMessages extends AnyNestedMessages, TTranslations extends { [language in string]: () => Promise<{ - default: { [key in keyof TMessages]: string | null }; + default: { + [key in keyof FlattenedMessages]: string | null; + }; }>; }, >( - config: TranslationRefOptions, -): TranslationRef { + config: TranslationRefOptions, +): TranslationRef> { const ref = new TranslationRefImpl(config); if (config.translations) { ref.setDefaultResource( @@ -132,7 +193,7 @@ export function createTranslationRef< /** @internal */ export function toInternalTranslationRef< TId extends string, - TMessages extends { [key in string]: string }, + TMessages extends AnyMessages, >(ref: TranslationRef): InternalTranslationRef { const r = ref as InternalTranslationRef; if (r.$$type !== '@backstage/TranslationRef') { diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 74c2c3b7ce..14ff49ff5c 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,86 @@ # @backstage/create-app +## 0.5.11-next.0 + +### Patch Changes + +- aeec29c: Updated `packages/app` as well as the root `package.json` type resolutions to use React v18. + + The `@testing-library/*` dependencies have also been updated to the ones compatible with React v18, and the test at `packages/app/src/App.test.tsx` had been updated to use more modern patterns that work better with these new versions. + + For information on how to migrate existing apps to React v18, see the [migration guide](https://backstage.io/docs/tutorials/react18-migration) + +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## 0.5.9 + +### Patch Changes + +- c9f71fb: Bumped create-app version. +- ac277f3: Bumped create-app version. +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## 0.5.9-next.2 + +### Patch Changes + +- Bumped create-app version. + +## 0.5.9-next.1 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## 0.5.9-next.0 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## 0.5.8 + +### Patch Changes + +- 8ece804: Bumped create-app version. +- 0351e09: Bumped create-app version. +- 3f1192f: Bumped create-app version. +- a96c2d4: Include the `` for group entities by default +- 375b6f7: CircelCI plugin moved permanently +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## 0.5.8-next.4 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## 0.5.8-next.3 + +### Patch Changes + +- a96c2d4: Include the `` for group entities by default +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## 0.5.8-next.2 + +### Patch Changes + +- 375b6f7d68: CircelCI plugin moved permanently +- Updated dependencies + - @backstage/cli-common@0.1.13 + ## 0.5.8-next.1 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 52f075adc5..6d3fe75f1d 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.5.8-next.1", + "version": "0.5.11-next.0", "publishConfig": { "access": "public" }, @@ -49,7 +49,6 @@ "@types/inquirer": "^8.1.3", "@types/node": "^18.17.8", "@types/recursive-readdir": "^2.2.0", - "mock-fs": "^5.2.0", "nodemon": "^3.0.1", "ts-node": "^10.0.0" }, diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index 81bae3c0b8..03ef32d921 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -58,7 +58,6 @@ import { version as pluginCatalogBackend } from '../../../../plugins/catalog-bac import { version as pluginCatalogBackendModuleScaffolderEntityModel } from '../../../../plugins/catalog-backend-module-scaffolder-entity-model/package.json'; import { version as pluginCatalogGraph } from '../../../../plugins/catalog-graph/package.json'; import { version as pluginCatalogImport } from '../../../../plugins/catalog-import/package.json'; -import { version as pluginCircleci } from '../../../../plugins/circleci/package.json'; import { version as pluginExplore } from '../../../../plugins/explore/package.json'; import { version as pluginGithubActions } from '../../../../plugins/github-actions/package.json'; import { version as pluginLighthouse } from '../../../../plugins/lighthouse/package.json'; @@ -111,7 +110,6 @@ export const packageVersions = { pluginCatalogBackendModuleScaffolderEntityModel, '@backstage/plugin-catalog-graph': pluginCatalogGraph, '@backstage/plugin-catalog-import': pluginCatalogImport, - '@backstage/plugin-circleci': pluginCircleci, '@backstage/plugin-explore': pluginExplore, '@backstage/plugin-github-actions': pluginGithubActions, '@backstage/plugin-lighthouse': pluginLighthouse, diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs index b74957dd56..40cc8282d9 100644 --- a/packages/create-app/templates/default-app/package.json.hbs +++ b/packages/create-app/templates/default-app/package.json.hbs @@ -42,8 +42,8 @@ "typescript": "~5.2.0" }, "resolutions": { - "@types/react": "^17", - "@types/react-dom": "^17" + "@types/react": "^18", + "@types/react-dom": "^18" }, "prettier": "@spotify/prettier-config", "lint-staged": { diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index 598f049a4c..b403295b09 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -42,8 +42,8 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "history": "^5.0.0", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.0.2", + "react-dom": "^18.0.2", "react-router": "^6.3.0", "react-router-dom": "^6.3.0", "react-use": "^17.2.4" @@ -51,10 +51,10 @@ "devDependencies": { "@backstage/test-utils": "^{{version '@backstage/test-utils'}}", "@playwright/test": "^1.32.3", - "@testing-library/jest-dom": "^5.10.1", - "@testing-library/react": "^12.1.3", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.0.0", - "@testing-library/dom": "^8.0.0", + "@testing-library/dom": "^9.0.0", "@types/react-dom": "*", "cross-env": "^7.0.0" }, diff --git a/packages/create-app/templates/default-app/packages/app/src/App.test.tsx b/packages/create-app/templates/default-app/packages/app/src/App.test.tsx index b94cac73b3..ec8ba1d714 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.test.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { renderWithEffects } from '@backstage/test-utils'; +import { render, waitFor } from '@testing-library/react'; import App from './App'; describe('App', () => { @@ -20,7 +20,10 @@ describe('App', () => { ] as any, }; - const rendered = await renderWithEffects(); - expect(rendered.baseElement).toBeInTheDocument(); + const rendered = render(); + + await waitFor(() => { + expect(rendered.baseElement).toBeInTheDocument(); + }); }); }); diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx index 6722ea2d03..7c6a71e070 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx @@ -300,9 +300,12 @@ const groupPage = ( - + + + + diff --git a/packages/create-app/templates/default-app/packages/app/src/index.tsx b/packages/create-app/templates/default-app/packages/app/src/index.tsx index b16aaf7cd2..d875c774c8 100644 --- a/packages/create-app/templates/default-app/packages/app/src/index.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/index.tsx @@ -1,6 +1,6 @@ import '@backstage/cli/asset-types'; import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import App from './App'; -ReactDOM.render(, document.getElementById('root')); +ReactDOM.createRoot(document.getElementById('root')!).render(); diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 485dc687ab..8ebf1aece5 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,125 @@ # @backstage/dev-utils +## 1.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/app-defaults@1.4.8-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration-react@1.1.23 + - @backstage/theme@0.5.0 + +## 1.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/app-defaults@1.4.7 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + +## 1.0.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## 1.0.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/app-defaults@1.4.7-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + +## 1.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/app-defaults@1.4.7-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + +## 1.0.25 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/core-app-api@1.11.2 + - @backstage/app-defaults@1.4.6 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + +## 1.0.25-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/app-defaults@1.4.6-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 1.0.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/app-defaults@1.4.6-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + +## 1.0.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/app-defaults@1.4.6-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 1.0.25-next.0 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index ddcb29bdc3..c74550550c 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "1.0.25-next.0", + "version": "1.0.27-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/e2e-test-utils/playwright-api-report.md b/packages/e2e-test-utils/api-report-playwright.md similarity index 100% rename from packages/e2e-test-utils/playwright-api-report.md rename to packages/e2e-test-utils/api-report-playwright.md diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md index 053825aae0..2adedfd43d 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,84 @@ # e2e-test +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.11-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.9 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.9-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.9-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.9-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.8 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## 0.2.10-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.8-next.4 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## 0.2.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.8-next.3 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.8-next.2 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + ## 0.2.10-next.1 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index 35eec664f9..c1731de8ea 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,7 +1,7 @@ { "name": "e2e-test", "description": "E2E test for verifying Backstage packages", - "version": "0.2.10-next.1", + "version": "0.2.12-next.0", "private": true, "backstage": { "role": "cli" diff --git a/packages/e2e-test/src/index.ts b/packages/e2e-test/src/index.ts index 92adc24b1e..f3c63ae175 100644 --- a/packages/e2e-test/src/index.ts +++ b/packages/e2e-test/src/index.ts @@ -36,23 +36,4 @@ async function main(argv: string[]) { program.parse(argv); } -process.on('unhandledRejection', (rejection: unknown) => { - // Try to avoid exiting if the unhandled error is coming from jsdom, i.e. zombie. - // Those are typically errors on the page that should be benign, at least in the - // context of this test. We have other ways of asserting that the page is being - // rendered correctly. - if ( - rejection instanceof Error && - rejection?.stack?.includes('node_modules/jsdom/lib') - ) { - console.log(`Ignored error inside jsdom, ${rejection?.stack ?? rejection}`); - } else { - if (rejection instanceof Error) { - exitWithError(rejection); - } else { - exitWithError(new Error(`Unknown rejection: '${rejection}'`)); - } - } -}); - main(process.argv).catch(exitWithError); diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 56ed54388a..df1734deab 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/eslint-plugin +## 0.1.5-next.0 + +### Patch Changes + +- 995d280: Added new `@backstage/no-top-level-material-ui-4-imports` rule that forbids top level imports from Material UI v4 packages + +## 0.1.4 + +### Patch Changes + +- 107dc46: The `no-undeclared-imports` rule will now prefer using version queries that already exist en the repo for the same dependency type when installing new packages. + +## 0.1.4-next.0 + +### Patch Changes + +- 107dc46ab1: The `no-undeclared-imports` rule will now prefer using version queries that already exist en the repo for the same dependency type when installing new packages. + ## 0.1.3 ### Patch Changes diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index c311670603..600b3268e1 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -35,8 +35,9 @@ 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`. | +| 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`. | +| [@backstage/no-top-level-material-ui-4-imports](./docs/rules/no-top-level-material-ui-4-imports.md) | Forbid top level import from Material UI v4 packages. | diff --git a/packages/eslint-plugin/docs/rules/no-top-level-material-ui-4-imports.md b/packages/eslint-plugin/docs/rules/no-top-level-material-ui-4-imports.md new file mode 100644 index 0000000000..a105014abc --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-top-level-material-ui-4-imports.md @@ -0,0 +1,43 @@ +# @backstage/no-top-level-material-ui-4-imports + +Forbid top level import from Material UI v4 packages. + +## Usage + +Add the rules as follows, it has no options: + +```js +'@backstage/no-top-level-material-ui-4-imports': 'error' +``` + +## Rule Details + +Automatically fixes imports from named to default imports. This will help you comply with [Material UI recommendations](https://mui.com/material-ui/guides/minimizing-bundle-size/) and make migrating to Material UI v5 easier. + +### Fail + +```tsx +import { Box, Typography } from '@material-ui/core'; +``` + +```tsx +import Box from '@material-ui/core'; +``` + +```tsx +import { + Box, + DialogActions, + DialogContent, + DialogTitle, + Grid, + makeStyles, +} from '@material-ui/core'; +``` + +### Pass + +```tsx +import Typography from '@material-ui/core/Typography'; +import Box from '@material-ui/core/Box'; +``` diff --git a/packages/eslint-plugin/index.js b/packages/eslint-plugin/index.js index 20a0ff2d18..af1a8891c3 100644 --- a/packages/eslint-plugin/index.js +++ b/packages/eslint-plugin/index.js @@ -29,5 +29,6 @@ module.exports = { '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'), + 'no-top-level-material-ui-4-imports': require('./rules/no-top-level-material-ui-4-imports'), }, }; diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 34a8205868..8fbf790ee5 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/eslint-plugin", "description": "Backstage ESLint plugin", - "version": "0.1.3", + "version": "0.1.5-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js b/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js new file mode 100644 index 0000000000..6a726545fc --- /dev/null +++ b/packages/eslint-plugin/rules/no-top-level-material-ui-4-imports.js @@ -0,0 +1,156 @@ +/* + * 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 KNOWN_STYLES = [ + 'makeStyles', + 'withStyles', + 'createStyles', + 'styled', + 'useTheme', + 'Theme', +]; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'problem', + fixable: 'code', + messages: { + topLevelImport: 'Top level imports for Material UI are not allowed', + }, + docs: { + description: 'Forbid top level import from Material UI v4 packages.', + url: 'https://github.com/backstage/backstage/blob/master/packages/eslint-plugin/docs/rules/no-top-level-material-ui-4-imports.md', + }, + }, + create: context => ({ + ImportDeclaration: node => { + // Anatomy of a Node + // Example: import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon'; + // Specifiers are the part between the `import` and `from`, in the example that would be `SvgIcon, { SvgIconProps }` + // Source is the part after the `from`, in the example that would be `'@material-ui/core/SvgIcon'` + // Source value gets you `@material-ui/core/SvgIcon` without the quotes, where as Source raw gets it as is + + // Return if empty import + if (node.specifiers.length === 0) return; + // Return if empty source value + if (!node.source.value) return; + // Return if source value not a string + if (typeof node.source.value !== 'string') return; + // Return if import does not start with '@material-ui/' + if (!node.source.value.startsWith('@material-ui/')) return; + // Return if import is from '@material-ui/core/styles', as it's valid already + if (node.source.value === '@material-ui/core/styles') return; + // Return if proper import eg. `import Box from '@material-ui/core/Box'` + // Or if third level or deeper imports + if (node.source.value?.split('/').length >= 3) return; + + // Report all other imports + context.report({ + node, + messageId: 'topLevelImport', + fix: fixer => { + const replacements = []; + const styles = []; + + const specifiers = node.specifiers.filter( + s => s.type === 'ImportSpecifier', + ); + + const specifiersMap = specifiers.map(s => { + const propsMatch = /^([A-Z]\w+)Props$/.exec(s.local.name); + + return { + emitComponent: !(propsMatch !== null), + emitProp: propsMatch !== null, + value: s.local.name, + propValue: propsMatch ? propsMatch[1] : undefined, + }; + }); + + // We have 3 cases: + // 1 - Just Prop: import { TabProps } from '@material-ui/core'; + // 2 - Just Component: import { Box } from '@material-ui/core'; + // 3 - Component and Prop: import { SvgIcon, SvgIconProps } from '@material-ui/core'; + + const components = specifiersMap + .filter(f => { + return f.emitComponent; + }) + .map(m => m.value); + const props = specifiersMap + .filter(f => { + return f.emitProp; + }) + .map(m => m.value); + + if ( + specifiersMap.some(s => s.emitProp) && + !specifiersMap.some(s => s.emitComponent) + ) { + // 1 - Just Prop + const propValue = specifiersMap + .filter(f => { + return f.emitProp; + }) + .map(m => m.propValue); + replacements.push( + `import { ${props.join(', ')} } from '@material-ui/core/${ + propValue[0] + }';`, + ); + } else if ( + !specifiersMap.some(s => s.emitProp) && + specifiersMap.some(s => s.emitComponent) + ) { + // 2 - Just Component + for (const specifier of specifiers) { + if (KNOWN_STYLES.includes(specifier.local.name)) { + styles.push(specifier.local.name); + } else { + const replacement = `import ${specifier.local.name} from '${node.source.value}/${specifier.local.name}';`; + replacements.push(replacement); + } + } + } else if ( + specifiersMap.some(s => s.emitProp) && + specifiersMap.some(s => s.emitComponent) + ) { + // 3 - Component and Prop + replacements.push( + `import ${components[0]}, { ${props.join( + ', ', + )} } from '@material-ui/core/${components[0]}';`, + ); + } + + if (styles.length > 0) { + const stylesReplacement = `import { ${styles.join( + ', ', + )} } from '@material-ui/core/styles';`; + replacements.push(stylesReplacement); + } + + const result = fixer.replaceText(node, replacements.join('\n')); + + return result; + }, + }); + }, + }), +}; diff --git a/packages/eslint-plugin/rules/no-undeclared-imports.js b/packages/eslint-plugin/rules/no-undeclared-imports.js index c986b52c65..d2c92ccb8a 100644 --- a/packages/eslint-plugin/rules/no-undeclared-imports.js +++ b/packages/eslint-plugin/rules/no-undeclared-imports.js @@ -121,6 +121,39 @@ function getAddFlagForDepsField(depsField) { } } +/** + * Looks up the most common version range for a dependency if it already exists in the repo. + * + * @param {string} name + * @param {string} flag + * @param {getPackageMap.PackageMap} packages + * @returns {string} + */ +function addVersionQuery(name, flag, packages) { + const rangeCounts = new Map(); + + for (const pkg of packages.list) { + const deps = + flag === '--dev' + ? pkg.packageJson.devDependencies + : flag === '--peer' + ? pkg.packageJson.peerDependencies + : pkg.packageJson.dependencies; + const range = deps?.[name]; + if (range) { + rangeCounts.set(range, (rangeCounts.get(range) ?? 0) + 1); + } + } + + const mostCommonRange = [...rangeCounts.entries()].sort( + (a, b) => b[1] - a[1], + )[0]?.[0]; + if (!mostCommonRange) { + return name; + } + return `${name}@${mostCommonRange}`; +} + /** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { @@ -177,13 +210,22 @@ module.exports = { } for (const [flag, names] of Object.entries(byFlag)) { + // Look up existing version queries in the repo for the same dependency + const namesWithQuery = [...names].map(name => + addVersionQuery(name, flag, packages), + ); + // The security implication of this is a bit interesting, as crafted add-import // directives could be used to install malicious packages. However, the same is true - // for adding malicious packages to package.json, so there's significant difference. - execFileSync('yarn', ['add', ...(flag ? [flag] : []), ...names], { - cwd: localPkg.dir, - stdio: 'inherit', - }); + // for adding malicious packages to package.json, so there's no significant difference. + execFileSync( + 'yarn', + ['add', ...(flag ? [flag] : []), ...namesWithQuery], + { + cwd: localPkg.dir, + stdio: 'inherit', + }, + ); } // This switches all import directives back to the original import. diff --git a/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts b/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts new file mode 100644 index 0000000000..568bdb8cc8 --- /dev/null +++ b/packages/eslint-plugin/src/no-top-level-material-ui-4-imports.test.ts @@ -0,0 +1,107 @@ +/* + * 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 rule from '../rules/no-top-level-material-ui-4-imports'; + +const ruleTester = new RuleTester({ + parserOptions: { + sourceType: 'module', + ecmaVersion: 2021, + }, +}); + +ruleTester.run('path-imports-rule', rule, { + valid: [ + { + code: `import Typography from '@material-ui/core/Typography';`, + }, + { + code: `import Box from '@material-ui/core/Box'`, + }, + { + code: `import { styled, withStyles } from '@material-ui/core/styles';`, + }, + { + code: `import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';`, + }, + { + code: `import { StyleRules } from '@material-ui/core/styles/withStyles';`, + }, + { + code: `import { CreateCSSProperties, StyledComponentProps } from '@material-ui/core/styles/withStyles';`, + }, + ], + invalid: [ + { + code: `import { Box, Typography } from '@material-ui/core';`, + errors: [{ messageId: 'topLevelImport' }], + output: `import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography';`, + }, + { + code: `import { Box } from '@material-ui/core';`, + errors: [{ messageId: 'topLevelImport' }], + output: `import Box from '@material-ui/core/Box';`, + }, + { + code: `import { + Box, + DialogActions, + DialogContent, + DialogTitle, + Grid, + makeStyles, + } from '@material-ui/core';`, + errors: [{ messageId: 'topLevelImport' }], + output: `import Box from '@material-ui/core/Box'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Grid from '@material-ui/core/Grid'; +import { makeStyles } from '@material-ui/core/styles';`, + }, + { + code: `import { Box, Button, makeStyles } from '@material-ui/core';`, + errors: [{ messageId: 'topLevelImport' }], + output: `import Box from '@material-ui/core/Box'; +import Button from '@material-ui/core/Button'; +import { makeStyles } from '@material-ui/core/styles';`, + }, + { + code: `import { Paper, Typography, styled, withStyles } from '@material-ui/core';`, + errors: [{ messageId: 'topLevelImport' }], + output: `import Paper from '@material-ui/core/Paper'; +import Typography from '@material-ui/core/Typography'; +import { styled, withStyles } from '@material-ui/core/styles';`, + }, + { + code: `import { styled } from '@material-ui/core';`, + errors: [{ messageId: 'topLevelImport' }], + output: `import { styled } from '@material-ui/core/styles';`, + }, + { + code: `import { SvgIcon, SvgIconProps } from '@material-ui/core';`, + errors: [{ messageId: 'topLevelImport' }], + output: `import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon';`, + }, + { + code: `import { TabProps } from '@material-ui/core';`, + errors: [{ messageId: 'topLevelImport' }], + output: `import { TabProps } from '@material-ui/core/Tab';`, + }, + ], +}); diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index a933f6a649..0ef44ba882 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,209 @@ # @backstage/frontend-app-api +## 0.6.0-next.0 + +### Minor Changes + +- 86346c2: **BREAKING**: The `app.routes.bindings` app-config mapping has been simplified. You now only need to specify the plugin ID and route ID on both sides of the mapping. + + Old form: + + ```yaml + app: + routes: + bindings: + plugin.catalog.externalRoutes.viewTechDoc: plugin.techdocs.routes.docRoot + plugin.catalog.externalRoutes.createComponent: plugin.catalog-import.routes.importPage + ``` + + New form: + + ```yaml + app: + routes: + bindings: + catalog.viewTechDoc: techdocs.docRoot + catalog.createComponent: catalog-import.importPage + ``` + +### Patch Changes + +- 42ebf27: Added `IconsApi` implementation and the ability to configure icons through the `icons` option for `createApp` and `createSpecializedApp`. This is not a long-term solution as icons should be installable via extensions instead. This is just a stop-gap to make sure there is feature parity with the existing frontend system. +- e0a4dd1: Improved the error message when data input/output shapes do not match +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.5.0 + +### Minor Changes + +- d4149bf: **BREAKING**: Renamed the `app/router` extension to `app/root`. +- 074dfe3: Attaching extensions to an input that does not exist is now a warning rather than an error. + +### Patch Changes + +- 7d63b32: Accepts sub route refs on the new `createPlugin` routes map. +- 516fd3e: Updated README to reflect release status +- c97fa1c: Added `elements`, `wrappers`, and `router` inputs to `app/root`, that let you add things to the root of the React tree above the layout. You can use the `createAppRootElementExtension`, `createAppRootWrapperExtension`, and `createRouterExtension` extension creator, respectively, to conveniently create such extensions. These are all optional, and if you do not supply a router a default one will be used (`BrowserRouter` in regular runs, `MemoryRouter` in tests/CI). +- 5fe6600: add oauth dialog and alert display to the root elements +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.4.1-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.4.0 + +### Minor Changes + +- e539735: Updated core extension structure to make space for the sign-in page by adding `core.router`. +- 44735df: Removed `featureLoader` from `createApp`, `features` instead accepts both `FrontendFeature` and `CreateAppFeatureLoader` +- af7bc3e: Switched all core extensions to instead use the namespace `'app'`. +- ea06590: The app no longer provides the `AppContext` from `@backstage/core-plugin-api`. Components that require this context to be available should use the `compatWrapper` helper from `@backstage/core-compat-api`. + +### Patch Changes + +- 5eb6b8a: Added the nav logo extension for customization of sidebar logo +- aeb8008: Add support for translation extensions. +- 1f12fb7: Create a core components extension that allows adopters to override core app components such as `Progress`, `BootErrorPage`, `NotFoundErrorPage` and `ErrorBoundaryFallback`. +- a379243: Leverage the new `FrontendFeature` type to simplify interfaces +- 60d6eb5: Removed `@backstage/plugin-graphiql` dependency. +- b7adf24: Use the new plugin type for error boundary components. +- 5970928: Collect and register feature flags from plugins and extension overrides. +- 9ad4039: Bringing over apis from core-plugin-api +- 8f5d6c1: Updates to match the new extension input wrapping. +- c35036b: A `configLoader` passed to `createApp` now returns an object, to make room for future expansion +- f27ee7d: Migrate analytics route tracker component. +- b8cb780: Added `createSpecializedApp`, which is a synchronous version of `createApp` where config and features already need to be loaded. +- c36e0b9: Renamed `AppRouteBinder` to `CreateAppRouteBinder` +- cb4197a: Forward ` node`` instead of `extensionId` to resolved extension inputs. +- 8837a96: Updates to match the introduction of `ExtensionDefinition` and new extension ID naming patterns. +- a5a0473: Updates to provide `node` to extension factories instead of `id` and `source`. +- 5cdf2b3: Updated usage of `Extension` and `ExtensionDefinition` as they are now opaque. +- f9ef632: Updates to match the new `coreExtensionData` structure. +- f1183b7: Renamed the `component` option of `createComponentExtension` to `loader`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/core-app-api@1.11.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.4.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.4.0-next.2 + +### Minor Changes + +- ea06590: The app no longer provides the `AppContext` from `@backstage/core-plugin-api`. Components that require this context to be available should use the `compatWrapper` helper from `@backstage/core-compat-api`. + +### Patch Changes + +- aeb8008: Add support for translation extensions. +- b7adf24: Use the new plugin type for error boundary components. +- 8f5d6c1: Updates to match the new extension input wrapping. +- cb4197a: Forward ` node`` instead of `extensionId` to resolved extension inputs. +- 8837a96: Updates to match the introduction of `ExtensionDefinition` and new extension ID naming patterns. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.4.0-next.1 + +### Minor Changes + +- e539735435: Updated core extension structure to make space for the sign-in page by adding `core.router`. + +### Patch Changes + +- 5eb6b8a7bc: Added the nav logo extension for customization of sidebar logo +- 1f12fb762c: Create a core components extension that allows adopters to override core app components such as `Progress`, `BootErrorPage`, `NotFoundErrorPage` and `ErrorBoundaryFallback`. +- 59709286b3: Collect and register feature flags from plugins and extension overrides. +- f27ee7d937: Migrate analytics route tracker component. +- a5a04739e1: Updates to provide `node` to extension factories instead of `id` and `source`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + ## 0.3.1-next.0 ### Patch Changes diff --git a/packages/frontend-app-api/README.md b/packages/frontend-app-api/README.md index 8b2fe5dc23..256ae99462 100644 --- a/packages/frontend-app-api/README.md +++ b/packages/frontend-app-api/README.md @@ -1,8 +1,8 @@ # @backstage/frontend-app-api -**This package is EXPERIMENTAL, we recommend against using it for production deployments** +**The [new frontend system](https://backstage.io/docs/frontend-system/) that this package is part of is in alpha, and we do not yet recommend using it for production deployments** -This package provides the core API used by Backstage frontend apps. It implements the design outlined in [RFC: Frontend System Evolution](https://github.com/backstage/backstage/issues/18372). +This package provides the framework API used by Backstage frontend apps. It implements the design outlined in [RFC: Frontend System Evolution](https://github.com/backstage/backstage/issues/18372). ## Documentation diff --git a/packages/frontend-app-api/api-report.md b/packages/frontend-app-api/api-report.md index 3ca51f6331..d0aadb7e8c 100644 --- a/packages/frontend-app-api/api-report.md +++ b/packages/frontend-app-api/api-report.md @@ -3,18 +3,40 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { Config } from '@backstage/config'; import { ConfigApi } from '@backstage/core-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { ExtensionOverrides } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { FrontendFeature } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SubRouteRef } from '@backstage/frontend-plugin-api'; +// @public (undocumented) +export function createApp(options?: { + icons?: { + [key in string]: IconComponent; + }; + features?: (FrontendFeature | CreateAppFeatureLoader)[]; + configLoader?: () => Promise<{ + config: ConfigApi; + }>; + bindRoutes?(context: { bind: CreateAppRouteBinder }): void; +}): { + createRoot(): JSX_2.Element; +}; + // @public -export type AppRouteBinder = < +export interface CreateAppFeatureLoader { + getLoaderName(): string; + load(options: { config: ConfigApi }): Promise<{ + features: FrontendFeature[]; + }>; +} + +// @public +export type CreateAppRouteBinder = < TExternalRoutes extends { [name: string]: ExternalRouteRef; }, @@ -26,26 +48,17 @@ export type AppRouteBinder = < >, ) => void; -// @public (undocumented) -export function createApp(options?: { - features?: (BackstagePlugin | ExtensionOverrides)[]; - configLoader?: () => Promise; - bindRoutes?(context: { bind: AppRouteBinder }): void; - featureLoader?: (ctx: { - config: ConfigApi; - }) => Promise<(BackstagePlugin | ExtensionOverrides)[]>; -}): { - createRoot(): JSX_2.Element; -}; - // @public (undocumented) export function createExtensionTree(options: { config: Config }): ExtensionTree; // @public export function createSpecializedApp(options?: { - features?: (BackstagePlugin | ExtensionOverrides)[]; + icons?: { + [key in string]: IconComponent; + }; + features?: FrontendFeature[]; config?: ConfigApi; - bindRoutes?(context: { bind: AppRouteBinder }): void; + bindRoutes?(context: { bind: CreateAppRouteBinder }): void; }): { createRoot(): JSX_2.Element; }; diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index 7a32004273..a95702aed5 100644 --- a/packages/frontend-app-api/package.json +++ b/packages/frontend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-app-api", - "version": "0.3.1-next.0", + "version": "0.6.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -38,6 +38,7 @@ "@backstage/core-app-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts new file mode 100644 index 0000000000..4d8bc47e24 --- /dev/null +++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts @@ -0,0 +1,39 @@ +/* + * 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 { ComponentType } from 'react'; +import { ComponentRef, ComponentsApi } from '@backstage/frontend-plugin-api'; + +/** + * Implementation for the {@linkComponentApi} + * + * @internal + */ +export class DefaultComponentsApi implements ComponentsApi { + #components: Map, ComponentType>; + + constructor(components: Map, any>) { + this.#components = components; + } + + getComponent(ref: ComponentRef): ComponentType { + const impl = this.#components.get(ref); + if (!impl) { + throw new Error(`No implementation found for component ref ${ref}`); + } + return impl; + } +} diff --git a/plugins/scaffolder/src/legacy/MultistepJsonForm/index.ts b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts similarity index 85% rename from plugins/scaffolder/src/legacy/MultistepJsonForm/index.ts rename to packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts index b125e7c4c2..f04059c54f 100644 --- a/plugins/scaffolder/src/legacy/MultistepJsonForm/index.ts +++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { MultistepJsonForm } from './MultistepJsonForm'; + +export { DefaultComponentsApi } from './ComponentsApi'; diff --git a/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts new file mode 100644 index 0000000000..53a7fa6421 --- /dev/null +++ b/packages/frontend-app-api/src/apis/implementations/IconsApi/DefaultIconsApi.ts @@ -0,0 +1,38 @@ +/* + * 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 { IconComponent, IconsApi } from '@backstage/frontend-plugin-api'; + +/** + * Implementation for the {@link IconsApi} + * + * @internal + */ +export class DefaultIconsApi implements IconsApi { + #icons: Map; + + constructor(icons: { [key in string]: IconComponent }) { + this.#icons = new Map(Object.entries(icons)); + } + + getIcon(key: string): IconComponent | undefined { + return this.#icons.get(key); + } + + listIconKeys(): string[] { + return Array.from(this.#icons.keys()); + } +} diff --git a/packages/frontend-app-api/src/apis/implementations/IconsApi/index.ts b/packages/frontend-app-api/src/apis/implementations/IconsApi/index.ts new file mode 100644 index 0000000000..e07f6379ed --- /dev/null +++ b/packages/frontend-app-api/src/apis/implementations/IconsApi/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { DefaultIconsApi } from './DefaultIconsApi'; diff --git a/packages/frontend-app-api/src/extensions/Core.tsx b/packages/frontend-app-api/src/extensions/App.tsx similarity index 68% rename from packages/frontend-app-api/src/extensions/Core.tsx rename to packages/frontend-app-api/src/extensions/App.tsx index 60772d435c..751a255fa7 100644 --- a/packages/frontend-app-api/src/extensions/Core.tsx +++ b/packages/frontend-app-api/src/extensions/App.tsx @@ -16,19 +16,29 @@ import { coreExtensionData, + createApiExtension, + createComponentExtension, createExtension, createExtensionInput, + createThemeExtension, + createTranslationExtension, } from '@backstage/frontend-plugin-api'; -export const Core = createExtension({ - id: 'core', +export const App = createExtension({ + namespace: 'app', attachTo: { id: 'root', input: 'default' }, // ignored inputs: { apis: createExtensionInput({ - api: coreExtensionData.apiFactory, + api: createApiExtension.factoryDataRef, }), themes: createExtensionInput({ - theme: coreExtensionData.theme, + theme: createThemeExtension.themeDataRef, + }), + components: createExtensionInput({ + component: createComponentExtension.componentDataRef, + }), + translations: createExtensionInput({ + translation: createTranslationExtension.translationDataRef, }), root: createExtensionInput( { @@ -42,7 +52,7 @@ export const Core = createExtension({ }, factory({ inputs }) { return { - root: inputs.root.element, + root: inputs.root.output.element, }; }, }); diff --git a/packages/frontend-app-api/src/extensions/CoreLayout.tsx b/packages/frontend-app-api/src/extensions/AppLayout.tsx similarity index 85% rename from packages/frontend-app-api/src/extensions/CoreLayout.tsx rename to packages/frontend-app-api/src/extensions/AppLayout.tsx index 97f49acac9..7be0029c25 100644 --- a/packages/frontend-app-api/src/extensions/CoreLayout.tsx +++ b/packages/frontend-app-api/src/extensions/AppLayout.tsx @@ -22,9 +22,10 @@ import { } from '@backstage/frontend-plugin-api'; import { SidebarPage } from '@backstage/core-components'; -export const CoreLayout = createExtension({ - id: 'core.layout', - attachTo: { id: 'core', input: 'root' }, +export const AppLayout = createExtension({ + namespace: 'app', + name: 'layout', + attachTo: { id: 'app/root', input: 'children' }, inputs: { nav: createExtensionInput( { @@ -46,8 +47,8 @@ export const CoreLayout = createExtension({ return { element: ( - {inputs.nav.element} - {inputs.content.element} + {inputs.nav.output.element} + {inputs.content.output.element} ), }; diff --git a/packages/frontend-app-api/src/extensions/CoreNav.tsx b/packages/frontend-app-api/src/extensions/AppNav.tsx similarity index 73% rename from packages/frontend-app-api/src/extensions/CoreNav.tsx rename to packages/frontend-app-api/src/extensions/AppNav.tsx index 37ce50276b..c5bf028ef8 100644 --- a/packages/frontend-app-api/src/extensions/CoreNav.tsx +++ b/packages/frontend-app-api/src/extensions/AppNav.tsx @@ -19,8 +19,9 @@ import { createExtension, coreExtensionData, createExtensionInput, - NavTarget, useRouteRef, + createNavItemExtension, + createNavLogoExtension, } from '@backstage/frontend-plugin-api'; import { makeStyles } from '@material-ui/core'; import { @@ -51,33 +52,49 @@ const useSidebarLogoStyles = makeStyles({ }, }); -const SidebarLogo = () => { +const SidebarLogo = ( + props: (typeof createNavLogoExtension.logoElementsDataRef)['T'], +) => { const classes = useSidebarLogoStyles(); const { isOpen } = useSidebarOpenState(); return (
- {isOpen ? : } + {isOpen + ? props?.logoFull ?? + : props?.logoIcon ?? }
); }; -const SidebarNavItem = (props: NavTarget) => { +const SidebarNavItem = ( + props: (typeof createNavItemExtension.targetDataRef)['T'], +) => { const { icon: Icon, title, routeRef } = props; const to = useRouteRef(routeRef)(); // TODO: Support opening modal, for example, the search one return ; }; -export const CoreNav = createExtension({ - id: 'core.nav', - attachTo: { id: 'core.layout', input: 'nav' }, +export const AppNav = createExtension({ + namespace: 'app', + name: 'nav', + attachTo: { id: 'app/layout', input: 'nav' }, inputs: { items: createExtensionInput({ - target: coreExtensionData.navTarget, + target: createNavItemExtension.targetDataRef, }), + logos: createExtensionInput( + { + elements: createNavLogoExtension.logoElementsDataRef, + }, + { + singleton: true, + optional: true, + }, + ), }, output: { element: coreExtensionData.reactElement, @@ -86,10 +103,10 @@ export const CoreNav = createExtension({ return { element: ( - + {inputs.items.map((item, index) => ( - + ))} ), diff --git a/packages/frontend-app-api/src/extensions/AppRoot.tsx b/packages/frontend-app-api/src/extensions/AppRoot.tsx new file mode 100644 index 0000000000..f3a962dd10 --- /dev/null +++ b/packages/frontend-app-api/src/extensions/AppRoot.tsx @@ -0,0 +1,209 @@ +/* + * 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, { + ComponentType, + Fragment, + PropsWithChildren, + ReactNode, + useContext, + useState, +} from 'react'; +import { + coreExtensionData, + createAppRootWrapperExtension, + createExtension, + createExtensionInput, + createRouterExtension, + createSignInPageExtension, +} from '@backstage/frontend-plugin-api'; +import { + IdentityApi, + SignInPageProps, + configApiRef, + useApi, +} from '@backstage/core-plugin-api'; +import { InternalAppContext } from '../wiring/InternalAppContext'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; +import { BrowserRouter } from 'react-router-dom'; +import { RouteTracker } from '../routing/RouteTracker'; +import { getBasePath } from '../routing/getBasePath'; + +export const AppRoot = createExtension({ + namespace: 'app', + name: 'root', + attachTo: { id: 'app', input: 'root' }, + inputs: { + router: createExtensionInput( + { component: createRouterExtension.componentDataRef }, + { singleton: true, optional: true }, + ), + signInPage: createExtensionInput( + { component: createSignInPageExtension.componentDataRef }, + { singleton: true, optional: true }, + ), + children: createExtensionInput( + { element: coreExtensionData.reactElement }, + { singleton: true }, + ), + elements: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + wrappers: createExtensionInput({ + component: createAppRootWrapperExtension.componentDataRef, + }), + }, + output: { + element: coreExtensionData.reactElement, + }, + factory({ inputs }) { + let content: React.ReactNode = ( + <> + {inputs.elements.map(el => ( + {el.output.element} + ))} + {inputs.children.output.element} + + ); + + for (const wrapper of inputs.wrappers) { + content = {content}; + } + + return { + element: ( + + {content} + + ), + }; + }, +}); + +// This wraps the sign-in page and waits for sign-in to be completed before rendering the app +function SignInPageWrapper({ + component: Component, + appIdentityProxy, + children, +}: { + component: ComponentType; + appIdentityProxy: AppIdentityProxy; + children: ReactNode; +}) { + const [identityApi, setIdentityApi] = useState(); + const configApi = useApi(configApiRef); + const basePath = getBasePath(configApi); + + if (!identityApi) { + return ; + } + + appIdentityProxy.setTarget(identityApi, { + signOutTargetUrl: basePath || '/', + }); + return <>{children}; +} + +/** + * Props for the {@link AppRouter} component. + * @public + */ +export interface AppRouterProps { + children?: ReactNode; + SignInPageComponent?: ComponentType; + RouterComponent?: ComponentType>; +} + +function DefaultRouter(props: PropsWithChildren<{}>) { + const configApi = useApi(configApiRef); + const basePath = getBasePath(configApi); + return {props.children}; +} + +/** + * App router and sign-in page wrapper. + * + * @remarks + * + * The AppRouter provides the routing context and renders the sign-in page. + * Until the user has successfully signed in, this component will render + * the sign-in page. Once the user has signed-in, it will instead render + * the app, while providing routing and route tracking for the app. + */ +export function AppRouter(props: AppRouterProps) { + const { + children, + SignInPageComponent, + RouterComponent = DefaultRouter, + } = props; + + const configApi = useApi(configApiRef); + const basePath = getBasePath(configApi); + const internalAppContext = useContext(InternalAppContext); + if (!internalAppContext) { + throw new Error('AppRouter must be rendered within the AppProvider'); + } + const { routeObjects, appIdentityProxy } = internalAppContext; + + // If the app hasn't configured a sign-in page, we just continue as guest. + if (!SignInPageComponent) { + appIdentityProxy.setTarget( + { + getUserId: () => 'guest', + getIdToken: async () => undefined, + getProfile: () => ({ + email: 'guest@example.com', + displayName: 'Guest', + }), + getProfileInfo: async () => ({ + email: 'guest@example.com', + displayName: 'Guest', + }), + getBackstageIdentity: async () => ({ + type: 'user', + userEntityRef: 'user:default/guest', + ownershipEntityRefs: ['user:default/guest'], + }), + getCredentials: async () => ({}), + signOut: async () => {}, + }, + { signOutTargetUrl: basePath || '/' }, + ); + + return ( + + + {children} + + ); + } + + return ( + + + + {children} + + + ); +} diff --git a/packages/frontend-app-api/src/extensions/CoreRoutes.tsx b/packages/frontend-app-api/src/extensions/AppRoutes.tsx similarity index 69% rename from packages/frontend-app-api/src/extensions/CoreRoutes.tsx rename to packages/frontend-app-api/src/extensions/AppRoutes.tsx index 985fd07be9..38a9291755 100644 --- a/packages/frontend-app-api/src/extensions/CoreRoutes.tsx +++ b/packages/frontend-app-api/src/extensions/AppRoutes.tsx @@ -19,12 +19,15 @@ import { createExtension, coreExtensionData, createExtensionInput, + coreComponentRefs, + useComponentRef, } from '@backstage/frontend-plugin-api'; import { useRoutes } from 'react-router-dom'; -export const CoreRoutes = createExtension({ - id: 'core.routes', - attachTo: { id: 'core.layout', input: 'content' }, +export const AppRoutes = createExtension({ + namespace: 'app', + name: 'routes', + attachTo: { id: 'app/layout', input: 'content' }, inputs: { routes: createExtensionInput({ path: coreExtensionData.routePath, @@ -37,15 +40,24 @@ export const CoreRoutes = createExtension({ }, factory({ inputs }) { const Routes = () => { - const element = useRoutes( - inputs.routes.map(route => ({ - path: `${route.path}/*`, - element: route.element, - })), + const NotFoundErrorPage = useComponentRef( + coreComponentRefs.notFoundErrorPage, ); + const element = useRoutes([ + ...inputs.routes.map(route => ({ + path: `${route.output.path}/*`, + element: route.output.element, + })), + { + path: '*', + element: , + }, + ]); + return element; }; + return { element: , }; diff --git a/packages/frontend-app-api/src/extensions/components.tsx b/packages/frontend-app-api/src/extensions/components.tsx new file mode 100644 index 0000000000..3064737e21 --- /dev/null +++ b/packages/frontend-app-api/src/extensions/components.tsx @@ -0,0 +1,56 @@ +/* + * 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'; +// TODO: Dependency on MUI should be removed from core packages +import { Button } from '@material-ui/core'; + +import { + createComponentExtension, + coreComponentRefs, +} from '@backstage/frontend-plugin-api'; +import { ErrorPanel } from '@backstage/core-components'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { components as defaultComponents } from '../../../app-defaults/src/defaults'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports + +export const DefaultProgressComponent = createComponentExtension({ + ref: coreComponentRefs.progress, + loader: { sync: () => defaultComponents.Progress }, +}); + +export const DefaultNotFoundErrorPageComponent = createComponentExtension({ + ref: coreComponentRefs.notFoundErrorPage, + loader: { sync: () => defaultComponents.NotFoundErrorPage }, +}); + +export const DefaultErrorBoundaryComponent = createComponentExtension({ + ref: coreComponentRefs.errorBoundaryFallback, + loader: { + sync: () => props => { + const { plugin, error, resetError } = props; + const title = `Error in ${plugin?.id}`; + + return ( + + + + ); + }, + }, +}); diff --git a/packages/frontend-app-api/src/extensions/elements.tsx b/packages/frontend-app-api/src/extensions/elements.tsx new file mode 100644 index 0000000000..0a764583bb --- /dev/null +++ b/packages/frontend-app-api/src/extensions/elements.tsx @@ -0,0 +1,45 @@ +/* + * 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 { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; +import { + createAppRootElementExtension, + createSchemaFromZod, +} from '@backstage/frontend-plugin-api'; +import React from 'react'; + +export const oauthRequestDialogAppRootElement = createAppRootElementExtension({ + namespace: 'app', + name: 'oauth-request-dialog', + element: , +}); + +export const alertDisplayAppRootElement = createAppRootElementExtension({ + namespace: 'app', + name: 'alert-display', + configSchema: createSchemaFromZod(z => + z.object({ + transientTimeoutMs: z.number().default(5000), + anchorOrigin: z + .object({ + vertical: z.enum(['top', 'bottom']).default('top'), + horizontal: z.enum(['left', 'center', 'right']).default('center'), + }) + .default({}), + }), + ), + element: ({ config }) => , +}); diff --git a/packages/frontend-app-api/src/routing/RouteResolver.test.ts b/packages/frontend-app-api/src/routing/RouteResolver.test.ts index 81e9d10be4..251241a7f9 100644 --- a/packages/frontend-app-api/src/routing/RouteResolver.test.ts +++ b/packages/frontend-app-api/src/routing/RouteResolver.test.ts @@ -45,20 +45,26 @@ const externalRef2 = createExternalRouteRef({ optional: true }); const externalRef3 = createExternalRouteRef({ params: ['x'] }); const externalRef4 = createExternalRouteRef({ optional: true, params: ['x'] }); +function src(sourcePath: string) { + return { sourcePath }; +} + describe('RouteResolver', () => { it('should not resolve anything with an empty resolver', () => { const r = new RouteResolver(new Map(), new Map(), [], new Map(), ''); - expect(r.resolve(ref1, '/')?.()).toBe(undefined); - expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined); - expect(r.resolve(subRef1, '/')?.()).toBe(undefined); - expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe(undefined); - expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(undefined); - expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(undefined); - expect(r.resolve(externalRef1, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef2, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined); - expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined); + expect(r.resolve(ref1, src('/'))?.()).toBe(undefined); + expect(r.resolve(ref2, src('/'))?.({ x: '1x' })).toBe(undefined); + expect(r.resolve(subRef1, src('/'))?.()).toBe(undefined); + expect(r.resolve(subRef2, src('/'))?.({ a: '2a' })).toBe(undefined); + expect(r.resolve(subRef3, src('/'))?.({ x: '3x' })).toBe(undefined); + expect(r.resolve(subRef4, src('/'))?.({ x: '4x', a: '4a' })).toBe( + undefined, + ); + expect(r.resolve(externalRef1, src('/'))?.()).toBe(undefined); + expect(r.resolve(externalRef2, src('/'))?.()).toBe(undefined); + expect(r.resolve(externalRef3, src('/'))?.({ x: '5x' })).toBe(undefined); + expect(r.resolve(externalRef4, src('/'))?.({ x: '6x' })).toBe(undefined); }); it('should resolve an absolute route', () => { @@ -70,16 +76,20 @@ describe('RouteResolver', () => { '', ); - expect(r.resolve(ref1, '/')?.()).toBe('/my-route'); - expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined); - expect(r.resolve(subRef1, '/')?.()).toBe('/my-route/foo'); - expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe('/my-route/foo/2a'); - expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(undefined); - expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(undefined); - expect(r.resolve(externalRef1, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef2, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined); - expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined); + expect(r.resolve(ref1, src('/'))?.()).toBe('/my-route'); + expect(r.resolve(ref2, src('/'))?.({ x: '1x' })).toBe(undefined); + expect(r.resolve(subRef1, src('/'))?.()).toBe('/my-route/foo'); + expect(r.resolve(subRef2, src('/'))?.({ a: '2a' })).toBe( + '/my-route/foo/2a', + ); + expect(r.resolve(subRef3, src('/'))?.({ x: '3x' })).toBe(undefined); + expect(r.resolve(subRef4, src('/'))?.({ x: '4x', a: '4a' })).toBe( + undefined, + ); + expect(r.resolve(externalRef1, src('/'))?.()).toBe(undefined); + expect(r.resolve(externalRef2, src('/'))?.()).toBe(undefined); + expect(r.resolve(externalRef3, src('/'))?.({ x: '5x' })).toBe(undefined); + expect(r.resolve(externalRef4, src('/'))?.({ x: '6x' })).toBe(undefined); }); it('should resolve an absolute route and sub route with an app base path', () => { @@ -104,37 +114,39 @@ describe('RouteResolver', () => { '/base', ); - expect(r.resolve(ref1, '/my-parent/1x')?.()).toBe( + expect(r.resolve(ref1, src('/my-parent/1x'))?.()).toBe( '/base/my-parent/1x/my-route', ); - expect(r.resolve(ref1, '/base/my-parent/1x')?.()).toBe( + expect(r.resolve(ref1, src('/base/my-parent/1x'))?.()).toBe( '/base/my-parent/1x/my-route', ); - expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe('/base/my-parent/1x'); - expect(r.resolve(ref2, '/base')?.({ x: '1x' })).toBe('/base/my-parent/1x'); - expect(r.resolve(ref3, '/')?.({ y: '1y' })).toBe(undefined); - expect(r.resolve(subRef1, '/my-parent/2x')?.()).toBe( + expect(r.resolve(ref2, src('/'))?.({ x: '1x' })).toBe('/base/my-parent/1x'); + expect(r.resolve(ref2, src('/base'))?.({ x: '1x' })).toBe( + '/base/my-parent/1x', + ); + expect(r.resolve(ref3, src('/'))?.({ y: '1y' })).toBe(undefined); + expect(r.resolve(subRef1, src('/my-parent/2x'))?.()).toBe( '/base/my-parent/2x/my-route/foo', ); - expect(r.resolve(subRef1, '/base/my-parent/2x')?.()).toBe( + expect(r.resolve(subRef1, src('/base/my-parent/2x'))?.()).toBe( '/base/my-parent/2x/my-route/foo', ); - expect(r.resolve(subRef2, '/my-parent/3x')?.({ a: '2a' })).toBe( + expect(r.resolve(subRef2, src('/my-parent/3x'))?.({ a: '2a' })).toBe( '/base/my-parent/3x/my-route/foo/2a', ); - expect(r.resolve(subRef2, '/base/my-parent/3x')?.({ a: '2a' })).toBe( + expect(r.resolve(subRef2, src('/base/my-parent/3x'))?.({ a: '2a' })).toBe( '/base/my-parent/3x/my-route/foo/2a', ); - expect(r.resolve(subRef3, '/')?.({ x: '5x' })).toBe( + expect(r.resolve(subRef3, src('/'))?.({ x: '5x' })).toBe( '/base/my-parent/5x/bar', ); - expect(r.resolve(subRef4, '/')?.({ x: '6x', a: '4a' })).toBe( + expect(r.resolve(subRef4, src('/'))?.({ x: '6x', a: '4a' })).toBe( '/base/my-parent/6x/bar/4a', ); - expect(r.resolve(externalRef1, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef2, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined); - expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined); + expect(r.resolve(externalRef1, src('/'))?.()).toBe(undefined); + expect(r.resolve(externalRef2, src('/'))?.()).toBe(undefined); + expect(r.resolve(externalRef3, src('/'))?.({ x: '5x' })).toBe(undefined); + expect(r.resolve(externalRef4, src('/'))?.({ x: '6x' })).toBe(undefined); }); it('should resolve an absolute route with a param and with a parent', () => { @@ -163,22 +175,26 @@ describe('RouteResolver', () => { '', ); - expect(r.resolve(ref1, '/')?.()).toBe('/my-route'); - expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe('/my-route/my-parent/1x'); - expect(r.resolve(subRef1, '/')?.()).toBe('/my-route/foo'); - expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe('/my-route/foo/2a'); - expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe( + expect(r.resolve(ref1, src('/'))?.()).toBe('/my-route'); + expect(r.resolve(ref2, src('/'))?.({ x: '1x' })).toBe( + '/my-route/my-parent/1x', + ); + expect(r.resolve(subRef1, src('/'))?.()).toBe('/my-route/foo'); + expect(r.resolve(subRef2, src('/'))?.({ a: '2a' })).toBe( + '/my-route/foo/2a', + ); + expect(r.resolve(subRef3, src('/'))?.({ x: '3x' })).toBe( '/my-route/my-parent/3x/bar', ); - expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe( + expect(r.resolve(subRef4, src('/'))?.({ x: '4x', a: '4a' })).toBe( '/my-route/my-parent/4x/bar/4a', ); - expect(r.resolve(externalRef1, '/')?.()).toBe('/my-route'); - expect(r.resolve(externalRef2, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe( + expect(r.resolve(externalRef1, src('/'))?.()).toBe('/my-route'); + expect(r.resolve(externalRef2, src('/'))?.()).toBe(undefined); + expect(r.resolve(externalRef3, src('/'))?.({ x: '5x' })).toBe( '/my-route/my-parent/5x', ); - expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe( + expect(r.resolve(externalRef4, src('/'))?.({ x: '6x' })).toBe( '/my-route/my-parent/6x/bar', ); }); @@ -221,18 +237,20 @@ describe('RouteResolver', () => { '', ); - expect(r.resolve(ref2, '/')?.({ x: 'x' })).toBe('/root/x'); - expect(r.resolve(ref3, '/root/x')?.({ y: 'y' })).toBe('/root/x/sub/y'); + expect(r.resolve(ref2, src('/'))?.({ x: 'x' })).toBe('/root/x'); + expect(r.resolve(ref3, src('/root/x'))?.({ y: 'y' })).toBe('/root/x/sub/y'); - expect(() => r.resolve(ref1, '/')?.()).toThrow( + expect(() => r.resolve(ref1, src('/'))?.()).toThrow( /^Cannot route.*with parent.*as it has parameters$/, ); - expect(() => r.resolve(ref1, '/root/x')?.()).toThrow( + expect(() => r.resolve(ref1, src('/root/x'))?.()).toThrow( /^Cannot route.*with parent.*as it has parameters$/, ); - expect(r.resolve(ref1, '/root/x/sub/y')?.()).toBe('/root/x/sub/y/deep'); + expect(r.resolve(ref1, src('/root/x/sub/y'))?.()).toBe( + '/root/x/sub/y/deep', + ); // Without the MATCH_ALL_ROUTE, we wouldn't properly match the route here - expect(r.resolve(ref1, '/root/x/sub/y/any/nested/path/here')?.()).toBe( + expect(r.resolve(ref1, src('/root/x/sub/y/any/nested/path/here'))?.()).toBe( '/root/x/sub/y/deep', ); }); @@ -276,62 +294,62 @@ describe('RouteResolver', () => { ); const l = '/my-grandparent/my-y/my-parent/my-x'; - expect(r.resolve(ref1, l)?.()).toBe( + expect(r.resolve(ref1, src(l))?.()).toBe( '/my-grandparent/my-y/my-parent/my-x/my-route', ); - expect(() => r.resolve(ref1, '/')?.()).toThrow( + expect(() => r.resolve(ref1, src('/'))?.()).toThrow( /^Cannot route.*with parent.*as it has parameters$/, ); - expect(r.resolve(ref2, l)?.({ x: '1x' })).toBe( + expect(r.resolve(ref2, src(l))?.({ x: '1x' })).toBe( '/my-grandparent/my-y/my-parent/1x', ); - expect(r.resolve(ref2, '/my-grandparent/my-y')?.({ x: '1x' })).toBe( + expect(r.resolve(ref2, src('/my-grandparent/my-y'))?.({ x: '1x' })).toBe( '/my-grandparent/my-y/my-parent/1x', ); - expect(() => r.resolve(ref2, '/')?.({ x: '1x' })).toThrow( + expect(() => r.resolve(ref2, src('/'))?.({ x: '1x' })).toThrow( /^Cannot route.*with parent.*as it has parameters$/, ); - expect(r.resolve(subRef1, l)?.()).toBe( + expect(r.resolve(subRef1, src(l))?.()).toBe( '/my-grandparent/my-y/my-parent/my-x/my-route/foo', ); - expect(() => r.resolve(subRef1, '/')?.()).toThrow( + expect(() => r.resolve(subRef1, src('/'))?.()).toThrow( /^Cannot route.*with parent.*as it has parameters$/, ); - expect(r.resolve(subRef2, l)?.({ a: '2a' })).toBe( + expect(r.resolve(subRef2, src(l))?.({ a: '2a' })).toBe( '/my-grandparent/my-y/my-parent/my-x/my-route/foo/2a', ); - expect(() => r.resolve(subRef2, '/')?.({ a: '2a' })).toThrow( + expect(() => r.resolve(subRef2, src('/'))?.({ a: '2a' })).toThrow( /^Cannot route.*with parent.*as it has parameters$/, ); - expect(r.resolve(subRef3, l)?.({ x: '3x' })).toBe( + expect(r.resolve(subRef3, src(l))?.({ x: '3x' })).toBe( '/my-grandparent/my-y/my-parent/3x/bar', ); - expect(r.resolve(subRef3, '/my-grandparent/my-y')?.({ x: '3x' })).toBe( + expect(r.resolve(subRef3, src('/my-grandparent/my-y'))?.({ x: '3x' })).toBe( '/my-grandparent/my-y/my-parent/3x/bar', ); - expect(r.resolve(subRef4, l)?.({ x: '4x', a: '4a' })).toBe( + expect(r.resolve(subRef4, src(l))?.({ x: '4x', a: '4a' })).toBe( '/my-grandparent/my-y/my-parent/4x/bar/4a', ); expect( - r.resolve(subRef4, '/my-grandparent/my-y')?.({ x: '4x', a: '4a' }), + r.resolve(subRef4, src('/my-grandparent/my-y'))?.({ x: '4x', a: '4a' }), ).toBe('/my-grandparent/my-y/my-parent/4x/bar/4a'); - expect(r.resolve(externalRef1, l)?.()).toBe( + expect(r.resolve(externalRef1, src(l))?.()).toBe( '/my-grandparent/my-y/my-parent/my-x/my-route', ); - expect(() => r.resolve(externalRef1, '/')?.()).toThrow( + expect(() => r.resolve(externalRef1, src('/'))?.()).toThrow( /^Cannot route.*with parent.*as it has parameters$/, ); - expect(r.resolve(externalRef2, l)?.()).toBe(undefined); - expect(r.resolve(externalRef3, l)?.({ x: '5x' })).toBe( + expect(r.resolve(externalRef2, src(l))?.()).toBe(undefined); + expect(r.resolve(externalRef3, src(l))?.({ x: '5x' })).toBe( '/my-grandparent/my-y/my-parent/5x', ); - expect(() => r.resolve(externalRef3, '/')?.({ x: '5x' })).toThrow( + expect(() => r.resolve(externalRef3, src('/'))?.({ x: '5x' })).toThrow( /^Cannot route.*with parent.*as it has parameters$/, ); - expect(r.resolve(externalRef4, l)?.({ x: '6x' })).toBe( + expect(r.resolve(externalRef4, src(l))?.({ x: '6x' })).toBe( '/my-grandparent/my-y/my-parent/6x/bar', ); - expect(() => r.resolve(externalRef4, '/')?.({ x: '6x' })).toThrow( + expect(() => r.resolve(externalRef4, src('/'))?.({ x: '6x' })).toThrow( /^Cannot route.*with parent.*as it has parameters$/, ); }); @@ -358,7 +376,7 @@ describe('RouteResolver', () => { '/base', ); - expect(r.resolve(ref2, '/')?.({ x: 'a/#&?b' })).toBe( + expect(r.resolve(ref2, src('/'))?.({ x: 'a/#&?b' })).toBe( '/base/my-parent/a%2F%23%26%3Fb', ); }); diff --git a/packages/frontend-app-api/src/routing/RouteResolver.ts b/packages/frontend-app-api/src/routing/RouteResolver.ts index 77336988e2..5405bb4d7f 100644 --- a/packages/frontend-app-api/src/routing/RouteResolver.ts +++ b/packages/frontend-app-api/src/routing/RouteResolver.ts @@ -21,6 +21,8 @@ import { SubRouteRef, AnyRouteRefParams, RouteFunc, + RouteResolutionApiResolveOptions, + RouteResolutionApi, } from '@backstage/frontend-plugin-api'; import mapValues from 'lodash/mapValues'; import { AnyRouteRef, BackstageRouteObject } from './types'; @@ -177,7 +179,7 @@ function resolveBasePath( return `${joinPaths(parentPath, ...diffPaths)}/`; } -export class RouteResolver { +export class RouteResolver implements RouteResolutionApi { constructor( private readonly routePaths: Map, private readonly routeParents: Map, @@ -189,13 +191,13 @@ export class RouteResolver { private readonly appBasePath: string, // base path without a trailing slash ) {} - resolve( + resolve( anyRouteRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, - sourceLocation: Parameters[1], - ): RouteFunc | undefined { + | RouteRef + | SubRouteRef + | ExternalRouteRef, + options?: RouteResolutionApiResolveOptions, + ): RouteFunc | undefined { // First figure out what our target absolute ref is, as well as our target path. const [targetRef, targetPath] = resolveTargetRef( anyRouteRef, @@ -208,17 +210,7 @@ export class RouteResolver { // The location that we get passed in uses the full path, so start by trimming off // the app base path prefix in case we're running the app on a sub-path. - let relativeSourceLocation: Parameters[1]; - if (typeof sourceLocation === 'string') { - relativeSourceLocation = this.trimPath(sourceLocation); - } else if (sourceLocation.pathname) { - relativeSourceLocation = { - ...sourceLocation, - pathname: this.trimPath(sourceLocation.pathname), - }; - } else { - relativeSourceLocation = sourceLocation; - } + const relativeSourceLocation = this.trimPath(options?.sourcePath ?? ''); // Next we figure out the base path, which is the combination of the common parent path // between our current location and our target location, as well as the additional path @@ -233,7 +225,7 @@ export class RouteResolver { this.routeObjects, ); - const routeFunc: RouteFunc = (...[params]) => { + const routeFunc: RouteFunc = (...[params]) => { // We selectively encode some some known-dangerous characters in the // params. The reason that we don't perform a blanket `encodeURIComponent` // here is that this encoding was added defensively long after the initial diff --git a/packages/frontend-app-api/src/routing/RouteTracker.test.tsx b/packages/frontend-app-api/src/routing/RouteTracker.test.tsx new file mode 100644 index 0000000000..0e66d0e50d --- /dev/null +++ b/packages/frontend-app-api/src/routing/RouteTracker.test.tsx @@ -0,0 +1,254 @@ +/* + * 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 { TestApiProvider } from '@backstage/test-utils'; +import React, { useEffect } from 'react'; +import { BackstageRouteObject } from './types'; +import { fireEvent, render } from '@testing-library/react'; +import { RouteTracker } from './RouteTracker'; +import { Link, MemoryRouter, Route, Routes } from 'react-router-dom'; +import { createPlugin } from '@backstage/core-plugin-api'; +import { + createRouteRef, + AnalyticsApi, + analyticsApiRef, + AppNode, + useAnalytics, +} from '@backstage/frontend-plugin-api'; +import { MATCH_ALL_ROUTE } from './extractRouteInfoFromAppNode'; + +describe('RouteTracker', () => { + const routeRef0 = createRouteRef(); + const routeRef1 = createRouteRef(); + const routeRef2 = createRouteRef(); + const plugin0 = createPlugin({ id: 'home' }); + const plugin1 = createPlugin({ id: 'plugin1' }); + const plugin2 = createPlugin({ id: 'plugin2' }); + + const routeObjects: BackstageRouteObject[] = [ + { + path: '', + element:
home page
, + routeRefs: new Set([routeRef0]), + plugins: new Set([plugin0]), + caseSensitive: false, + children: [MATCH_ALL_ROUTE], + appNode: { + spec: { extension: { id: 'home.page.index' }, source: { id: 'home' } }, + } as AppNode, + }, + { + path: '/path/:p1/:p2', + element: go, + routeRefs: new Set([routeRef1]), + plugins: new Set([plugin1]), + caseSensitive: false, + children: [MATCH_ALL_ROUTE], + appNode: { + spec: { + extension: { id: 'plugin1.page.index' }, + source: { id: 'plugin1' }, + }, + } as AppNode, + }, + { + path: '/path2/:param', + element:
hi there
, + routeRefs: new Set([routeRef2]), + plugins: new Set([plugin2]), + caseSensitive: false, + children: [MATCH_ALL_ROUTE], + appNode: { + spec: { + extension: { id: 'plugin2.page.index' }, + source: { id: 'plugin2' }, + }, + } as AppNode, + }, + ]; + + const mockedAnalytics: jest.Mocked = { + captureEvent: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should capture the navigate event on load', async () => { + render( + + + + + , + ); + + expect(mockedAnalytics.captureEvent).toHaveBeenCalledWith({ + action: 'navigate', + attributes: { + p1: 'foo', + p2: 'bar', + }, + context: { + extensionId: 'plugin1.page.index', + pluginId: 'plugin1', + }, + subject: '/path/foo/bar', + value: undefined, + }); + }); + + it('should capture the navigate event on route change', async () => { + const { getByText } = render( + + + + + + {routeObjects.map(({ path, element }) => ( + + ))} + + + , + ); + + fireEvent.click(getByText('go')); + + expect(mockedAnalytics.captureEvent).toHaveBeenCalledWith({ + action: 'navigate', + attributes: { + param: 'hello', + }, + context: { + extensionId: 'plugin2.page.index', + pluginId: 'plugin2', + }, + subject: '/path2/hello', + value: undefined, + }); + }); + + it('should capture path query and hash', async () => { + render( + + + + + , + ); + + expect(mockedAnalytics.captureEvent).toHaveBeenCalledWith({ + action: 'navigate', + attributes: { + p1: 'foo', + p2: 'bar', + }, + context: { + extensionId: 'plugin1.page.index', + pluginId: 'plugin1', + }, + subject: '/path/foo/bar?q=1#header-1', + value: undefined, + }); + }); + + it('should match the root path and send relevant context', async () => { + render( + + + + + , + ); + + expect(mockedAnalytics.captureEvent).toHaveBeenCalledWith({ + action: 'navigate', + attributes: {}, + context: { + extensionId: 'home.page.index', + pluginId: 'home', + }, + subject: '/', + value: undefined, + }); + }); + + it('should return default context when it would have otherwise matched on the root path', async () => { + const Dummy = () => { + const analytics = useAnalytics(); + useEffect(() => { + analytics.captureEvent('click', 'test', {}); + }, [analytics]); + return
dummy
; + }; + + render( + + + + + } /> + + + , + ); + + expect(mockedAnalytics.captureEvent).toHaveBeenNthCalledWith(1, { + action: 'navigate', + attributes: {}, + context: { + extensionId: 'App', + pluginId: 'root', + }, + subject: '/not-routable-extension', + value: undefined, + }); + expect(mockedAnalytics.captureEvent).toHaveBeenNthCalledWith(2, { + action: 'click', + attributes: undefined, + context: { + extensionId: 'App', + pluginId: 'root', + }, + subject: 'test', + value: undefined, + }); + }); + + it('should return parent route context on navigating to a sub-route', async () => { + render( + + + + + , + ); + + expect(mockedAnalytics.captureEvent).toHaveBeenCalledWith({ + action: 'navigate', + attributes: { + param: 'param-value', + }, + context: { + extensionId: 'plugin2.page.index', + pluginId: 'plugin2', + }, + subject: '/path2/param-value/sub-route', + value: undefined, + }); + }); +}); diff --git a/packages/frontend-app-api/src/routing/RouteTracker.tsx b/packages/frontend-app-api/src/routing/RouteTracker.tsx new file mode 100644 index 0000000000..8cddca5c68 --- /dev/null +++ b/packages/frontend-app-api/src/routing/RouteTracker.tsx @@ -0,0 +1,129 @@ +/* + * 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, { useEffect } from 'react'; +import { matchRoutes, useLocation } from 'react-router-dom'; +import { + useAnalytics, + AnalyticsContext, + AnalyticsEventAttributes, +} from '@backstage/frontend-plugin-api'; +import { BackstageRouteObject } from './types'; + +/** + * Returns an extension context given the current pathname and a list of + * Backstage route objects. + */ +const getExtensionContext = ( + pathname: string, + routes: BackstageRouteObject[], +) => { + try { + // Find matching routes for the given path name. + const matches = matchRoutes(routes, { pathname }); + + // Of the matching routes, get the last (e.g. most specific) instance of + // the BackstageRouteObject that contains a routeRef. Filtering by routeRef + // ensures subRouteRefs are aligned to their parent routes' context. + const routeMatch = matches + ?.filter(match => match?.route.routeRefs?.size > 0) + .pop(); + const routeObject = routeMatch?.route; + + // If there is no route object, then allow inheritance of default context. + if (!routeObject) { + return undefined; + } + + // If the matched route is the root route (no path), and the pathname is + // not the path of the homepage, then inherit from the default context. + if (routeObject.path === '' && pathname !== '/') { + return undefined; + } + + const params = Object.entries( + routeMatch?.params || {}, + ).reduce((acc, [key, value]) => { + if (value !== undefined && key !== '*') { + acc[key] = value; + } + return acc; + }, {}); + + const plugin = routeObject.appNode?.spec.source; + const extension = routeObject.appNode?.spec.extension; + + return { + params, + pluginId: plugin?.id || 'root', + extensionId: extension?.id || 'App', + }; + } catch { + return undefined; + } +}; + +/** + * Performs the actual event capture on render. + */ +const TrackNavigation = ({ + pathname, + search, + hash, + attributes, +}: { + pathname: string; + search: string; + hash: string; + attributes?: AnalyticsEventAttributes; +}) => { + const analytics = useAnalytics(); + useEffect(() => { + analytics.captureEvent('navigate', `${pathname}${search}${hash}`, { + attributes, + }); + }, [analytics, pathname, search, hash, attributes]); + + return null; +}; + +/** + * Logs a "navigate" event with appropriate plugin-level analytics context + * attributes each time the user navigates to a page. + */ +export const RouteTracker = ({ + routeObjects, +}: { + routeObjects: BackstageRouteObject[]; +}) => { + const { pathname, search, hash } = useLocation(); + + const { params, ...attributes } = getExtensionContext( + pathname, + routeObjects, + ) || { params: {} }; + + return ( + + + + ); +}; diff --git a/packages/frontend-app-api/src/routing/RoutingProvider.tsx b/packages/frontend-app-api/src/routing/RoutingProvider.tsx deleted file mode 100644 index 965faa3655..0000000000 --- a/packages/frontend-app-api/src/routing/RoutingProvider.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 React, { ReactNode } from 'react'; -import { - ExternalRouteRef, - RouteRef, - SubRouteRef, -} from '@backstage/frontend-plugin-api'; -import { - createVersionedValueMap, - createVersionedContext, -} from '@backstage/version-bridge'; -import { RouteResolver } from './RouteResolver'; -import { BackstageRouteObject } from './types'; - -const RoutingContext = createVersionedContext<{ 1: RouteResolver }>( - 'routing-context', -); - -type ProviderProps = { - routePaths: Map; - routeParents: Map; - routeObjects: BackstageRouteObject[]; - routeBindings: Map; - basePath?: string; - children: ReactNode; -}; - -// TODO(Rugvip): Migrate to a routing API instead -export const RoutingProvider = ({ - routePaths, - routeParents, - routeObjects, - routeBindings, - basePath = '', - children, -}: ProviderProps) => { - const resolver = new RouteResolver( - routePaths, - routeParents, - routeObjects, - routeBindings, - basePath, - ); - - const versionedValue = createVersionedValueMap({ 1: resolver }); - return ( - - {children} - - ); -}; diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.test.ts b/packages/frontend-app-api/src/routing/collectRouteIds.test.ts index 95a5afd8cd..6b9fdecf70 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.test.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.test.ts @@ -37,15 +37,13 @@ describe('collectRouteIds', () => { createPlugin({ id: 'test', routes: { ref }, externalRoutes: { extRef } }), ]); expect(Object.fromEntries(collected.routes)).toEqual({ - 'plugin.test.routes.ref': ref, + 'test.ref': ref, }); expect(Object.fromEntries(collected.externalRoutes)).toEqual({ - 'plugin.test.externalRoutes.extRef': extRef, + 'test.extRef': extRef, }); - expect(String(ref)).toBe('RouteRef{plugin.test.routes.ref}'); - expect(String(extRef)).toBe( - 'ExternalRouteRef{plugin.test.externalRoutes.extRef}', - ); + expect(String(ref)).toBe('RouteRef{test.ref}'); + expect(String(extRef)).toBe('ExternalRouteRef{test.extRef}'); }); }); diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.ts b/packages/frontend-app-api/src/routing/collectRouteIds.ts index 7e2a4cad2e..d45871ae89 100644 --- a/packages/frontend-app-api/src/routing/collectRouteIds.ts +++ b/packages/frontend-app-api/src/routing/collectRouteIds.ts @@ -15,16 +15,20 @@ */ import { - BackstagePlugin, - ExtensionOverrides, RouteRef, SubRouteRef, ExternalRouteRef, + FrontendFeature, } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalRouteRef } from '../../../frontend-plugin-api/src/routing/RouteRef'; +import { + isRouteRef, + toInternalRouteRef, +} from '../../../frontend-plugin-api/src/routing/RouteRef'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalExternalRouteRef } from '../../../frontend-plugin-api/src/routing/ExternalRouteRef'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalSubRouteRef } from '../../../frontend-plugin-api/src/routing/SubRouteRef'; /** @internal */ export interface RouteRefsById { @@ -33,9 +37,7 @@ export interface RouteRefsById { } /** @internal */ -export function collectRouteIds( - features: (BackstagePlugin | ExtensionOverrides)[], -): RouteRefsById { +export function collectRouteIds(features: FrontendFeature[]): RouteRefsById { const routesById = new Map(); const externalRoutesById = new Map(); @@ -45,17 +47,22 @@ export function collectRouteIds( } for (const [name, ref] of Object.entries(feature.routes)) { - const refId = `plugin.${feature.id}.routes.${name}`; + const refId = `${feature.id}.${name}`; if (routesById.has(refId)) { throw new Error(`Unexpected duplicate route '${refId}'`); } - const internalRef = toInternalRouteRef(ref); - internalRef.setId(refId); - routesById.set(refId, ref); + if (isRouteRef(ref)) { + const internalRef = toInternalRouteRef(ref); + internalRef.setId(refId); + routesById.set(refId, ref); + } else { + const internalRef = toInternalSubRouteRef(ref); + routesById.set(refId, internalRef); + } } for (const [name, ref] of Object.entries(feature.externalRoutes)) { - const refId = `plugin.${feature.id}.externalRoutes.${name}`; + const refId = `${feature.id}.${name}`; if (externalRoutesById.has(refId)) { throw new Error(`Unexpected duplicate external route '${refId}'`); } diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts index c44c10180a..1c332b331f 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts @@ -19,7 +19,8 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { extractRouteInfoFromAppNode } from './extractRouteInfoFromAppNode'; import { AnyRouteRefParams, - Extension, + AppNode, + ExtensionDefinition, RouteRef, coreExtensionData, createExtension, @@ -29,10 +30,7 @@ import { } from '@backstage/frontend-plugin-api'; import { MockConfigApi } from '@backstage/test-utils'; import { createAppTree } from '../tree'; -import { Core } from '../extensions/Core'; -import { CoreRoutes } from '../extensions/CoreRoutes'; -import { CoreNav } from '../extensions/CoreNav'; -import { CoreLayout } from '../extensions/CoreLayout'; +import { builtinExtensions } from '../wiring/createApp'; const ref1 = createRouteRef(); const ref2 = createRouteRef(); @@ -42,16 +40,16 @@ const ref5 = createRouteRef(); const refOrder: RouteRef[] = [ref1, ref2, ref3, ref4, ref5]; function createTestExtension(options: { - id: string; + name: string; parent?: string; path?: string; routeRef?: RouteRef; }) { return createExtension({ - id: options.id, + name: options.name, attachTo: options.parent - ? { id: options.parent, input: 'children' } - : { id: 'core.routes', input: 'routes' }, + ? { id: `test/${options.parent}`, input: 'children' } + : { id: 'app/routes', input: 'routes' }, output: { element: coreExtensionData.reactElement, path: coreExtensionData.routePath.optional(), @@ -72,14 +70,14 @@ function createTestExtension(options: { }); } -function routeInfoFromExtensions(extensions: Extension[]) { +function routeInfoFromExtensions(extensions: ExtensionDefinition[]) { const plugin = createPlugin({ id: 'test', extensions, }); const tree = createAppTree({ config: new MockConfigApi({}), - builtinExtensions: [Core, CoreRoutes, CoreNav, CoreLayout], + builtinExtensions, features: [plugin], }); @@ -98,8 +96,10 @@ function routeObj( children: any[] = [], type: 'mounted' | 'gathered' = 'mounted', backstagePlugin?: BackstagePlugin, + appNode?: AppNode, ) { return { + appNode, path: path, caseSensitive: false, element: type, @@ -122,33 +122,33 @@ describe('discovery', () => { it('should collect routes', () => { const info = routeInfoFromExtensions([ createTestExtension({ - id: 'nothing', + name: 'nothing', path: 'nothing', }), createTestExtension({ - id: 'page1', + name: 'page1', path: 'foo', routeRef: ref1, }), createTestExtension({ - id: 'page2', + name: 'page2', parent: 'page1', path: 'bar/:id', routeRef: ref2, }), createTestExtension({ - id: 'page3', + name: 'page3', parent: 'page2', path: 'baz', routeRef: ref3, }), createTestExtension({ - id: 'page4', + name: 'page4', path: 'divsoup', routeRef: ref4, }), createTestExtension({ - id: 'page5', + name: 'page5', parent: 'page1', path: 'blop', routeRef: ref5, @@ -170,7 +170,14 @@ describe('discovery', () => { [ref5, ref1], ]); expect(info.routeObjects).toEqual([ - routeObj('nothing', []), + routeObj( + 'nothing', + [], + undefined, + undefined, + undefined, + expect.any(Object), + ), routeObj( 'foo', [ref1], @@ -178,45 +185,70 @@ describe('discovery', () => { routeObj( 'bar/:id', [ref2], - [routeObj('baz', [ref3], undefined, undefined, expect.any(Object))], + [ + routeObj( + 'baz', + [ref3], + undefined, + undefined, + expect.any(Object), + expect.any(Object), + ), + ], undefined, expect.any(Object), + expect.any(Object), + ), + routeObj( + 'blop', + [ref5], + undefined, + undefined, + expect.any(Object), + expect.any(Object), ), - routeObj('blop', [ref5], undefined, undefined, expect.any(Object)), ], undefined, expect.any(Object), + expect.any(Object), + ), + routeObj( + 'divsoup', + [ref4], + undefined, + undefined, + expect.any(Object), + expect.any(Object), ), - routeObj('divsoup', [ref4], undefined, undefined, expect.any(Object)), ]); }); it('should handle all react router Route patterns', () => { const info = routeInfoFromExtensions([ createTestExtension({ - id: 'page1', + name: 'page1', path: 'foo', routeRef: ref1, }), createTestExtension({ - id: 'page2', + name: 'page2', parent: 'page1', path: 'bar/:id', routeRef: ref2, }), createTestExtension({ - id: 'page3', + name: 'page3', path: 'baz', routeRef: ref3, }), createTestExtension({ - id: 'page4', + name: 'page4', parent: 'page3', path: 'divsoup', routeRef: ref4, }), createTestExtension({ - id: 'page5', + name: 'page5', parent: 'page3', path: 'blop', routeRef: ref5, @@ -242,29 +274,29 @@ describe('discovery', () => { it('should strip leading slashes in route paths', () => { const info = routeInfoFromExtensions([ createTestExtension({ - id: 'page1', + name: 'page1', path: '/foo', routeRef: ref1, }), createTestExtension({ - id: 'page2', + name: 'page2', parent: 'page1', path: '/bar/:id', routeRef: ref2, }), createTestExtension({ - id: 'page3', + name: 'page3', path: '/baz', routeRef: ref3, }), createTestExtension({ - id: 'page4', + name: 'page4', parent: 'page3', path: '/divsoup', routeRef: ref4, }), createTestExtension({ - id: 'page5', + name: 'page5', parent: 'page3', path: '/blop', routeRef: ref5, @@ -290,44 +322,44 @@ describe('discovery', () => { it('should use the route aggregator key to bind child routes to the same path', () => { const info = routeInfoFromExtensions([ createTestExtension({ - id: 'foo', + name: 'foo', path: 'foo', }), createTestExtension({ - id: 'page1', + name: 'page1', parent: 'foo', routeRef: ref1, }), createTestExtension({ - id: 'fooChild', + name: 'fooChild', parent: 'foo', }), createTestExtension({ - id: 'page2', + name: 'page2', parent: 'fooChild', routeRef: ref2, }), createTestExtension({ - id: 'fooEmpty', + name: 'fooEmpty', parent: 'foo', }), createTestExtension({ - id: 'page3', + name: 'page3', path: 'bar', routeRef: ref3, }), createTestExtension({ - id: 'page3Child', + name: 'page3Child', parent: 'page3', path: '', }), createTestExtension({ - id: 'page4', + name: 'page4', parent: 'page3Child', routeRef: ref4, }), createTestExtension({ - id: 'page5', + name: 'page5', parent: 'page4', routeRef: ref5, }), @@ -348,13 +380,30 @@ describe('discovery', () => { [ref5, ref3], ]); expect(info.routeObjects).toEqual([ - routeObj('foo', [ref1, ref2], [], 'mounted', expect.any(Object)), + routeObj( + 'foo', + [ref1, ref2], + [], + 'mounted', + expect.any(Object), + expect.any(Object), + ), routeObj( 'bar', [ref3], - [routeObj('', [ref4, ref5], [], 'mounted', expect.any(Object))], + [ + routeObj( + '', + [ref4, ref5], + [], + 'mounted', + expect.any(Object), + expect.any(Object), + ), + ], 'mounted', expect.any(Object), + expect.any(Object), ), ]); }); @@ -362,34 +411,34 @@ describe('discovery', () => { it('should use the route aggregator but stop when encountering explicit path', () => { const info = routeInfoFromExtensions([ createTestExtension({ - id: 'page1', + name: 'page1', path: 'foo', routeRef: ref1, }), createTestExtension({ - id: 'page1Child', + name: 'page1Child', parent: 'page1', path: 'bar', }), createTestExtension({ - id: 'page2', + name: 'page2', parent: 'page1Child', routeRef: ref2, }), createTestExtension({ - id: 'page3', + name: 'page3', parent: 'page2', path: 'baz', routeRef: ref3, }), createTestExtension({ - id: 'page4', + name: 'page4', parent: 'page3', path: '/blop', routeRef: ref4, }), createTestExtension({ - id: 'page5', + name: 'page5', parent: 'page2', routeRef: ref5, }), @@ -428,18 +477,22 @@ describe('discovery', () => { undefined, undefined, expect.any(Object), + expect.any(Object), ), ], undefined, expect.any(Object), + expect.any(Object), ), ], 'mounted', expect.any(Object), + expect.any(Object), ), ], undefined, expect.any(Object), + expect.any(Object), ), ]); }); @@ -447,34 +500,34 @@ describe('discovery', () => { it('should account for loose route paths', () => { const info = routeInfoFromExtensions([ createTestExtension({ - id: 'r', + name: 'r', path: 'r', }), createTestExtension({ - id: 'page1', + name: 'page1', parent: 'r', path: 'x', routeRef: ref1, }), createTestExtension({ - id: 'y', + name: 'y', path: 'y', parent: 'r', }), createTestExtension({ - id: 'page2', + name: 'page2', parent: 'y', path: '1', routeRef: ref2, }), createTestExtension({ - id: 'page3', + name: 'page3', parent: 'page2', path: 'a', routeRef: ref3, }), createTestExtension({ - id: 'page4', + name: 'page4', parent: 'page2', path: 'b', routeRef: ref4, @@ -498,7 +551,14 @@ describe('discovery', () => { 'r', [], [ - routeObj('x', [ref1], [], 'mounted', expect.any(Object)), + routeObj( + 'x', + [ref1], + [], + 'mounted', + expect.any(Object), + expect.any(Object), + ), routeObj( 'y', [], @@ -513,6 +573,7 @@ describe('discovery', () => { undefined, 'mounted', expect.any(Object), + expect.any(Object), ), routeObj( 'b', @@ -520,15 +581,22 @@ describe('discovery', () => { undefined, 'mounted', expect.any(Object), + expect.any(Object), ), ], 'mounted', expect.any(Object), + expect.any(Object), ), ], 'mounted', + undefined, + expect.any(Object), ), ], + undefined, + undefined, + expect.any(Object), ), ]); }); diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts index 2bfd09a3e8..17afbeb4c9 100644 --- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts +++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.ts @@ -84,6 +84,7 @@ export function extractRouteInfoFromAppNode(node: AppNode): { caseSensitive: false, children: [MATCH_ALL_ROUTE], plugins: new Set(), + appNode: current, }; parentChildren.push(currentObj); diff --git a/plugins/scaffolder/src/legacy/MultistepJsonForm/FieldOverrides/DescriptionField.tsx b/packages/frontend-app-api/src/routing/getBasePath.ts similarity index 59% rename from plugins/scaffolder/src/legacy/MultistepJsonForm/FieldOverrides/DescriptionField.tsx rename to packages/frontend-app-api/src/routing/getBasePath.ts index eded38beee..53a2230245 100644 --- a/plugins/scaffolder/src/legacy/MultistepJsonForm/FieldOverrides/DescriptionField.tsx +++ b/packages/frontend-app-api/src/routing/getBasePath.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Backstage Authors + * Copyright 2024 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. @@ -14,9 +14,14 @@ * limitations under the License. */ -import React from 'react'; -import { MarkdownContent } from '@backstage/core-components'; -import { FieldProps } from '@rjsf/utils'; +import { ConfigApi } from '@backstage/frontend-plugin-api'; -export const DescriptionField = ({ description }: FieldProps) => - description && ; +/** @internal */ +export function getBasePath(configApi: ConfigApi) { + let { pathname } = new URL( + configApi.getOptionalString('app.baseUrl') ?? '/', + 'http://sample.dev', // baseUrl can be specified as just a path + ); + pathname = pathname.replace(/\/*$/, ''); + return pathname; +} diff --git a/packages/frontend-app-api/src/routing/index.ts b/packages/frontend-app-api/src/routing/index.ts index 8c26a732d3..fedbad0261 100644 --- a/packages/frontend-app-api/src/routing/index.ts +++ b/packages/frontend-app-api/src/routing/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { type AppRouteBinder } from './resolveRouteBindings'; +export { type CreateAppRouteBinder } from './resolveRouteBindings'; diff --git a/packages/frontend-app-api/src/routing/resolveRouteBindings.ts b/packages/frontend-app-api/src/routing/resolveRouteBindings.ts index f5a2e5bd94..18ea9f9bd8 100644 --- a/packages/frontend-app-api/src/routing/resolveRouteBindings.ts +++ b/packages/frontend-app-api/src/routing/resolveRouteBindings.ts @@ -64,7 +64,7 @@ type TargetRouteMap< * * @public */ -export type AppRouteBinder = < +export type CreateAppRouteBinder = < TExternalRoutes extends { [name: string]: ExternalRouteRef }, >( externalRoutes: TExternalRoutes, @@ -76,14 +76,14 @@ export type AppRouteBinder = < /** @internal */ export function resolveRouteBindings( - bindRoutes: ((context: { bind: AppRouteBinder }) => void) | undefined, + bindRoutes: ((context: { bind: CreateAppRouteBinder }) => void) | undefined, config: Config, routesById: RouteRefsById, ): Map { const result = new Map(); if (bindRoutes) { - const bind: AppRouteBinder = ( + const bind: CreateAppRouteBinder = ( externalRoutes, targetRoutes: { [name: string]: RouteRef | SubRouteRef }, ) => { diff --git a/packages/frontend-app-api/src/routing/types.ts b/packages/frontend-app-api/src/routing/types.ts index 5f1ed9be84..05ea73dff8 100644 --- a/packages/frontend-app-api/src/routing/types.ts +++ b/packages/frontend-app-api/src/routing/types.ts @@ -15,6 +15,7 @@ */ import { + AppNode, ExternalRouteRef, RouteRef, SubRouteRef, @@ -35,4 +36,5 @@ export interface BackstageRouteObject { path: string; routeRefs: Set; plugins: Set; + appNode?: AppNode; } diff --git a/packages/frontend-app-api/src/tree/createAppTree.test.ts b/packages/frontend-app-api/src/tree/createAppTree.test.ts index 667e3a6fc3..9ee58fe2d2 100644 --- a/packages/frontend-app-api/src/tree/createAppTree.test.ts +++ b/packages/frontend-app-api/src/tree/createAppTree.test.ts @@ -24,18 +24,18 @@ import { createAppTree } from './createAppTree'; const extBase = { id: 'test', - attachTo: { id: 'core', input: 'root' }, + attachTo: { id: 'app', input: 'root' }, output: {}, factory: () => ({}), }; describe('createAppTree', () => { - it('throws an error when a core extension is parametrized', () => { + it('throws an error when a app extension is parametrized', () => { const config = new MockConfigApi({ app: { extensions: [ { - core: {}, + app: {}, }, ], }, @@ -48,18 +48,17 @@ describe('createAppTree', () => { ]; expect(() => createAppTree({ features, config, builtinExtensions: [] }), - ).toThrow("Configuration of the 'core' extension is forbidden"); + ).toThrow("Configuration of the 'app' extension is forbidden"); }); - it('throws an error when a core extension is overridden', () => { + it('throws an error when a app extension is overridden', () => { const config = new MockConfigApi({}); const features = [ - createPlugin({ - id: 'plugin', + createExtensionOverrides({ extensions: [ createExtension({ - id: 'core', - attachTo: { id: 'core.routes', input: 'route' }, + name: 'app', + attachTo: { id: 'app/routes', input: 'route' }, inputs: {}, output: {}, factory: () => ({}), @@ -70,33 +69,7 @@ describe('createAppTree', () => { expect(() => createAppTree({ features, config, builtinExtensions: [] }), ).toThrow( - "It is forbidden to override the following extension(s): 'core', which is done by the following plugin(s): 'plugin'", - ); - }); - - it('throws an error when duplicated extensions are detected', () => { - const config = new MockConfigApi({}); - - const ExtensionA = createExtension({ ...extBase, id: 'A' }); - - const ExtensionB = createExtension({ ...extBase, id: 'B' }); - - const PluginA = createPlugin({ - id: 'A', - extensions: [ExtensionA, ExtensionA], - }); - - const PluginB = createPlugin({ - id: 'B', - extensions: [ExtensionA, ExtensionB, ExtensionB], - }); - - const features = [PluginA, PluginB]; - - expect(() => - createAppTree({ features, config, builtinExtensions: [] }), - ).toThrow( - "The following extensions are duplicated: The extension 'A' was provided 2 time(s) by the plugin 'A' and 1 time(s) by the plugin 'B', The extension 'B' was provided 2 time(s) by the plugin 'B'", + "It is forbidden to override the following extension(s): 'app', which is done by one or more extension overrides", ); }); @@ -106,13 +79,13 @@ describe('createAppTree', () => { features: [ createExtensionOverrides({ extensions: [ - createExtension({ ...extBase, id: 'a' }), - createExtension({ ...extBase, id: 'a' }), - createExtension({ ...extBase, id: 'b' }), + createExtension({ ...extBase, name: 'a' }), + createExtension({ ...extBase, name: 'a' }), + createExtension({ ...extBase, name: 'b' }), ], }), createExtensionOverrides({ - extensions: [createExtension({ ...extBase, id: 'b' })], + extensions: [createExtension({ ...extBase, name: 'b' })], }), ], config: new MockConfigApi({}), diff --git a/packages/frontend-app-api/src/tree/createAppTree.ts b/packages/frontend-app-api/src/tree/createAppTree.ts index 950669febd..12c7f7fc3a 100644 --- a/packages/frontend-app-api/src/tree/createAppTree.ts +++ b/packages/frontend-app-api/src/tree/createAppTree.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - BackstagePlugin, - Extension, - ExtensionOverrides, -} from '@backstage/frontend-plugin-api'; +import { Extension, FrontendFeature } from '@backstage/frontend-plugin-api'; import { readAppExtensionsConfig } from './readAppExtensionsConfig'; import { resolveAppTree } from './resolveAppTree'; import { resolveAppNodeSpecs } from './resolveAppNodeSpecs'; @@ -28,7 +24,7 @@ import { instantiateAppNodeTree } from './instantiateAppNodeTree'; /** @internal */ export interface CreateAppTreeOptions { - features: (BackstagePlugin | ExtensionOverrides)[]; + features: FrontendFeature[]; builtinExtensions: Extension[]; config: Config; } @@ -36,12 +32,12 @@ export interface CreateAppTreeOptions { /** @internal */ export function createAppTree(options: CreateAppTreeOptions): AppTree { const tree = resolveAppTree( - 'core', + 'app', resolveAppNodeSpecs({ features: options.features, builtinExtensions: options.builtinExtensions, parameters: readAppExtensionsConfig(options.config), - forbidden: new Set(['core']), + forbidden: new Set(['app']), }), ); instantiateAppNodeTree(tree.root); diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts index 68a95fa1fe..3330b9add8 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.test.ts @@ -15,6 +15,7 @@ */ import { + AppNode, Extension, createExtension, createExtensionDataRef, @@ -25,53 +26,72 @@ import { createAppNodeInstance, instantiateAppNodeTree, } from './instantiateAppNodeTree'; -import { AppNodeInstance, AppNodeSpec } from '@backstage/frontend-plugin-api'; +import { AppNodeSpec } from '@backstage/frontend-plugin-api'; import { resolveAppTree } from './resolveAppTree'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; +import { withLogCollector } from '@backstage/test-utils'; const testDataRef = createExtensionDataRef('test'); const otherDataRef = createExtensionDataRef('other'); const inputMirrorDataRef = createExtensionDataRef('mirror'); -const simpleExtension = createExtension({ - id: 'core.test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - test: testDataRef, - other: otherDataRef.optional(), - }, - configSchema: createSchemaFromZod(z => - z.object({ - output: z.string().default('test'), - other: z.number().optional(), - }), - ), - factory({ config }) { - return { test: config.output, other: config.other }; - }, -}); +const simpleExtension = resolveExtensionDefinition( + createExtension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + test: testDataRef, + other: otherDataRef.optional(), + }, + configSchema: createSchemaFromZod(z => + z.object({ + output: z.string().default('test'), + other: z.number().optional(), + }), + ), + factory({ config }) { + return { test: config.output, other: config.other }; + }, + }), +); function makeSpec( extension: Extension, - config?: TConfig, + spec?: Partial, ): AppNodeSpec { return { id: extension.id, attachTo: extension.attachTo, disabled: extension.disabled, extension, - config, source: undefined, + ...spec, + }; +} + +function makeNode( + extension: Extension, + spec?: Partial, +): AppNode { + return { + spec: makeSpec(extension, spec), + edges: { + attachments: new Map(), + }, }; } function makeInstanceWithId( extension: Extension, config?: TConfig, -): { id: string; instance: AppNodeInstance } { +): AppNode { + const node = makeNode(extension, { config }); return { - id: extension.id, + ...node, instance: createAppNodeInstance({ - spec: makeSpec(extension, config), + node, attachments: new Map(), }), }; @@ -80,7 +100,7 @@ function makeInstanceWithId( describe('instantiateAppNodeTree', () => { it('should instantiate a single node', () => { const tree = resolveAppTree('root-node', [ - { ...makeSpec(simpleExtension), id: 'root-node' }, + makeSpec(simpleExtension, { id: 'root-node' }), ]); expect(tree.root.instance).not.toBeDefined(); instantiateAppNodeTree(tree.root); @@ -94,7 +114,7 @@ describe('instantiateAppNodeTree', () => { it('should not instantiate disabled nodes', () => { const tree = resolveAppTree('root-node', [ - { ...makeSpec(simpleExtension), id: 'root-node', disabled: true }, + makeSpec(simpleExtension, { id: 'root-node', disabled: true }), ]); expect(tree.root.instance).not.toBeDefined(); instantiateAppNodeTree(tree.root); @@ -103,10 +123,10 @@ describe('instantiateAppNodeTree', () => { it('should instantiate a node with attachments', () => { const tree = resolveAppTree('root-node', [ - { - ...makeSpec( + makeSpec( + resolveExtensionDefinition( createExtension({ - id: 'root-node', + namespace: 'root-node', attachTo: { id: 'ignored', input: 'ignored' }, inputs: { test: createExtensionInput({ test: testDataRef }), @@ -119,12 +139,11 @@ describe('instantiateAppNodeTree', () => { }, }), ), - }, - { - ...makeSpec(simpleExtension), + ), + makeSpec(simpleExtension, { id: 'child-node', attachTo: { id: 'root-node', input: 'test' }, - }, + }), ]); const childNode = tree.nodes.get('child-node'); @@ -135,8 +154,10 @@ describe('instantiateAppNodeTree', () => { instantiateAppNodeTree(tree.root); expect(tree.root.instance).toBeDefined(); expect(childNode?.instance).toBeDefined(); - expect(tree.root.instance?.getData(inputMirrorDataRef)).toEqual({ - test: [{ test: 'test' }], + expect(tree.root.instance?.getData(inputMirrorDataRef)).toMatchObject({ + test: [ + { node: { spec: { id: 'child-node' } }, output: { test: 'test' } }, + ], }); // Multiple calls should have no effect @@ -149,19 +170,21 @@ describe('instantiateAppNodeTree', () => { const tree = resolveAppTree('root-node', [ { ...makeSpec( - createExtension({ - id: 'root-node', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - test: createExtensionInput({ test: testDataRef }), - }, - output: { - inputMirror: inputMirrorDataRef, - }, - factory({ inputs }) { - return { inputMirror: inputs }; - }, - }), + resolveExtensionDefinition( + createExtension({ + namespace: 'root-node', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + test: createExtensionInput({ test: testDataRef }), + }, + output: { + inputMirror: inputMirrorDataRef, + }, + factory({ inputs }) { + return { inputMirror: inputs }; + }, + }), + ), ), }, { @@ -191,7 +214,7 @@ describe('createAppNodeInstance', () => { it('should create a simple extension instance', () => { const attachments = new Map(); const instance = createAppNodeInstance({ - spec: makeSpec(simpleExtension), + node: makeNode(simpleExtension), attachments, }); @@ -231,164 +254,188 @@ describe('createAppNodeInstance', () => { ]); const instance = createAppNodeInstance({ attachments, - spec: makeSpec( - createExtension({ - id: 'core.test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - optionalSingletonPresent: createExtensionInput( - { + node: makeNode( + resolveExtensionDefinition( + createExtension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + optionalSingletonPresent: createExtensionInput( + { + test: testDataRef, + other: otherDataRef.optional(), + }, + { singleton: true, optional: true }, + ), + optionalSingletonMissing: createExtensionInput( + { + test: testDataRef, + other: otherDataRef.optional(), + }, + { singleton: true, optional: true }, + ), + singleton: createExtensionInput( + { + test: testDataRef, + other: otherDataRef.optional(), + }, + { singleton: true }, + ), + many: createExtensionInput({ test: testDataRef, other: otherDataRef.optional(), - }, - { singleton: true, optional: true }, - ), - optionalSingletonMissing: createExtensionInput( - { - test: testDataRef, - other: otherDataRef.optional(), - }, - { singleton: true, optional: true }, - ), - singleton: createExtensionInput( - { - test: testDataRef, - other: otherDataRef.optional(), - }, - { singleton: true }, - ), - many: createExtensionInput({ - test: testDataRef, - other: otherDataRef.optional(), - }), - }, - output: { - inputMirror: inputMirrorDataRef, - }, - factory({ inputs }) { - return { inputMirror: inputs }; - }, - }), + }), + }, + output: { + inputMirror: inputMirrorDataRef, + }, + factory({ inputs }) { + return { inputMirror: inputs }; + }, + }), + ), ), }); expect(Array.from(instance.getDataRefs())).toEqual([inputMirrorDataRef]); - expect(instance.getData(inputMirrorDataRef)).toEqual({ - optionalSingletonPresent: { test: 'optionalSingletonPresent' }, - singleton: { test: 'singleton', other: 2 }, - many: [{ test: 'many1' }, { test: 'many2', other: 3 }], + expect(instance.getData(inputMirrorDataRef)).toMatchObject({ + optionalSingletonPresent: { + node: { spec: { id: 'app/test' } }, + output: { test: 'optionalSingletonPresent' }, + }, + singleton: { + node: { spec: { id: 'app/test' } }, + output: { test: 'singleton', other: 2 }, + }, + many: [ + { node: { spec: { id: 'app/test' } }, output: { test: 'many1' } }, + { + node: { spec: { id: 'app/test' } }, + output: { test: 'many2', other: 3 }, + }, + ], }); }); it('should refuse to create an extension with invalid config', () => { expect(() => createAppNodeInstance({ - spec: { - ...makeSpec(simpleExtension), - config: { other: 'not-a-number' }, - }, + node: makeNode(simpleExtension, { config: { other: 'not-a-number' } }), attachments: new Map(), }), ).toThrow( - "Invalid configuration for extension 'core.test'; caused by Error: Expected number, received string at 'other'", + "Invalid configuration for extension 'app/test'; caused by Error: Expected number, received string at 'other'", ); }); it('should forward extension factory errors', () => { expect(() => createAppNodeInstance({ - spec: makeSpec( - createExtension({ - id: 'core.test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: {}, - factory() { - const error = new Error('NOPE'); - error.name = 'NopeError'; - throw error; - }, - }), + node: makeNode( + resolveExtensionDefinition( + createExtension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: {}, + factory() { + const error = new Error('NOPE'); + error.name = 'NopeError'; + throw error; + }, + }), + ), ), attachments: new Map(), }), ).toThrow( - "Failed to instantiate extension 'core.test'; caused by NopeError: NOPE", + "Failed to instantiate extension 'app/test'; caused by NopeError: NOPE", ); }); it('should refuse to create an instance with duplicate output', () => { expect(() => createAppNodeInstance({ - spec: makeSpec( - createExtension({ - id: 'core.test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - test1: testDataRef, - test2: testDataRef, - }, - factory({}) { - return { test1: 'test', test2: 'test2' }; - }, - }), + node: makeNode( + resolveExtensionDefinition( + createExtension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + test1: testDataRef, + test2: testDataRef, + }, + factory({}) { + return { test1: 'test', test2: 'test2' }; + }, + }), + ), ), attachments: new Map(), }), ).toThrow( - "Failed to instantiate extension 'core.test', duplicate extension data 'test' received via output 'test2'", + "Failed to instantiate extension 'app/test', duplicate extension data 'test' received via output 'test2'", ); }); it('should refuse to create an instance with disconnected output data', () => { expect(() => createAppNodeInstance({ - spec: makeSpec( - createExtension({ - id: 'core.test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { - test: testDataRef, - }, - factory({}) { - return { nonexistent: 'test' } as any; - }, - }), + node: makeNode( + resolveExtensionDefinition( + createExtension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + test: testDataRef, + }, + factory({}) { + return { nonexistent: 'test' } as any; + }, + }), + ), ), attachments: new Map(), }), ).toThrow( - "Failed to instantiate extension 'core.test', unknown output provided via 'nonexistent'", + "Failed to instantiate extension 'app/test', unknown output provided via 'nonexistent'", ); }); it('should refuse to create an instance with missing required input', () => { expect(() => createAppNodeInstance({ - spec: makeSpec( - createExtension({ - id: 'core.test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - test: testDataRef, - }, - { singleton: true }, - ), - }, - output: {}, - factory: () => ({}), - }), + node: makeNode( + resolveExtensionDefinition( + createExtension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createExtensionInput( + { + test: testDataRef, + }, + { singleton: true }, + ), + }, + output: {}, + factory: () => ({}), + }), + ), ), attachments: new Map(), }), ).toThrow( - "Failed to instantiate extension 'core.test', input 'singleton' is required but was not received", + "Failed to instantiate extension 'app/test', input 'singleton' is required but was not received", ); }); - it('should refuse to create an instance with undeclared inputs', () => { - expect(() => + it('should warn when creating an instance with undeclared inputs', () => { + const { warn } = withLogCollector(['warn'], () => createAppNodeInstance({ attachments: new Map([ [ @@ -408,27 +455,32 @@ describe('createAppNodeInstance', () => { ], ], ]), - spec: makeSpec( - createExtension({ - id: 'core.test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - declared: createExtensionInput({ - test: testDataRef, - }), - }, - output: {}, - factory: () => ({}), - }), + node: makeNode( + resolveExtensionDefinition( + createExtension({ + namespace: 'app', + name: 'parent', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + declared: createExtensionInput({ + test: testDataRef, + }), + }, + output: {}, + factory: () => ({}), + }), + ), ), }), - ).toThrow( - "Failed to instantiate extension 'core.test', received undeclared input 'undeclared' from extension 'core.test'", ); + + expect(warn).toEqual([ + "The extension 'app/test' is attached to the input 'undeclared' of the extension 'app/parent', but it has no such input (candidates are 'declared')", + ]); }); it('should refuse to create an instance with multiple undeclared inputs', () => { - expect(() => + const { warn } = withLogCollector(['warn'], () => createAppNodeInstance({ attachments: new Map([ [ @@ -443,18 +495,24 @@ describe('createAppNodeInstance', () => { ], ], ]), - spec: makeSpec( - createExtension({ - id: 'core.test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: {}, - factory: () => ({}), - }), + node: makeNode( + resolveExtensionDefinition( + createExtension({ + namespace: 'app', + name: 'parent', + attachTo: { id: 'ignored', input: 'ignored' }, + output: {}, + factory: () => ({}), + }), + ), ), }), - ).toThrow( - "Failed to instantiate extension 'core.test', received undeclared inputs 'undeclared1' from extension 'core.test' and 'undeclared2' from extensions 'core.test', 'core.test'", ); + + expect(warn).toEqual([ + "The extension 'app/test' is attached to the input 'undeclared1' of the extension 'app/parent', but it has no inputs", + "The extensions 'app/test', 'app/test' are attached to the input 'undeclared2' of the extension 'app/parent', but it has no inputs", + ]); }); it('should refuse to create an instance with multiple inputs for required singleton', () => { @@ -469,25 +527,28 @@ describe('createAppNodeInstance', () => { ], ], ]), - spec: makeSpec( - createExtension({ - id: 'core.test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - test: testDataRef, - }, - { singleton: true }, - ), - }, - output: {}, - factory: () => ({}), - }), + node: makeNode( + resolveExtensionDefinition( + createExtension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createExtensionInput( + { + test: testDataRef, + }, + { singleton: true }, + ), + }, + output: {}, + factory: () => ({}), + }), + ), ), }), ).toThrow( - "Failed to instantiate extension 'core.test', expected exactly one 'singleton' input but received multiple: 'core.test', 'core.test'", + "Failed to instantiate extension 'app/test', expected exactly one 'singleton' input but received multiple: 'app/test', 'app/test'", ); }); @@ -503,25 +564,28 @@ describe('createAppNodeInstance', () => { ], ], ]), - spec: makeSpec( - createExtension({ - id: 'core.test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - test: testDataRef, - }, - { singleton: true, optional: true }, - ), - }, - output: {}, - factory: () => ({}), - }), + node: makeNode( + resolveExtensionDefinition( + createExtension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createExtensionInput( + { + test: testDataRef, + }, + { singleton: true, optional: true }, + ), + }, + output: {}, + factory: () => ({}), + }), + ), ), }), ).toThrow( - "Failed to instantiate extension 'core.test', expected at most one 'singleton' input but received multiple: 'core.test', 'core.test'", + "Failed to instantiate extension 'app/test', expected at most one 'singleton' input but received multiple: 'app/test', 'app/test'", ); }); @@ -531,25 +595,28 @@ describe('createAppNodeInstance', () => { attachments: new Map([ ['singleton', [makeInstanceWithId(simpleExtension, undefined)]], ]), - spec: makeSpec( - createExtension({ - id: 'core.test', - attachTo: { id: 'ignored', input: 'ignored' }, - inputs: { - singleton: createExtensionInput( - { - other: otherDataRef, - }, - { singleton: true }, - ), - }, - output: {}, - factory: () => ({}), - }), + node: makeNode( + resolveExtensionDefinition( + createExtension({ + namespace: 'app', + name: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + inputs: { + singleton: createExtensionInput( + { + other: otherDataRef, + }, + { singleton: true }, + ), + }, + output: {}, + factory: () => ({}), + }), + ), ), }), - ).toThrow( - "Failed to instantiate extension 'core.test', input 'singleton' did not receive required extension data 'other' from extension 'core.test'", + ).toThrowErrorMatchingInlineSnapshot( + `"Failed to instantiate extension 'app/test', extension 'app/test' could not be attached because its output data ('test', 'other') does not match what the input 'singleton' requires ('other')"`, ); }); }); diff --git a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts index 003abbb97e..63eca749b7 100644 --- a/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts +++ b/packages/frontend-app-api/src/tree/instantiateAppNodeTree.ts @@ -18,13 +18,12 @@ import { AnyExtensionDataMap, AnyExtensionInputMap, ExtensionDataRef, + ResolvedExtensionInputs, } from '@backstage/frontend-plugin-api'; import mapValues from 'lodash/mapValues'; -import { - AppNode, - AppNodeInstance, - AppNodeSpec, -} from '@backstage/frontend-plugin-api'; +import { AppNode, AppNodeInstance } from '@backstage/frontend-plugin-api'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; type Mutable = { -readonly [P in keyof T]: T[P]; @@ -32,14 +31,23 @@ type Mutable = { function resolveInputData( dataMap: AnyExtensionDataMap, - attachment: { id: string; instance: AppNodeInstance }, + attachment: AppNode, inputName: string, ) { return mapValues(dataMap, ref => { - const value = attachment.instance.getData(ref); + const value = attachment.instance?.getData(ref); if (value === undefined && !ref.config.optional) { + const expected = Object.values(dataMap) + .filter(r => !r.config.optional) + .map(r => `'${r.id}'`) + .join(', '); + + const provided = [...(attachment.instance?.getDataRefs() ?? [])] + .map(r => `'${r.id}'`) + .join(', '); + throw new Error( - `input '${inputName}' did not receive required extension data '${ref.id}' from extension '${attachment.id}'`, + `extension '${attachment.spec.id}' could not be attached because its output data (${provided}) does not match what the input '${inputName}' requires (${expected})`, ); } return value; @@ -47,26 +55,32 @@ function resolveInputData( } function resolveInputs( + id: string, inputMap: AnyExtensionInputMap, - attachments: ReadonlyMap, -) { + attachments: ReadonlyMap, +): ResolvedExtensionInputs { const undeclaredAttachments = Array.from(attachments.entries()).filter( ([inputName]) => inputMap[inputName] === undefined, ); - // TODO: Make this a warning rather than an error - if (undeclaredAttachments.length > 0) { - throw new Error( - `received undeclared input${ - undeclaredAttachments.length > 1 ? 's' : '' - } ${undeclaredAttachments - .map( - ([k, exts]) => - `'${k}' from extension${exts.length > 1 ? 's' : ''} '${exts - .map(e => e.id) - .join("', '")}'`, - ) - .join(' and ')}`, - ); + + if (process.env.NODE_ENV !== 'production') { + const inputNames = Object.keys(inputMap); + + for (const [name, nodes] of undeclaredAttachments) { + const pl = nodes.length > 1; + // eslint-disable-next-line no-console + console.warn( + [ + `The extension${pl ? 's' : ''} '${nodes + .map(n => n.spec.id) + .join("', '")}' ${pl ? 'are' : 'is'}`, + `attached to the input '${name}' of the extension '${id}', but it`, + inputNames.length === 0 + ? 'has no inputs' + : `has no such input (candidates are '${inputNames.join("', '")}')`, + ].join(' '), + ); + } } return mapValues(inputMap, (input, inputName) => { @@ -74,7 +88,7 @@ function resolveInputs( if (input.config.singleton) { if (attachedNodes.length > 1) { - const attachedNodeIds = attachedNodes.map(e => e.id); + const attachedNodeIds = attachedNodes.map(e => e.spec.id); throw Error( `expected ${ input.config.optional ? 'at most' : 'exactly' @@ -88,22 +102,30 @@ function resolveInputs( } throw Error(`input '${inputName}' is required but was not received`); } - return resolveInputData(input.extensionData, attachedNodes[0], inputName); + return { + node: attachedNodes[0], + output: resolveInputData( + input.extensionData, + attachedNodes[0], + inputName, + ), + }; } - return attachedNodes.map(attachment => - resolveInputData(input.extensionData, attachment, inputName), - ); - }); + return attachedNodes.map(attachment => ({ + node: attachment, + output: resolveInputData(input.extensionData, attachment, inputName), + })); + }) as ResolvedExtensionInputs; } /** @internal */ export function createAppNodeInstance(options: { - spec: AppNodeSpec; - attachments: ReadonlyMap; + node: AppNode; + attachments: ReadonlyMap; }): AppNodeInstance { - const { spec, attachments } = options; - const { id, extension, config, source } = spec; + const { node, attachments } = options; + const { id, extension, config } = node.spec; const extensionData = new Map(); const extensionDataRefs = new Set>(); @@ -117,14 +139,16 @@ export function createAppNodeInstance(options: { } try { - const namedOutputs = extension.factory({ - source, + const internalExtension = toInternalExtension(extension); + + const namedOutputs = internalExtension.factory({ + node, config: parsedConfig, - inputs: resolveInputs(extension.inputs, attachments), + inputs: resolveInputs(id, internalExtension.inputs, attachments), }); for (const [name, output] of Object.entries(namedOutputs)) { - const ref = extension.output[name]; + const ref = internalExtension.output[name]; if (!ref) { throw new Error(`unknown output provided via '${name}'`); } @@ -139,7 +163,7 @@ export function createAppNodeInstance(options: { } catch (e) { throw new Error( `Failed to instantiate extension '${id}'${ - e.name === 'Error' ? `, ${e.message}` : `; caused by ${e}` + e.name === 'Error' ? `, ${e.message}` : `; caused by ${e.stack}` }`, ); } @@ -167,10 +191,7 @@ export function instantiateAppNodeTree(rootNode: AppNode): void { return undefined; } - const instantiatedAttachments = new Map< - string, - { id: string; instance: AppNodeInstance }[] - >(); + const instantiatedAttachments = new Map(); for (const [input, children] of node.edges.attachments) { const instantiatedChildren = children.flatMap(child => { @@ -178,7 +199,7 @@ export function instantiateAppNodeTree(rootNode: AppNode): void { if (!childInstance) { return []; } - return [{ id: child.spec.id, instance: childInstance }]; + return [child]; }); if (instantiatedChildren.length > 0) { instantiatedAttachments.set(input, instantiatedChildren); @@ -186,7 +207,7 @@ export function instantiateAppNodeTree(rootNode: AppNode): void { } (node as Mutable).instance = createAppNodeInstance({ - spec: node.spec, + node, attachments: instantiatedAttachments, }); diff --git a/packages/frontend-app-api/src/tree/readAppExtensionsConfig.test.ts b/packages/frontend-app-api/src/tree/readAppExtensionsConfig.test.ts index 91f6992f29..440407549e 100644 --- a/packages/frontend-app-api/src/tree/readAppExtensionsConfig.test.ts +++ b/packages/frontend-app-api/src/tree/readAppExtensionsConfig.test.ts @@ -25,18 +25,18 @@ describe('readAppExtensionsConfig', () => { it('should disable extension with shorthand notation', () => { expect( readAppExtensionsConfig( - new ConfigReader({ app: { extensions: [{ 'core.router': false }] } }), + new ConfigReader({ app: { extensions: [{ 'app/root': false }] } }), ), ).toEqual([ { - id: 'core.router', + id: 'app/root', disabled: true, }, ]); expect( readAppExtensionsConfig( new ConfigReader({ - app: { extensions: [{ 'core.router': { disabled: true } }] }, + app: { extensions: [{ 'app/root': { disabled: true } }] }, }), ), ).toEqual([ @@ -44,7 +44,7 @@ describe('readAppExtensionsConfig', () => { at: undefined, config: undefined, disabled: true, - id: 'core.router', + id: 'app/root', }, ]); }); @@ -52,33 +52,33 @@ describe('readAppExtensionsConfig', () => { it('should enable extension with shorthand notation', () => { expect( readAppExtensionsConfig( - new ConfigReader({ app: { extensions: ['core.router'] } }), + new ConfigReader({ app: { extensions: ['app/root'] } }), ), ).toEqual([ { - id: 'core.router', + id: 'app/root', disabled: false, }, ]); expect( readAppExtensionsConfig( - new ConfigReader({ app: { extensions: [{ 'core.router': true }] } }), + new ConfigReader({ app: { extensions: [{ 'app/root': true }] } }), ), ).toEqual([ { - id: 'core.router', + id: 'app/root', disabled: false, }, ]); expect( readAppExtensionsConfig( new ConfigReader({ - app: { extensions: [{ 'core.router': { disabled: false } }] }, + app: { extensions: [{ 'app/root': { disabled: false } }] }, }), ), ).toEqual([ { - id: 'core.router', + id: 'app/root', disabled: false, }, ]); @@ -89,12 +89,12 @@ describe('readAppExtensionsConfig', () => { readAppExtensionsConfig( new ConfigReader({ app: { - extensions: [{ 'core.router': 'some-string' }], + extensions: [{ 'app/root': 'some-string' }], }, }), ), ).toThrow( - 'Invalid extension configuration at app.extensions[0][core.router], value must be a boolean or object', + 'Invalid extension configuration at app.extensions[0][app/root], value must be a boolean or object', ); }); @@ -105,7 +105,7 @@ describe('readAppExtensionsConfig', () => { app: { extensions: [ { - 'core.router/routes': { + '': { extension: 'example-package#MyPage', config: { foo: 'bar' }, }, @@ -115,7 +115,7 @@ describe('readAppExtensionsConfig', () => { }), ), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid extension configuration at app.extensions[0], extension ID must not contain slashes; got 'core.router/routes', did you mean 'core.router'?"`, + `"Invalid extension configuration at app.extensions[0], extension ID must not be empty or contain whitespace"`, ); }); }); @@ -156,8 +156,8 @@ describe('expandShorthandExtensionParameters', () => { }); it('supports string key', () => { - expect(run('core.router')).toEqual({ - id: 'core.router', + expect(run('app/root')).toEqual({ + id: 'app/root', disabled: false, }); expect(() => run('')).toThrowErrorMatchingInlineSnapshot( @@ -166,103 +166,100 @@ describe('expandShorthandExtensionParameters', () => { expect(() => run(' a')).toThrowErrorMatchingInlineSnapshot( `"Invalid extension configuration at app.extensions[1], extension ID must not be empty or contain whitespace"`, ); - expect(() => run('core.router/routes')).toThrowErrorMatchingInlineSnapshot( - `"Invalid extension configuration at app.extensions[1], extension ID must not contain slashes; got 'core.router/routes', did you mean 'core.router'?"`, - ); }); it('supports null value', () => { // this is the result of typing: - // - core.router: + // - app/root: // The missing value is interpreted as null by the yaml parser so we deal with that - expect(run({ 'core.router': null })).toEqual({ - id: 'core.router', + expect(run({ 'app/root': null })).toEqual({ + id: 'app/root', disabled: false, }); }); it('supports boolean value', () => { - expect(run({ 'core.router': true })).toEqual({ - id: 'core.router', + expect(run({ 'app/root': true })).toEqual({ + id: 'app/root', disabled: false, }); - expect(run({ 'core.router': false })).toEqual({ - id: 'core.router', + expect(run({ 'app/root': false })).toEqual({ + id: 'app/root', disabled: true, }); }); it('should not support string values', () => { expect(() => - run({ 'core.router': 'example-package#MyRouter' }), + run({ 'app/root': 'example-package#MyRouter' }), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid extension configuration at app.extensions[1][core.router], value must be a boolean or object"`, + `"Invalid extension configuration at app.extensions[1][app/root], value must be a boolean or object"`, ); }); it('supports object id only in the key', () => { expect(() => - run({ 'core.router': { id: 'some.id' } }), + run({ 'app/root': { id: 'some.id' } }), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid extension configuration at app.extensions[1][core.router].id, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`, + `"Invalid extension configuration at app.extensions[1][app/root].id, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`, ); }); it('supports object attachTo', () => { expect( run({ - 'core.router': { attachTo: { id: 'other.root', input: 'inputs' } }, + 'app/root': { attachTo: { id: 'other.root', input: 'inputs' } }, }), ).toEqual({ - id: 'core.router', + id: 'app/root', attachTo: { id: 'other.root', input: 'inputs' }, }); expect(() => run({ - 'core.router': { + 'app/root': { id: 'other-id', }, }), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid extension configuration at app.extensions[1][core.router].id, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`, + `"Invalid extension configuration at app.extensions[1][app/root].id, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`, ); }); it('supports object disabled', () => { - expect(run({ 'core.router': { disabled: true } })).toEqual({ - id: 'core.router', + expect(run({ 'app/root': { disabled: true } })).toEqual({ + id: 'app/root', disabled: true, }); - expect(run({ 'core.router': { disabled: false } })).toEqual({ - id: 'core.router', + expect(run({ 'app/root': { disabled: false } })).toEqual({ + id: 'app/root', disabled: false, }); expect(() => - run({ 'core.router': { disabled: 0 } }), + run({ 'app/root': { disabled: 0 } }), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid extension configuration at app.extensions[1][core.router].disabled, must be a boolean"`, + `"Invalid extension configuration at app.extensions[1][app/root].disabled, must be a boolean"`, ); }); it('supports object config', () => { - expect( - run({ 'core.router': { config: { disableRedirects: true } } }), - ).toEqual({ - id: 'core.router', - config: { disableRedirects: true }, - }); + expect(run({ 'app/root': { config: { disableRedirects: true } } })).toEqual( + { + id: 'app/root', + config: { disableRedirects: true }, + }, + ); expect(() => - run({ 'core.router': { config: 0 } }), + run({ 'app/root': { config: 0 } }), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid extension configuration at app.extensions[1][core.router].config, must be an object"`, + `"Invalid extension configuration at app.extensions[1][app/root].config, must be an object"`, ); }); it('rejects unknown object keys', () => { expect(() => - run({ 'core.router': { foo: { settings: true } } }), + run({ 'app/root': { foo: { settings: true } } }), ).toThrowErrorMatchingInlineSnapshot( - `"Invalid extension configuration at app.extensions[1][core.router].foo, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`, + `"Invalid extension configuration at app.extensions[1][app/root].foo, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`, ); }); }); diff --git a/packages/frontend-app-api/src/tree/readAppExtensionsConfig.ts b/packages/frontend-app-api/src/tree/readAppExtensionsConfig.ts index 2b5b51ce92..cca7064add 100644 --- a/packages/frontend-app-api/src/tree/readAppExtensionsConfig.ts +++ b/packages/frontend-app-api/src/tree/readAppExtensionsConfig.ts @@ -70,15 +70,6 @@ export function expandShorthandExtensionParameters( errorMsg('extension ID must not be empty or contain whitespace'), ); } - - if (id.includes('/')) { - let message = `extension ID must not contain slashes; got '${id}'`; - const good = id.split('/')[0]; - if (good) { - message += `, did you mean '${good}'?`; - } - throw new Error(errorMsg(message)); - } } // Example YAML: diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts index 39eceaaf28..82f274b960 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts @@ -18,6 +18,7 @@ import { createExtensionOverrides, createPlugin, Extension, + ExtensionDefinition, } from '@backstage/frontend-plugin-api'; import { resolveAppNodeSpecs } from './resolveAppNodeSpecs'; @@ -27,12 +28,29 @@ function makeExt( attachId: string = 'root', ) { return { + $$type: '@backstage/Extension', + version: 'v1', id, attachTo: { id: attachId, input: 'default' }, disabled: status === 'disabled', + toString: expect.any(Function), } as Extension; } +function makeExtDef( + name: string, + status: 'disabled' | 'enabled' = 'enabled', + attachId: string = 'root', +) { + return { + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + name, + attachTo: { id: attachId, input: 'default' }, + disabled: status === 'disabled', + } as ExtensionDefinition; +} + describe('resolveAppNodeSpecs', () => { it('should not filter out disabled extension instances', () => { const a = makeExt('a', 'disabled'); @@ -78,9 +96,8 @@ describe('resolveAppNodeSpecs', () => { }); it('should override attachment points', () => { - const a = makeExt('a'); const b = makeExt('b'); - const pluginA = createPlugin({ id: 'test', extensions: [a] }); + const pluginA = createPlugin({ id: 'test', extensions: [makeExtDef('a')] }); expect( resolveAppNodeSpecs({ features: [pluginA], @@ -94,8 +111,8 @@ describe('resolveAppNodeSpecs', () => { }), ).toEqual([ { - id: 'a', - extension: a, + id: 'test/a', + extension: makeExt('test/a'), attachTo: { id: 'root', input: 'default' }, source: pluginA, disabled: false, @@ -110,31 +127,34 @@ describe('resolveAppNodeSpecs', () => { }); it('should fully override configuration and duplicate', () => { - const a = makeExt('a'); - const b = makeExt('b'); - const plugin = createPlugin({ id: 'test', extensions: [a, b] }); + const a = makeExt('test/a'); + const b = makeExt('test/b'); + const plugin = createPlugin({ + id: 'test', + extensions: [makeExtDef('a'), makeExtDef('b')], + }); expect( resolveAppNodeSpecs({ features: [plugin], builtinExtensions: [], parameters: [ { - id: 'a', + id: 'test/a', config: { foo: { bar: 1 } }, }, { - id: 'b', + id: 'test/b', config: { foo: { bar: 2 } }, }, { - id: 'b', + id: 'test/b', config: { foo: { qux: 3 } }, }, ], }), ).toEqual([ { - id: 'a', + id: 'test/a', extension: a, attachTo: { id: 'root', input: 'default' }, source: plugin, @@ -142,7 +162,7 @@ describe('resolveAppNodeSpecs', () => { disabled: false, }, { - id: 'b', + id: 'test/b', extension: b, attachTo: { id: 'root', input: 'default' }, source: plugin, @@ -187,11 +207,12 @@ describe('resolveAppNodeSpecs', () => { }); it('should apply extension overrides', () => { - const a = makeExt('a'); - const b = makeExt('b'); - const plugin = createPlugin({ id: 'test', extensions: [a, b] }); - const aOverride = makeExt('a', 'enabled', 'other'); - const bOverride = makeExt('b', 'disabled', 'other'); + const plugin = createPlugin({ + id: 'test', + extensions: [makeExtDef('a'), makeExtDef('b')], + }); + const aOverride = makeExt('test/a', 'enabled', 'other'); + const bOverride = makeExt('test/b', 'disabled', 'other'); const cOverride = makeExt('c'); expect( @@ -199,7 +220,11 @@ describe('resolveAppNodeSpecs', () => { features: [ plugin, createExtensionOverrides({ - extensions: [aOverride, bOverride, cOverride], + extensions: [ + makeExtDef('test/a', 'enabled', 'other'), + makeExtDef('test/b', 'disabled', 'other'), + makeExtDef('c'), + ], }), ], builtinExtensions: [], @@ -207,14 +232,14 @@ describe('resolveAppNodeSpecs', () => { }), ).toEqual([ { - id: 'a', + id: 'test/a', extension: aOverride, attachTo: { id: 'other', input: 'default' }, source: plugin, disabled: false, }, { - id: 'b', + id: 'test/b', extension: bOverride, attachTo: { id: 'other', input: 'default' }, source: plugin, @@ -231,39 +256,50 @@ describe('resolveAppNodeSpecs', () => { }); it('should use order from configuration when rather than overrides', () => { - const a = makeExt('a', 'disabled'); - const b = makeExt('b', 'disabled'); - const c = makeExt('c', 'disabled'); - const aOverride = makeExt('c', 'disabled'); - const bOverride = makeExt('b', 'disabled'); - const cOverride = makeExt('a', 'disabled'); - const result = resolveAppNodeSpecs({ features: [ - createPlugin({ id: 'test', extensions: [a, b, c] }), + createPlugin({ + id: 'test', + extensions: [ + makeExtDef('a', 'disabled'), + makeExtDef('b', 'disabled'), + makeExtDef('c', 'disabled'), + ], + }), createExtensionOverrides({ - extensions: [cOverride, bOverride, aOverride], + extensions: [ + makeExtDef('test/c', 'disabled'), + makeExtDef('test/b', 'disabled'), + makeExtDef('test/a', 'disabled'), + ], }), ], builtinExtensions: [], - parameters: ['b', 'c', 'a'].map(id => ({ id, disabled: false })), + parameters: ['test/b', 'test/c', 'test/a'].map(id => ({ + id, + disabled: false, + })), }); - expect(result.map(r => r.extension.id)).toEqual(['b', 'c', 'a']); + expect(result.map(r => r.extension.id)).toEqual([ + 'test/b', + 'test/c', + 'test/a', + ]); }); it('throws an error when a forbidden extension is overridden by a plugin', () => { expect(() => resolveAppNodeSpecs({ features: [ - createPlugin({ id: 'test', extensions: [makeExt('forbidden')] }), + createPlugin({ id: 'test', extensions: [makeExtDef('forbidden')] }), ], builtinExtensions: [], parameters: [], - forbidden: new Set(['forbidden']), + forbidden: new Set(['test/forbidden']), }), ).toThrow( - "It is forbidden to override the following extension(s): 'forbidden', which is done by the following plugin(s): 'test'", + "It is forbidden to override the following extension(s): 'test/forbidden', which is done by the following plugin(s): 'test'", ); }); @@ -271,7 +307,7 @@ describe('resolveAppNodeSpecs', () => { expect(() => resolveAppNodeSpecs({ features: [ - createExtensionOverrides({ extensions: [makeExt('forbidden')] }), + createExtensionOverrides({ extensions: [makeExtDef('forbidden')] }), ], builtinExtensions: [], parameters: [], diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts index 0a024c0a3f..8fd9d44638 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts @@ -18,15 +18,20 @@ import { BackstagePlugin, Extension, ExtensionOverrides, + FrontendFeature, } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/wiring/createExtensionOverrides'; import { ExtensionParameters } from './readAppExtensionsConfig'; import { AppNodeSpec } from '@backstage/frontend-plugin-api'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalBackstagePlugin } from '../../../frontend-plugin-api/src/wiring/createPlugin'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; /** @internal */ export function resolveAppNodeSpecs(options: { - features: (BackstagePlugin | ExtensionOverrides)[]; + features: FrontendFeature[]; builtinExtensions: Extension[]; parameters: Array; forbidden?: Set; @@ -42,7 +47,10 @@ export function resolveAppNodeSpecs(options: { ); const pluginExtensions = plugins.flatMap(source => { - return source.extensions.map(extension => ({ ...extension, source })); + return toInternalBackstagePlugin(source).extensions.map(extension => ({ + ...extension, + source, + })); }); const overrideExtensions = overrides.flatMap( override => toInternalExtensionOverrides(override).extensions, @@ -83,45 +91,53 @@ export function resolveAppNodeSpecs(options: { } const configuredExtensions = [ - ...pluginExtensions.map(({ source, ...extension }) => ({ - extension, - params: { - source, - attachTo: extension.attachTo, - disabled: extension.disabled, - config: undefined as unknown, - }, - })), - ...builtinExtensions.map(extension => ({ - extension, - params: { - source: undefined, - attachTo: extension.attachTo, - disabled: extension.disabled, - config: undefined as unknown, - }, - })), + ...pluginExtensions.map(({ source, ...extension }) => { + const internalExtension = toInternalExtension(extension); + return { + extension: internalExtension, + params: { + source, + attachTo: internalExtension.attachTo, + disabled: internalExtension.disabled, + config: undefined as unknown, + }, + }; + }), + ...builtinExtensions.map(extension => { + const internalExtension = toInternalExtension(extension); + return { + extension: internalExtension, + params: { + source: undefined, + attachTo: internalExtension.attachTo, + disabled: internalExtension.disabled, + config: undefined as unknown, + }, + }; + }), ]; // Install all extension overrides for (const extension of overrideExtensions) { + const internalExtension = toInternalExtension(extension); + // Check if our override is overriding an extension that already exists const index = configuredExtensions.findIndex( e => e.extension.id === extension.id, ); if (index !== -1) { // Only implementation, attachment point and default disabled status are overridden, the source is kept - configuredExtensions[index].extension = extension; - configuredExtensions[index].params.attachTo = extension.attachTo; - configuredExtensions[index].params.disabled = extension.disabled; + configuredExtensions[index].extension = internalExtension; + configuredExtensions[index].params.attachTo = internalExtension.attachTo; + configuredExtensions[index].params.disabled = internalExtension.disabled; } else { // Add the extension as a new one when not overriding an existing one configuredExtensions.push({ - extension, + extension: internalExtension, params: { source: undefined, - attachTo: extension.attachTo, - disabled: extension.disabled, + attachTo: internalExtension.attachTo, + disabled: internalExtension.disabled, config: undefined, }, }); diff --git a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts index d84064b26a..f964fceb50 100644 --- a/packages/frontend-app-api/src/tree/resolveAppTree.test.ts +++ b/packages/frontend-app-api/src/tree/resolveAppTree.test.ts @@ -16,15 +16,17 @@ import { createExtension } from '@backstage/frontend-plugin-api'; import { resolveAppTree } from './resolveAppTree'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; -const extBaseConfig = { - id: 'test', - attachTo: { id: 'nonexistent', input: 'nonexistent' }, - output: {}, - factory: () => ({}), -}; - -const extension = createExtension(extBaseConfig); +const extension = resolveExtensionDefinition( + createExtension({ + name: 'test', + attachTo: { id: 'nonexistent', input: 'nonexistent' }, + output: {}, + factory: () => ({}), + }), +); const baseSpec = { extension, @@ -34,19 +36,19 @@ const baseSpec = { describe('buildAppTree', () => { it('should fail to create an empty tree', () => { - expect(() => resolveAppTree('core', [])).toThrow( - "No root node with id 'core' found in app tree", + expect(() => resolveAppTree('app', [])).toThrow( + "No root node with id 'app' found in app tree", ); }); it('should create a tree with only one node', () => { - const tree = resolveAppTree('core', [{ ...baseSpec, id: 'core' }]); + const tree = resolveAppTree('app', [{ ...baseSpec, id: 'app' }]); expect(tree.root).toEqual({ - spec: { ...baseSpec, id: 'core' }, + spec: { ...baseSpec, id: 'app' }, edges: { attachments: new Map() }, }); expect(Array.from(tree.orphans)).toEqual([]); - expect(Array.from(tree.nodes.keys())).toEqual(['core']); + expect(Array.from(tree.nodes.keys())).toEqual(['app']); }); it('should create a tree', () => { @@ -157,7 +159,7 @@ describe('buildAppTree', () => { it('throws an error when duplicated extensions are detected', () => { expect(() => - resolveAppTree('core', [ + resolveAppTree('app', [ { ...baseSpec, id: 'a' }, { ...baseSpec, id: 'a' }, ]), diff --git a/packages/frontend-app-api/src/wiring/InternalAppContext.ts b/packages/frontend-app-api/src/wiring/InternalAppContext.ts new file mode 100644 index 0000000000..81264bd81b --- /dev/null +++ b/packages/frontend-app-api/src/wiring/InternalAppContext.ts @@ -0,0 +1,28 @@ +/* + * 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 { createContext } from 'react'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; +import { BackstageRouteObject } from '../routing/types'; + +export const InternalAppContext = createContext< + | undefined + | { + appIdentityProxy: AppIdentityProxy; + routeObjects: BackstageRouteObject[]; + } +>(undefined); diff --git a/packages/frontend-app-api/src/wiring/createApp.test.tsx b/packages/frontend-app-api/src/wiring/createApp.test.tsx index 80d9f846f0..43bb4499b1 100644 --- a/packages/frontend-app-api/src/wiring/createApp.test.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.test.tsx @@ -17,25 +17,32 @@ import { AppTreeApi, appTreeApiRef, + coreExtensionData, + createExtension, + createExtensionOverrides, createPageExtension, createPlugin, createThemeExtension, } from '@backstage/frontend-plugin-api'; import { screen, waitFor } from '@testing-library/react'; -import { createApp } from './createApp'; +import { CreateAppFeatureLoader, createApp } from './createApp'; import { MockConfigApi, renderWithEffects } from '@backstage/test-utils'; import React from 'react'; -import { useApi } from '@backstage/core-plugin-api'; +import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api'; describe('createApp', () => { it('should allow themes to be installed', async () => { const app = createApp({ - configLoader: async () => - new MockConfigApi({ + configLoader: async () => ({ + config: new MockConfigApi({ app: { - extensions: [{ 'themes.light': false }, { 'themes.dark': false }], + extensions: [ + { 'theme:app/light': false }, + { 'theme:app/dark': false }, + ], }, }), + }), features: [ createPlugin({ id: 'test', @@ -59,13 +66,12 @@ describe('createApp', () => { it('should deduplicate features keeping the last received one', async () => { const duplicatedFeatureId = 'test'; const app = createApp({ - configLoader: async () => new MockConfigApi({}), + configLoader: async () => ({ config: new MockConfigApi({}) }), features: [ createPlugin({ id: duplicatedFeatureId, extensions: [ createPageExtension({ - id: 'test.page.first', defaultPath: '/', loader: async () =>
First Page
, }), @@ -75,7 +81,6 @@ describe('createApp', () => { id: duplicatedFeatureId, extensions: [ createPageExtension({ - id: 'test.page.last', defaultPath: '/', loader: async () =>
Last Page
, }), @@ -94,17 +99,129 @@ describe('createApp', () => { ); }); + it('should support feature loaders', async () => { + const loader: CreateAppFeatureLoader = { + getLoaderName() { + return 'test-loader'; + }, + async load({ config }) { + return { + features: [ + createPlugin({ + id: 'test', + extensions: [ + createPageExtension({ + defaultPath: '/', + loader: async () =>
{config.getString('key')}
, + }), + ], + }), + ], + }; + }, + }; + + const app = createApp({ + configLoader: async () => ({ + config: new MockConfigApi({ key: 'config-value' }), + }), + features: [loader], + }); + + await renderWithEffects(app.createRoot()); + + await expect( + screen.findByText('config-value'), + ).resolves.toBeInTheDocument(); + }); + + it('should propagate errors thrown by feature loaders', async () => { + const loader: CreateAppFeatureLoader = { + getLoaderName() { + return 'test-loader'; + }, + async load() { + throw new TypeError('boom'); + }, + }; + + const app = createApp({ + configLoader: async () => ({ + config: new MockConfigApi({}), + }), + features: [loader], + }); + + await expect( + renderWithEffects(app.createRoot()), + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Failed to read frontend features from loader 'test-loader', TypeError: boom"`, + ); + }); + + it('should register feature flags', async () => { + const app = createApp({ + configLoader: async () => ({ config: new MockConfigApi({}) }), + features: [ + createPlugin({ + id: 'test', + featureFlags: [{ name: 'test-1' }], + extensions: [ + createExtension({ + name: 'first', + attachTo: { id: 'app', input: 'root' }, + output: { element: coreExtensionData.reactElement }, + factory() { + const Component = () => { + const flagsApi = useApi(featureFlagsApiRef); + return ( +
+ Flags:{' '} + {flagsApi + .getRegisteredFlags() + .map(flag => `${flag.name} from '${flag.pluginId}'`) + .join(', ')} +
+ ); + }; + return { element: }; + }, + }), + ], + }), + createExtensionOverrides({ + featureFlags: [{ name: 'test-2' }], + extensions: [ + createExtension({ + namespace: 'app', + name: 'root', + attachTo: { id: 'app', input: 'root' }, + disabled: true, + output: {}, + factory: () => ({}), + }), + ], + }), + ], + }); + + await renderWithEffects(app.createRoot()); + + await expect( + screen.findByText("Flags: test-1 from 'test', test-2 from ''"), + ).resolves.toBeInTheDocument(); + }); + it('should make the app structure available through the AppTreeApi', async () => { let appTreeApi: AppTreeApi | undefined = undefined; const app = createApp({ - configLoader: async () => new MockConfigApi({}), + configLoader: async () => ({ config: new MockConfigApi({}) }), features: [ createPlugin({ id: 'my-plugin', extensions: [ createPageExtension({ - id: 'plugin.my-plugin.page', defaultPath: '/', loader: async () => { const Component = () => { @@ -125,26 +242,58 @@ describe('createApp', () => { const { tree } = appTreeApi!.getTree(); expect(String(tree.root)).toMatchInlineSnapshot(` - " + " root [ - - content [ - - routes [ - + + children [ + + content [ + + routes [ + + ] + ] - + nav [ + + ] + ] - nav [ - + elements [ + + ] - + + ] + components [ + + + ] themes [ - - + + ] - " + apis [ + + + + + + + + + + + + + + + + + + ] +
" `); }); }); diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index d39111eb99..6966ef87c3 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -19,27 +19,33 @@ import { ConfigReader, Config } from '@backstage/config'; import { AppTree, appTreeApiRef, - BackstagePlugin, + ComponentRef, + componentsApiRef, coreExtensionData, + createApiExtension, + createComponentExtension, + createNavItemExtension, + createThemeExtension, + createTranslationExtension, ExtensionDataRef, - ExtensionOverrides, + FrontendFeature, + iconsApiRef, RouteRef, + RouteResolutionApi, + routeResolutionApiRef, useRouteRef, } from '@backstage/frontend-plugin-api'; -import { Core } from '../extensions/Core'; -import { CoreRoutes } from '../extensions/CoreRoutes'; -import { CoreLayout } from '../extensions/CoreLayout'; -import { CoreNav } from '../extensions/CoreNav'; +import { App } from '../extensions/App'; +import { AppRoutes } from '../extensions/AppRoutes'; +import { AppLayout } from '../extensions/AppLayout'; +import { AppNav } from '../extensions/AppNav'; import { AnyApiFactory, ApiHolder, - AppComponents, - AppContext, appThemeApiRef, ConfigApi, configApiRef, IconComponent, - BackstagePlugin as LegacyBackstagePlugin, featureFlagsApiRef, attachComponentData, identityApiRef, @@ -59,8 +65,6 @@ import { AppThemeProvider } from '../../../core-app-api/src/app/AppThemeProvider // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { AppContextProvider } from '../../../core-app-api/src/app/AppContext'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports import { LocalStorageFeatureFlags } from '../../../core-app-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { defaultConfigLoaderSync } from '../../../core-app-api/src/app/defaultConfigLoader'; @@ -71,35 +75,62 @@ import { AppLanguageSelector } from '../../../core-app-api/src/apis/implementati // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { I18nextTranslationApi } from '../../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - apis as defaultApis, - components as defaultComponents, - icons as defaultIcons, -} from '../../../app-defaults/src/defaults'; -import { BrowserRouter, Route } from 'react-router-dom'; +import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { apis as defaultApis } from '../../../app-defaults/src/defaults'; +import { Route } from 'react-router-dom'; import { SidebarItem } from '@backstage/core-components'; import { DarkTheme, LightTheme } from '../extensions/themes'; +import { + oauthRequestDialogAppRootElement, + alertDisplayAppRootElement, +} from '../extensions/elements'; import { extractRouteInfoFromAppNode } from '../routing/extractRouteInfoFromAppNode'; import { appLanguageApiRef, translationApiRef, } from '@backstage/core-plugin-api/alpha'; -import { AppRouteBinder } from '../routing'; -import { RoutingProvider } from '../routing/RoutingProvider'; +import { CreateAppRouteBinder } from '../routing'; +import { RouteResolver } from '../routing/RouteResolver'; import { resolveRouteBindings } from '../routing/resolveRouteBindings'; import { collectRouteIds } from '../routing/collectRouteIds'; import { createAppTree } from '../tree'; +import { + DefaultProgressComponent, + DefaultErrorBoundaryComponent, + DefaultNotFoundErrorPageComponent, +} from '../extensions/components'; import { AppNode } from '@backstage/frontend-plugin-api'; -import { toLegacyPlugin } from '../routing/toLegacyPlugin'; +import { InternalAppContext } from './InternalAppContext'; +import { AppRoot } from '../extensions/AppRoot'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalBackstagePlugin } from '../../../frontend-plugin-api/src/wiring/createPlugin'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalExtensionOverrides } from '../../../frontend-plugin-api/src/wiring/createExtensionOverrides'; +import { DefaultComponentsApi } from '../apis/implementations/ComponentsApi'; +import { DefaultIconsApi } from '../apis/implementations/IconsApi'; +import { stringifyError } from '@backstage/errors'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { icons as defaultIcons } from '../../../app-defaults/src/defaults'; +import { getBasePath } from '../routing/getBasePath'; -const builtinExtensions = [ - Core, - CoreRoutes, - CoreNav, - CoreLayout, +const DefaultApis = defaultApis.map(factory => createApiExtension({ factory })); + +export const builtinExtensions = [ + App, + AppRoot, + AppRoutes, + AppNav, + AppLayout, + DefaultProgressComponent, + DefaultErrorBoundaryComponent, + DefaultNotFoundErrorPageComponent, LightTheme, DarkTheme, -]; + oauthRequestDialogAppRootElement, + alertDisplayAppRootElement, + ...DefaultApis, +].map(def => resolveExtensionDefinition(def)); /** @public */ export interface ExtensionTreeNode { @@ -154,7 +185,7 @@ export function createExtensionTree(options: { ); }, getRootRoutes(): JSX.Element[] { - return this.getExtensionAttachments('core.routes', 'routes').map(node => { + return this.getExtensionAttachments('app/routes', 'routes').map(node => { const path = node.getData(coreExtensionData.routePath); const element = node.getData(coreExtensionData.reactElement); const routeRef = node.getData(coreExtensionData.routeRef); @@ -181,9 +212,9 @@ export function createExtensionTree(options: { ); }; - return this.getExtensionAttachments('core.nav', 'items') + return this.getExtensionAttachments('app/nav', 'items') .map((node, index) => { - const target = node.getData(coreExtensionData.navTarget); + const target = node.getData(createNavItemExtension.targetDataRef); if (!target) { return null; } @@ -202,8 +233,8 @@ export function createExtensionTree(options: { } function deduplicateFeatures( - allFeatures: (BackstagePlugin | ExtensionOverrides)[], -): (BackstagePlugin | ExtensionOverrides)[] { + allFeatures: FrontendFeature[], +): FrontendFeature[] { // Start by removing duplicates by reference const features = Array.from(new Set(allFeatures)); @@ -224,34 +255,65 @@ function deduplicateFeatures( .reverse(); } +/** + * A source of dynamically loaded frontend features. + * + * @public + */ +export interface CreateAppFeatureLoader { + /** + * Returns name of this loader. suitable for showing to users. + */ + getLoaderName(): string; + + /** + * Loads a number of features dynamically. + */ + load(options: { config: ConfigApi }): Promise<{ + features: FrontendFeature[]; + }>; +} + /** @public */ export function createApp(options?: { - features?: (BackstagePlugin | ExtensionOverrides)[]; - configLoader?: () => Promise; - bindRoutes?(context: { bind: AppRouteBinder }): void; - featureLoader?: (ctx: { - config: ConfigApi; - }) => Promise<(BackstagePlugin | ExtensionOverrides)[]>; + icons?: { [key in string]: IconComponent }; + features?: (FrontendFeature | CreateAppFeatureLoader)[]; + configLoader?: () => Promise<{ config: ConfigApi }>; + bindRoutes?(context: { bind: CreateAppRouteBinder }): void; }): { createRoot(): JSX.Element; } { async function appLoader() { const config = - (await options?.configLoader?.()) ?? + (await options?.configLoader?.().then(c => c.config)) ?? ConfigReader.fromConfigs( overrideBaseUrlConfigs(defaultConfigLoaderSync()), ); const discoveredFeatures = getAvailableFeatures(config); - const loadedFeatures = (await options?.featureLoader?.({ config })) ?? []; + + const providedFeatures: FrontendFeature[] = []; + for (const entry of options?.features ?? []) { + if ('load' in entry) { + try { + const result = await entry.load({ config }); + providedFeatures.push(...result.features); + } catch (e) { + throw new Error( + `Failed to read frontend features from loader '${entry.getLoaderName()}', ${stringifyError( + e, + )}`, + ); + } + } else { + providedFeatures.push(entry); + } + } const app = createSpecializedApp({ + icons: options?.icons, config, - features: [ - ...discoveredFeatures, - ...loadedFeatures, - ...(options?.features ?? []), - ], + features: [...discoveredFeatures, ...providedFeatures], bindRoutes: options?.bindRoutes, }).createRoot(); @@ -273,12 +335,14 @@ export function createApp(options?: { /** * Synchronous version of {@link createApp}, expecting all features and * config to have been loaded already. + * * @public */ export function createSpecializedApp(options?: { - features?: (BackstagePlugin | ExtensionOverrides)[]; + icons?: { [key in string]: IconComponent }; + features?: FrontendFeature[]; config?: ConfigApi; - bindRoutes?(context: { bind: AppRouteBinder }): void; + bindRoutes?(context: { bind: CreateAppRouteBinder }): void; }): { createRoot(): JSX.Element } { const { features: duplicatedFeatures = [], @@ -293,79 +357,100 @@ export function createSpecializedApp(options?: { config, }); - const appContext = createLegacyAppContext( - features.filter( - (f): f is BackstagePlugin => f.$$type === '@backstage/BackstagePlugin', - ), - ); - - const apiHolder = createApiHolder(tree, config); const routeInfo = extractRouteInfoFromAppNode(tree.root); const routeBindings = resolveRouteBindings( options?.bindRoutes, config, collectRouteIds(features), ); + + const appIdentityProxy = new AppIdentityProxy(); + const apiHolder = createApiHolder( + tree, + config, + appIdentityProxy, + new RouteResolver( + routeInfo.routePaths, + routeInfo.routeParents, + routeInfo.routeObjects, + routeBindings, + getBasePath(config), + ), + options?.icons, + ); + + const featureFlagApi = apiHolder.get(featureFlagsApiRef); + if (featureFlagApi) { + for (const feature of features) { + if (feature.$$type === '@backstage/BackstagePlugin') { + toInternalBackstagePlugin(feature).featureFlags.forEach(flag => + featureFlagApi.registerFlag({ + name: flag.name, + pluginId: feature.id, + }), + ); + } + if (feature.$$type === '@backstage/ExtensionOverrides') { + toInternalExtensionOverrides(feature).featureFlags.forEach(flag => + featureFlagApi.registerFlag({ name: flag.name, pluginId: '' }), + ); + } + } + } + const rootEl = tree.root.instance!.getData(coreExtensionData.reactElement); - const App = () => ( + const AppComponent = () => ( - - - - {/* TODO: set base path using the logic from AppRouter */} - {rootEl} - - - + + + {rootEl} + + ); return { createRoot() { - return ; + return ; }, }; } -function createLegacyAppContext(plugins: BackstagePlugin[]): AppContext { - return { - getPlugins(): LegacyBackstagePlugin[] { - return plugins.map(toLegacyPlugin); - }, - - getSystemIcon(key: string): IconComponent | undefined { - return key in defaultIcons - ? defaultIcons[key as keyof typeof defaultIcons] - : undefined; - }, - - getSystemIcons(): Record { - return defaultIcons; - }, - - getComponents(): AppComponents { - return defaultComponents; - }, - }; -} - -function createApiHolder(tree: AppTree, configApi: ConfigApi): ApiHolder { +function createApiHolder( + tree: AppTree, + configApi: ConfigApi, + appIdentityProxy: AppIdentityProxy, + routeResolutionApi: RouteResolutionApi, + icons?: { [key in string]: IconComponent }, +): ApiHolder { const factoryRegistry = new ApiFactoryRegistry(); const pluginApis = tree.root.edges.attachments .get('apis') - ?.map(e => e.instance?.getData(coreExtensionData.apiFactory)) + ?.map(e => e.instance?.getData(createApiExtension.factoryDataRef)) .filter((x): x is AnyApiFactory => !!x) ?? []; const themeExtensions = tree.root.edges.attachments .get('themes') - ?.map(e => e.instance?.getData(coreExtensionData.theme)) + ?.map(e => e.instance?.getData(createThemeExtension.themeDataRef)) .filter((x): x is AppTheme => !!x) ?? []; - for (const factory of [...defaultApis, ...pluginApis]) { + const translationResources = + tree.root.edges.attachments + .get('translations') + ?.map(e => + e.instance?.getData(createTranslationExtension.translationDataRef), + ) + .filter( + (x): x is typeof createTranslationExtension.translationDataRef.T => !!x, + ) ?? []; + + for (const factory of pluginApis) { factoryRegistry.register('default', factory); } @@ -379,33 +464,7 @@ function createApiHolder(tree: AppTree, configApi: ConfigApi): ApiHolder { factoryRegistry.register('static', { api: identityApiRef, deps: {}, - factory: () => { - const appIdentityProxy = new AppIdentityProxy(); - // TODO: Remove this when sign-in page is migrated - appIdentityProxy.setTarget( - { - getUserId: () => 'guest', - getIdToken: async () => undefined, - getProfile: () => ({ - email: 'guest@example.com', - displayName: 'Guest', - }), - getProfileInfo: async () => ({ - email: 'guest@example.com', - displayName: 'Guest', - }), - getBackstageIdentity: async () => ({ - type: 'user', - userEntityRef: 'user:default/guest', - ownershipEntityRefs: ['user:default/guest'], - }), - getCredentials: async () => ({}), - signOut: async () => {}, - }, - { signOutTargetUrl: '/' }, - ); - return appIdentityProxy; - }, + factory: () => appIdentityProxy, }); factoryRegistry.register('static', { @@ -416,6 +475,36 @@ function createApiHolder(tree: AppTree, configApi: ConfigApi): ApiHolder { }), }); + factoryRegistry.register('static', { + api: routeResolutionApiRef, + deps: {}, + factory: () => routeResolutionApi, + }); + + const componentsExtensions = + tree.root.edges.attachments + .get('components') + ?.map(e => e.instance?.getData(createComponentExtension.componentDataRef)) + .filter(x => !!x) ?? []; + + const componentsMap = componentsExtensions.reduce( + (components, component) => + component ? components.set(component.ref, component?.impl) : components, + new Map, any>(), + ); + + factoryRegistry.register('static', { + api: componentsApiRef, + deps: {}, + factory: () => new DefaultComponentsApi(componentsMap), + }); + + factoryRegistry.register('static', { + api: iconsApiRef, + deps: {}, + factory: () => new DefaultIconsApi({ ...defaultIcons, ...icons }), + }); + factoryRegistry.register('static', { api: appThemeApiRef, deps: {}, @@ -429,15 +518,6 @@ function createApiHolder(tree: AppTree, configApi: ConfigApi): ApiHolder { factory: () => AppLanguageSelector.createWithStorage(), }); - factoryRegistry.register('default', { - api: translationApiRef, - deps: { languageApi: appLanguageApiRef }, - factory: ({ languageApi }) => - I18nextTranslationApi.create({ - languageApi, - }), - }); - factoryRegistry.register('static', { api: configApiRef, deps: {}, @@ -450,24 +530,16 @@ function createApiHolder(tree: AppTree, configApi: ConfigApi): ApiHolder { factory: () => AppLanguageSelector.createWithStorage(), }); - factoryRegistry.register('default', { + factoryRegistry.register('static', { api: translationApiRef, deps: { languageApi: appLanguageApiRef }, factory: ({ languageApi }) => I18nextTranslationApi.create({ languageApi, + resources: translationResources, }), }); - // TODO: ship these as default extensions instead - for (const factory of defaultApis as AnyApiFactory[]) { - if (!factoryRegistry.register('app', factory)) { - throw new Error( - `Duplicate or forbidden API factory for ${factory.api} in app`, - ); - } - } - ApiResolver.validateFactories(factoryRegistry, factoryRegistry.getAllApis()); return new ApiResolver(factoryRegistry); diff --git a/packages/frontend-app-api/src/wiring/discovery.ts b/packages/frontend-app-api/src/wiring/discovery.ts index 1181992d53..4e50290350 100644 --- a/packages/frontend-app-api/src/wiring/discovery.ts +++ b/packages/frontend-app-api/src/wiring/discovery.ts @@ -15,10 +15,7 @@ */ import { Config, ConfigReader } from '@backstage/config'; -import { - BackstagePlugin, - ExtensionOverrides, -} from '@backstage/frontend-plugin-api'; +import { FrontendFeature } from '@backstage/frontend-plugin-api'; interface DiscoveryGlobal { modules: Array<{ name: string; export?: string; default: unknown }>; @@ -58,9 +55,7 @@ function readPackageDetectionConfig(config: Config) { /** * @public */ -export function getAvailableFeatures( - config: Config, -): (BackstagePlugin | ExtensionOverrides)[] { +export function getAvailableFeatures(config: Config): FrontendFeature[] { const discovered = ( window as { '__@backstage/discovered__'?: DiscoveryGlobal } )['__@backstage/discovered__']; @@ -86,9 +81,7 @@ export function getAvailableFeatures( ); } -function isBackstageFeature( - obj: unknown, -): obj is BackstagePlugin | ExtensionOverrides { +function isBackstageFeature(obj: unknown): obj is FrontendFeature { if (obj !== null && typeof obj === 'object' && '$$type' in obj) { return ( obj.$$type === '@backstage/BackstagePlugin' || diff --git a/packages/frontend-app-api/src/wiring/index.ts b/packages/frontend-app-api/src/wiring/index.ts index 412e643523..00c6458bd5 100644 --- a/packages/frontend-app-api/src/wiring/index.ts +++ b/packages/frontend-app-api/src/wiring/index.ts @@ -18,6 +18,7 @@ export { createApp, createSpecializedApp, createExtensionTree, + type CreateAppFeatureLoader, type ExtensionTreeNode, type ExtensionTree, } from './createApp'; diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index 94f90d6717..07f74e3ff4 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,148 @@ # @backstage/frontend-plugin-api +## 0.5.1-next.0 + +### Patch Changes + +- 7eae3e0: Added initial `IconsApi` definition. +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.5.0 + +### Minor Changes + +- d4149bf: **BREAKING**: Renamed the `app/router` extension to `app/root`. + +### Patch Changes + +- b2d370e: Exposed `createComponentRef`, and ensured that produced refs and feature bits have a `toString` for easier debugging +- 7d63b32: Accepts sub route refs on the new `createPlugin` routes map. +- 516fd3e: Updated README to reflect release status +- 4016f21: Remove some unused dependencies +- c97fa1c: Added `elements`, `wrappers`, and `router` inputs to `app/root`, that let you add things to the root of the React tree above the layout. You can use the `createAppRootElementExtension`, `createAppRootWrapperExtension`, and `createRouterExtension` extension creator, respectively, to conveniently create such extensions. These are all optional, and if you do not supply a router a default one will be used (`BrowserRouter` in regular runs, `MemoryRouter` in tests/CI). +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.4.1-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.4.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.4.0 + +### Minor Changes + +- af7bc3e: Switched all core extensions to instead use the namespace `'app'`. +- 5cdf2b3: Changed `Extension` and `ExtensionDefinition` to use opaque types. +- 8f5d6c1: Extension inputs are now wrapped into an additional object when passed to the extension factory, with the previous values being available at the `output` property. The `ExtensionInputValues` type has also been replaced by `ResolvedExtensionInputs`. +- 8837a96: **BREAKING**: This version changes how extensions are created and how their IDs are determined. The `createExtension` function now accepts `kind`, `namespace` and `name` instead of `id`. All of the new options are optional, and are used to construct the final extension ID. By convention extension creators should set the `kind` to match their own name, for example `createNavItemExtension` sets the kind `nav-item`. + + The `createExtension` function as well as all extension creators now also return an `ExtensionDefinition` rather than an `Extension`, which in turn needs to be passed to `createPlugin` or `createExtensionOverrides` to be used. + +- f9ef632: Moved several extension data references from `coreExtensionData` to their respective extension creators. +- a5a0473: The extension `factory` function now longer receives `id` or `source`, but instead now provides the extension's `AppNode` as `node`. The `ExtensionBoundary` component has also been updated to receive a `node` prop rather than `id` and `source`. + +### Patch Changes + +- a379243: Add the `FrontendFeature` type, which is the union of `BackstagePlugin` and `ExtensionOverrides` +- b7adf24: Update alpha component ref type to be more specific than any, delete boot page component and use new plugin type for error boundary component extensions. +- 5eb6b8a: Added the nav logo extension for customization of sidebar logo +- 1f12fb7: Create factories for overriding default core components extensions. +- 5970928: Add feature flags to plugins and extension overrides. +- e539735: Added `createSignInPageExtension`. +- 73246ec: Added translation APIs as well as `createTranslationExtension`. +- cb4197a: Forward ` node`` instead of `extensionId` to resolved extension inputs. +- f27ee7d: Migrate analytics api and context files. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- f1183b7: Renamed the `component` option of `createComponentExtension` to `loader`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.4.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.4.0-next.2 + +### Minor Changes + +- 8f5d6c1: Extension inputs are now wrapped into an additional object when passed to the extension factory, with the previous values being available at the `output` property. The `ExtensionInputValues` type has also been replaced by `ResolvedExtensionInputs`. +- 8837a96: **BREAKING**: This version changes how extensions are created and how their IDs are determined. The `createExtension` function now accepts `kind`, `namespace` and `name` instead of `id`. All of the new options are optional, and are used to construct the final extension ID. By convention extension creators should set the `kind` to match their own name, for example `createNavItemExtension` sets the kind `nav-item`. + + The `createExtension` function as well as all extension creators now also return an `ExtensionDefinition` rather than an `Extension`, which in turn needs to be passed to `createPlugin` or `createExtensionOverrides` to be used. + +### Patch Changes + +- b7adf24: Update alpha component ref type to be more specific than any, delete boot page component and use new plugin type for error boundary component extensions. +- 73246ec: Added translation APIs as well as `createTranslationExtension`. +- cb4197a: Forward ` node`` instead of `extensionId` to resolved extension inputs. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 0.4.0-next.1 + +### Minor Changes + +- a5a04739e1: The extension `factory` function now longer receives `id` or `source`, but instead now provides the extension's `AppNode` as `node`. The `ExtensionBoundary` component has also been updated to receive a `node` prop rather than `id` and `source`. + +### Patch Changes + +- 5eb6b8a7bc: Added the nav logo extension for customization of sidebar logo +- 1f12fb762c: Create factories for overriding default core components extensions. +- 59709286b3: Add feature flags to plugins and extension overrides. +- e539735435: Added `createSignInPageExtension`. +- f27ee7d937: Migrate analytics api and context files. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + ## 0.3.1-next.0 ### Patch Changes diff --git a/packages/frontend-plugin-api/README.md b/packages/frontend-plugin-api/README.md index 1fd3da79d8..aedf740840 100644 --- a/packages/frontend-plugin-api/README.md +++ b/packages/frontend-plugin-api/README.md @@ -1,8 +1,8 @@ # @backstage/frontend-plugin-api -**This package is EXPERIMENTAL, we recommend against using it for production deployments** +**The [new frontend system](https://backstage.io/docs/frontend-system/) that this package is part of is in alpha, and we do not yet recommend using it for production deployments** -This package provides the core API used by Backstage frontend plugins. It implements the design outlined in [RFC: Frontend System Evolution](https://github.com/backstage/backstage/issues/18372). +This package provides the framework API used by Backstage frontend plugins. It implements the design outlined in [RFC: Frontend System Evolution](https://github.com/backstage/backstage/issues/18372). ## Documentation diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index f1537fd5b7..4116469c2a 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -30,6 +30,9 @@ import { ConfigApi } from '@backstage/core-plugin-api'; import { configApiRef } from '@backstage/core-plugin-api'; import { createApiFactory } from '@backstage/core-plugin-api'; import { createApiRef } from '@backstage/core-plugin-api'; +import { createTranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { createTranslationResource } from '@backstage/core-plugin-api/alpha'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { discoveryApiRef } from '@backstage/core-plugin-api'; import { ErrorApi } from '@backstage/core-plugin-api'; @@ -64,16 +67,25 @@ import { OpenIdConnectApi } from '@backstage/core-plugin-api'; import { PendingOAuthRequest } from '@backstage/core-plugin-api'; import { ProfileInfo } from '@backstage/core-plugin-api'; import { ProfileInfoApi } from '@backstage/core-plugin-api'; +import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { SessionApi } from '@backstage/core-plugin-api'; import { SessionState } from '@backstage/core-plugin-api'; +import { SignInPageProps } from '@backstage/core-plugin-api'; import { StorageApi } from '@backstage/core-plugin-api'; import { storageApiRef } from '@backstage/core-plugin-api'; import { StorageValueSnapshot } from '@backstage/core-plugin-api'; +import { TranslationMessages } from '@backstage/core-plugin-api/alpha'; +import { TranslationMessagesOptions } from '@backstage/core-plugin-api/alpha'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { TranslationRefOptions } from '@backstage/core-plugin-api/alpha'; +import { TranslationResource } from '@backstage/core-plugin-api/alpha'; +import { TranslationResourceOptions } from '@backstage/core-plugin-api/alpha'; import { TypesToApiRefs } from '@backstage/core-plugin-api'; import { useApi } from '@backstage/core-plugin-api'; import { useApiHolder } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { withApis } from '@backstage/core-plugin-api'; import { z } from 'zod'; import { ZodSchema } from 'zod'; @@ -85,6 +97,51 @@ export { alertApiRef }; export { AlertMessage }; +// @public +export type AnalyticsApi = { + captureEvent(event: AnalyticsEvent): void; +}; + +// @public +export const analyticsApiRef: ApiRef; + +// @public +export const AnalyticsContext: (options: { + attributes: Partial; + children: ReactNode; +}) => React_2.JSX.Element; + +// @public +export type AnalyticsContextValue = CommonAnalyticsContext & { + [param in string]: string | boolean | number | undefined; +}; + +// @public +export type AnalyticsEvent = { + action: string; + subject: string; + value?: number; + attributes?: AnalyticsEventAttributes; + context: AnalyticsContextValue; +}; + +// @public +export type AnalyticsEventAttributes = { + [attribute in string]: string | boolean | number; +}; + +// @public +export type AnalyticsTracker = { + captureEvent: ( + action: string, + subject: string, + options?: { + value?: number; + attributes?: AnalyticsEventAttributes; + }, + ) => void; +}; + export { AnyApiFactory }; export { AnyApiRef }; @@ -124,7 +181,7 @@ export type AnyRouteRefParams = // @public (undocumented) export type AnyRoutes = { - [name in string]: RouteRef; + [name in string]: RouteRef | SubRouteRef; }; export { ApiFactory }; @@ -217,15 +274,13 @@ export interface BackstagePlugin< ExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, > { // (undocumented) - $$type: '@backstage/BackstagePlugin'; + readonly $$type: '@backstage/BackstagePlugin'; // (undocumented) - extensions: Extension[]; + readonly externalRoutes: ExternalRoutes; // (undocumented) - externalRoutes: ExternalRoutes; + readonly id: string; // (undocumented) - id: string; - // (undocumented) - routes: Routes; + readonly routes: Routes; } export { BackstageUserIdentity }; @@ -234,6 +289,27 @@ export { bitbucketAuthApiRef }; export { bitbucketServerAuthApiRef }; +// @public +export type CommonAnalyticsContext = { + pluginId: string; + extensionId: string; +}; + +// @public (undocumented) +export type ComponentRef = { + id: string; + T: T; +}; + +// @public +export interface ComponentsApi { + // (undocumented) + getComponent(ref: ComponentRef): ComponentType; +} + +// @public +export const componentsApiRef: ApiRef; + export { ConfigApi }; export { configApiRef }; @@ -254,16 +330,35 @@ export interface ConfigurableExtensionDataRef< >; } +// @public (undocumented) +export const coreComponentRefs: { + progress: ComponentRef; + notFoundErrorPage: ComponentRef; + errorBoundaryFallback: ComponentRef; +}; + +// @public (undocumented) +export type CoreErrorBoundaryFallbackProps = { + plugin?: BackstagePlugin; + error: Error; + resetError: () => void; +}; + // @public (undocumented) export const coreExtensionData: { reactElement: ConfigurableExtensionDataRef; routePath: ConfigurableExtensionDataRef; - apiFactory: ConfigurableExtensionDataRef; routeRef: ConfigurableExtensionDataRef, {}>; - navTarget: ConfigurableExtensionDataRef; - theme: ConfigurableExtensionDataRef; }; +// @public (undocumented) +export type CoreNotFoundErrorPageProps = { + children?: ReactNode; +}; + +// @public (undocumented) +export type CoreProgressProps = {}; + // @public (undocumented) export function createApiExtension< TConfig extends {}, @@ -274,7 +369,7 @@ export function createApiExtension< api: AnyApiRef; factory: (options: { config: TConfig; - inputs: Expand>; + inputs: Expand>; }) => AnyApiFactory; } | { @@ -284,12 +379,116 @@ export function createApiExtension< configSchema?: PortableSchema; inputs?: TInputs; }, -): Extension; +): ExtensionDefinition; + +// @public (undocumented) +export namespace createApiExtension { + const // (undocumented) + factoryDataRef: ConfigurableExtensionDataRef; +} export { createApiFactory }; export { createApiRef }; +// @public +export function createAppRootElementExtension< + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options: { + namespace?: string; + name?: string; + attachTo?: { + id: string; + input: string; + }; + configSchema?: PortableSchema; + disabled?: boolean; + inputs?: TInputs; + element: + | JSX_2.Element + | ((options: { + inputs: Expand>; + config: TConfig; + }) => JSX_2.Element); +}): ExtensionDefinition; + +// @public +export function createAppRootWrapperExtension< + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options: { + namespace?: string; + name?: string; + attachTo?: { + id: string; + input: string; + }; + configSchema?: PortableSchema; + disabled?: boolean; + inputs?: TInputs; + Component: ComponentType< + PropsWithChildren<{ + inputs: Expand>; + config: TConfig; + }> + >; +}): ExtensionDefinition; + +// @public (undocumented) +export namespace createAppRootWrapperExtension { + const // (undocumented) + componentDataRef: ConfigurableExtensionDataRef< + React_2.ComponentType<{ + children?: React_2.ReactNode; + }>, + {} + >; +} + +// @public (undocumented) +export function createComponentExtension< + TProps extends {}, + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options: { + ref: ComponentRef; + name?: string; + disabled?: boolean; + inputs?: TInputs; + configSchema?: PortableSchema; + loader: + | { + lazy: (values: { + config: TConfig; + inputs: Expand>; + }) => Promise>; + } + | { + sync: (values: { + config: TConfig; + inputs: Expand>; + }) => ComponentType; + }; +}): ExtensionDefinition; + +// @public (undocumented) +export namespace createComponentExtension { + const // (undocumented) + componentDataRef: ConfigurableExtensionDataRef< + { + ref: ComponentRef; + impl: ComponentType; + }, + {} + >; +} + +// @public (undocumented) +export function createComponentRef(options: { + id: string; +}): ComponentRef; + // @public (undocumented) export function createExtension< TOutput extends AnyExtensionDataMap, @@ -297,7 +496,7 @@ export function createExtension< TConfig = never, >( options: CreateExtensionOptions, -): Extension; +): ExtensionDefinition; // @public (undocumented) export function createExtensionDataRef( @@ -339,15 +538,19 @@ export interface CreateExtensionOptions< disabled?: boolean; // (undocumented) factory(options: { - source?: BackstagePlugin; + node: AppNode; config: TConfig; - inputs: Expand>; + inputs: Expand>; }): Expand>; // (undocumented) - id: string; - // (undocumented) inputs?: TInputs; // (undocumented) + kind?: string; + // (undocumented) + name?: string; + // (undocumented) + namespace?: string; + // (undocumented) output: TOutput; } @@ -383,14 +586,48 @@ export function createExternalRouteRef< // @public export function createNavItemExtension(options: { - id: string; + namespace?: string; + name?: string; routeRef: RouteRef; title: string; icon: IconComponent_2; -}): Extension<{ +}): ExtensionDefinition<{ title: string; }>; +// @public (undocumented) +export namespace createNavItemExtension { + const // (undocumented) + targetDataRef: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent_2; + routeRef: RouteRef; + }, + {} + >; +} + +// @public +export function createNavLogoExtension(options: { + name?: string; + namespace?: string; + logoIcon: JSX.Element; + logoFull: JSX.Element; +}): ExtensionDefinition; + +// @public (undocumented) +export namespace createNavLogoExtension { + const // (undocumented) + logoElementsDataRef: ConfigurableExtensionDataRef< + { + logoIcon?: JSX.Element | undefined; + logoFull?: JSX.Element | undefined; + }, + {} + >; +} + // @public export function createPageExtension< TConfig extends { @@ -406,7 +643,8 @@ export function createPageExtension< configSchema: PortableSchema; } ) & { - id: string; + namespace?: string; + name?: string; attachTo?: { id: string; input: string; @@ -416,10 +654,10 @@ export function createPageExtension< routeRef?: RouteRef; loader: (options: { config: TConfig; - inputs: Expand>; + inputs: Expand>; }) => Promise; }, -): Extension; +): ExtensionDefinition; // @public (undocumented) export function createPlugin< @@ -449,11 +687,73 @@ export function createRouteRef< } >; +// @public +export function createRouterExtension< + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options: { + namespace?: string; + name?: string; + attachTo?: { + id: string; + input: string; + }; + configSchema?: PortableSchema; + disabled?: boolean; + inputs?: TInputs; + Component: ComponentType< + PropsWithChildren<{ + inputs: Expand>; + config: TConfig; + }> + >; +}): ExtensionDefinition; + +// @public (undocumented) +export namespace createRouterExtension { + const // (undocumented) + componentDataRef: ConfigurableExtensionDataRef< + React_2.ComponentType<{ + children?: React_2.ReactNode; + }>, + {} + >; +} + // @public (undocumented) export function createSchemaFromZod( schemaCreator: (zImpl: typeof z) => ZodSchema, ): PortableSchema; +// @public (undocumented) +export function createSignInPageExtension< + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options: { + namespace?: string; + name?: string; + attachTo?: { + id: string; + input: string; + }; + configSchema?: PortableSchema; + disabled?: boolean; + inputs?: TInputs; + loader: (options: { + config: TConfig; + inputs: Expand>; + }) => Promise>; +}): ExtensionDefinition; + +// @public (undocumented) +export namespace createSignInPageExtension { + const // (undocumented) + componentDataRef: ConfigurableExtensionDataRef< + React_2.ComponentType, + {} + >; +} + // @public export function createSubRouteRef< Path extends string, @@ -464,7 +764,43 @@ export function createSubRouteRef< }): MakeSubRouteRef, ParentParams>; // @public (undocumented) -export function createThemeExtension(theme: AppTheme): Extension; +export function createThemeExtension( + theme: AppTheme, +): ExtensionDefinition; + +// @public (undocumented) +export namespace createThemeExtension { + const // (undocumented) + themeDataRef: ConfigurableExtensionDataRef; +} + +// @public (undocumented) +export function createTranslationExtension(options: { + name?: string; + resource: TranslationResource | TranslationMessages; +}): ExtensionDefinition; + +// @public (undocumented) +export namespace createTranslationExtension { + const // (undocumented) + translationDataRef: ConfigurableExtensionDataRef< + | TranslationResource + | TranslationMessages< + string, + { + [x: string]: string; + }, + boolean + >, + {} + >; +} + +export { createTranslationMessages }; + +export { createTranslationRef }; + +export { createTranslationResource }; export { DiscoveryApi }; @@ -483,29 +819,16 @@ export interface Extension { // (undocumented) $$type: '@backstage/Extension'; // (undocumented) - attachTo: { + readonly attachTo: { id: string; input: string; }; // (undocumented) - configSchema?: PortableSchema; + readonly configSchema?: PortableSchema; // (undocumented) - disabled: boolean; + readonly disabled: boolean; // (undocumented) - factory(options: { - source?: BackstagePlugin; - config: TConfig; - inputs: Record< - string, - undefined | Record | Array> - >; - }): ExtensionDataValues; - // (undocumented) - id: string; - // (undocumented) - inputs: AnyExtensionInputMap; - // (undocumented) - output: AnyExtensionDataMap; + readonly id: string; } // @public (undocumented) @@ -518,11 +841,9 @@ export interface ExtensionBoundaryProps { // (undocumented) children: ReactNode; // (undocumented) - id: string; + node: AppNode; // (undocumented) routable?: boolean; - // (undocumented) - source?: BackstagePlugin; } // @public (undocumented) @@ -553,6 +874,27 @@ export type ExtensionDataValues = { : never]?: TExtensionData[DataName]['T']; }; +// @public (undocumented) +export interface ExtensionDefinition { + // (undocumented) + $$type: '@backstage/ExtensionDefinition'; + // (undocumented) + readonly attachTo: { + id: string; + input: string; + }; + // (undocumented) + readonly configSchema?: PortableSchema; + // (undocumented) + readonly disabled: boolean; + // (undocumented) + readonly kind?: string; + // (undocumented) + readonly name?: string; + // (undocumented) + readonly namespace?: string; +} + // @public (undocumented) export interface ExtensionInput< TExtensionData extends AnyExtensionDataMap, @@ -569,31 +911,18 @@ export interface ExtensionInput< extensionData: TExtensionData; } -// @public -export type ExtensionInputValues< - TInputs extends { - [name in string]: ExtensionInput; - }, -> = { - [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton'] - ? Array>> - : false extends TInputs[InputName]['config']['optional'] - ? Expand> - : Expand< - ExtensionDataValues | undefined - >; -}; - // @public (undocumented) export interface ExtensionOverrides { // (undocumented) - $$type: '@backstage/ExtensionOverrides'; + readonly $$type: '@backstage/ExtensionOverrides'; } // @public (undocumented) export interface ExtensionOverridesOptions { // (undocumented) - extensions: Extension[]; + extensions: ExtensionDefinition[]; + // (undocumented) + featureFlags?: FeatureFlagConfig[]; } // @public @@ -611,6 +940,11 @@ export interface ExternalRouteRef< export { FeatureFlag }; +// @public +export type FeatureFlagConfig = { + name: string; +}; + export { FeatureFlagsApi }; export { featureFlagsApiRef }; @@ -623,6 +957,9 @@ export { FetchApi }; export { fetchApiRef }; +// @public (undocumented) +export type FrontendFeature = BackstagePlugin | ExtensionOverrides; + export { githubAuthApiRef }; export { gitlabAuthApiRef }; @@ -639,19 +976,23 @@ export type IconComponent = ComponentType< } >; +// @public +export interface IconsApi { + // (undocumented) + getIcon(key: string): IconComponent | undefined; + // (undocumented) + listIconKeys(): string[]; +} + +// @public +export const iconsApiRef: ApiRef; + export { IdentityApi }; export { identityApiRef }; export { microsoftAuthApiRef }; -// @public (undocumented) -export type NavTarget = { - title: string; - icon: IconComponent_2; - routeRef: RouteRef; -}; - export { OAuthApi }; export { OAuthRequestApi }; @@ -678,10 +1019,12 @@ export interface PluginOptions< ExternalRoutes extends AnyExternalRoutes, > { // (undocumented) - extensions?: Extension[]; + extensions?: ExtensionDefinition[]; // (undocumented) externalRoutes?: ExternalRoutes; // (undocumented) + featureFlags?: FeatureFlagConfig[]; + // (undocumented) id: string; // (undocumented) routes?: Routes; @@ -697,6 +1040,28 @@ export { ProfileInfo }; export { ProfileInfoApi }; +// @public +export type ResolvedExtensionInput = + { + node: AppNode; + output: ExtensionDataValues; + }; + +// @public +export type ResolvedExtensionInputs< + TInputs extends { + [name in string]: ExtensionInput; + }, +> = { + [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton'] + ? Array>> + : false extends TInputs[InputName]['config']['optional'] + ? Expand> + : Expand< + ResolvedExtensionInput | undefined + >; +}; + // @public export type RouteFunc = ( ...[params]: TParams extends undefined @@ -714,6 +1079,26 @@ export interface RouteRef< readonly T: TParams; } +// @public (undocumented) +export interface RouteResolutionApi { + // (undocumented) + resolve( + anyRouteRef: + | RouteRef + | SubRouteRef + | ExternalRouteRef, + options?: RouteResolutionApiResolveOptions, + ): RouteFunc | undefined; +} + +// @public +export const routeResolutionApiRef: ApiRef; + +// @public (undocumented) +export type RouteResolutionApiResolveOptions = { + sourcePath?: string; +}; + export { SessionApi }; export { SessionState }; @@ -736,12 +1121,32 @@ export interface SubRouteRef< readonly T: TParams; } +export { TranslationMessages }; + +export { TranslationMessagesOptions }; + +export { TranslationRef }; + +export { TranslationRefOptions }; + +export { TranslationResource }; + +export { TranslationResourceOptions }; + export { TypesToApiRefs }; +// @public +export function useAnalytics(): AnalyticsTracker; + export { useApi }; export { useApiHolder }; +// @public +export function useComponentRef( + ref: ComponentRef, +): ComponentType; + // @public export function useRouteRef< TOptional extends boolean, @@ -760,5 +1165,7 @@ export function useRouteRefParams( _routeRef: RouteRef | SubRouteRef, ): Params; +export { useTranslationRef }; + export { withApis }; ``` diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 45888aff39..1d97626cc8 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-plugin-api", - "version": "0.3.1-next.0", + "version": "0.5.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,6 +22,21 @@ "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack" }, + "dependencies": { + "@backstage/core-components": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", + "@backstage/types": "workspace:^", + "@backstage/version-bridge": "workspace:^", + "@material-ui/core": "^4.12.4", + "@types/react": "^16.13.1 || ^17.0.0", + "lodash": "^4.17.21", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.21.4" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-router-dom": "6.0.0-beta.0 || ^6.3.0" + }, "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/frontend-app-api": "workspace:^", @@ -33,21 +48,5 @@ }, "files": [ "dist" - ], - "peerDependencies": { - "react": "^16.13.1 || ^17.0.0 || ^18.0.0", - "react-router-dom": "6.0.0-beta.0 || ^6.3.0" - }, - "dependencies": { - "@backstage/config": "workspace:^", - "@backstage/core-components": "workspace:^", - "@backstage/core-plugin-api": "workspace:^", - "@backstage/types": "workspace:^", - "@backstage/version-bridge": "workspace:^", - "@material-ui/core": "^4.12.4", - "@types/react": "^16.13.1 || ^17.0.0", - "lodash": "^4.17.21", - "zod": "^3.21.4", - "zod-to-json-schema": "^3.21.4" - } + ] } diff --git a/packages/frontend-plugin-api/src/analytics/AnalyticsContext.test.tsx b/packages/frontend-plugin-api/src/analytics/AnalyticsContext.test.tsx new file mode 100644 index 0000000000..8e78bdc8c4 --- /dev/null +++ b/packages/frontend-plugin-api/src/analytics/AnalyticsContext.test.tsx @@ -0,0 +1,82 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; +import { AnalyticsContext, useAnalyticsContext } from './AnalyticsContext'; + +const AnalyticsSpy = () => { + const context = useAnalyticsContext(); + return ( + <> +
{context.pluginId}
+
{context.extensionId}
+
{context.custom}
+ + ); +}; + +describe('AnalyticsContext', () => { + describe('useAnalyticsContext', () => { + it('returns default values', () => { + const { result } = renderHook(() => useAnalyticsContext()); + expect(result.current).toEqual({ + extensionId: 'App', + pluginId: 'root', + }); + }); + }); + + describe('AnalyticsContext', () => { + it('uses default analytics context', () => { + const result = render( + + + , + ); + + expect(result.getByTestId('extension-id')).toHaveTextContent('App'); + expect(result.getByTestId('plugin-id')).toHaveTextContent('root'); + }); + + it('uses provided analytics context', () => { + const result = render( + + + , + ); + + expect(result.getByTestId('extension-id')).toHaveTextContent('App'); + expect(result.getByTestId('plugin-id')).toHaveTextContent('custom'); + }); + + it('uses nested analytics context', () => { + const result = render( + + + + + , + ); + + expect(result.getByTestId('extension-id')).toHaveTextContent( + 'AnalyticsSpy', + ); + expect(result.getByTestId('plugin-id')).toHaveTextContent('custom'); + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/analytics/AnalyticsContext.tsx b/packages/frontend-plugin-api/src/analytics/AnalyticsContext.tsx new file mode 100644 index 0000000000..10ef475803 --- /dev/null +++ b/packages/frontend-plugin-api/src/analytics/AnalyticsContext.tsx @@ -0,0 +1,108 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + createVersionedContext, + createVersionedValueMap, +} from '@backstage/version-bridge'; +import React, { ReactNode, useContext } from 'react'; +import { AnalyticsContextValue } from './types'; + +const AnalyticsReactContext = createVersionedContext<{ + 1: AnalyticsContextValue; +}>('analytics-context'); + +/** + * A "private" (to this package) hook that enables context inheritance and a + * way to read Analytics Context values at event capture-time. + * + * @internal + */ +export const useAnalyticsContext = (): AnalyticsContextValue => { + const theContext = useContext(AnalyticsReactContext); + + // Provide a default value if no value exists. + if (theContext === undefined) { + return { + pluginId: 'root', + extensionId: 'App', + }; + } + + // This should probably never happen, but check for it. + const theValue = theContext.atVersion(1); + if (theValue === undefined) { + throw new Error('No context found for version 1.'); + } + + return theValue; +}; + +/** + * Provides components in the child react tree an Analytics Context, ensuring + * all analytics events captured within the context have relevant attributes. + * + * @remarks + * + * Analytics contexts are additive, meaning the context ultimately emitted with + * an event is the combination of all contexts in the parent tree. + * + * @public + */ +export const AnalyticsContext = (options: { + attributes: Partial; + children: ReactNode; +}) => { + const { attributes, children } = options; + + const parentValues = useAnalyticsContext(); + const combinedValue = { + ...parentValues, + ...attributes, + }; + + const versionedCombinedValue = createVersionedValueMap({ 1: combinedValue }); + return ( + + {children} + + ); +}; + +/** + * Returns an HOC wrapping the provided component in an Analytics context with + * the given values. + * + * @param Component - Component to be wrapped with analytics context attributes + * @param values - Analytics context key/value pairs. + * @internal + */ +export function withAnalyticsContext( + Component: React.ComponentType, + values: AnalyticsContextValue, +) { + const ComponentWithAnalyticsContext = (props: TProps) => { + return ( + + + + ); + }; + ComponentWithAnalyticsContext.displayName = `WithAnalyticsContext(${ + Component.displayName || Component.name || 'Component' + })`; + return ComponentWithAnalyticsContext; +} diff --git a/packages/frontend-plugin-api/src/analytics/Tracker.test.ts b/packages/frontend-plugin-api/src/analytics/Tracker.test.ts new file mode 100644 index 0000000000..f1670cf9c4 --- /dev/null +++ b/packages/frontend-plugin-api/src/analytics/Tracker.test.ts @@ -0,0 +1,285 @@ +/* + * 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 { getOrCreateGlobalSingleton } from '@backstage/version-bridge'; +import { Tracker, routableExtensionRenderedEvent } from './Tracker'; + +describe('Tracker', () => { + const defaultContext = { + pluginId: 'root', + extensionId: 'App', + }; + const globalEvents = getOrCreateGlobalSingleton( + 'core-plugin-api:analytics-tracker-events', + () => ({}), + ); + const analyticsApiSpy = { + captureEvent: jest.fn(), + }; + let trackerUnderTest: Tracker; + + beforeEach(() => { + // Reset mocks and global state + jest.resetAllMocks(); + globalEvents.mostRecentGatheredNavigation = undefined; + globalEvents.mostRecentRoutableExtensionRender = undefined; + + // Set up a new tracker to test. + trackerUnderTest = new Tracker(analyticsApiSpy); + }); + + it('captures simple event with default context', () => { + trackerUnderTest.captureEvent('click', 'test 1'); + + expect(analyticsApiSpy.captureEvent).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'click', + subject: 'test 1', + context: defaultContext, + }), + ); + }); + + it('captures simple event with set context', () => { + trackerUnderTest.setContext({ + pluginId: 'catalog', + extensionId: 'App', + }); + trackerUnderTest.captureEvent('click', 'test 2', { + value: 2, + attributes: { to: '/page-2' }, + }); + + expect(analyticsApiSpy.captureEvent).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'click', + subject: 'test 2', + value: 2, + attributes: { to: '/page-2' }, + context: { + pluginId: 'catalog', + extensionId: 'App', + }, + }), + ); + }); + + describe('accurate navigate events', () => { + it('never captures _routeNodeType context key on navigate event', () => { + trackerUnderTest.setContext({ + pluginId: 'catalog', + extensionId: 'App', + }); + trackerUnderTest.captureEvent('navigate', '/page-3'); + + const receivedContext = + analyticsApiSpy.captureEvent.mock.calls[0][0].context; + expect(receivedContext.pluginId).toBe('catalog'); + expect(receivedContext._routeNodeType).toBe(undefined); + }); + + it('never immediately captures navigate event with _routeNodeType "gathered"', () => { + trackerUnderTest.setContext({ + ...defaultContext, + }); + trackerUnderTest.captureEvent('navigate', '/page-4'); + + expect(analyticsApiSpy.captureEvent).not.toHaveBeenCalled(); + }); + + it('never captures "routable-extension-rendered" events', () => { + trackerUnderTest.captureEvent(routableExtensionRenderedEvent, ''); + + expect(analyticsApiSpy.captureEvent).not.toHaveBeenCalled(); + }); + + it('captures deferred navigate event with expected context', () => { + // User navigates to a gathered mountpoint with multiple plugins + trackerUnderTest.setContext({ + ...defaultContext, + }); + trackerUnderTest.captureEvent('navigate', '/page-5'); + + // A routable extension is rendered with specific plugin context + trackerUnderTest.setContext({ + pluginId: 'some-plugin', + extensionId: 'App', + }); + trackerUnderTest.captureEvent(routableExtensionRenderedEvent, ''); + + // A non-navigate event + trackerUnderTest.captureEvent('click', 'test 5'); + + expect(analyticsApiSpy.captureEvent).toHaveBeenCalledTimes(2); + expect(analyticsApiSpy.captureEvent).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + action: 'navigate', + subject: '/page-5', + context: { + pluginId: 'some-plugin', + extensionId: 'App', + }, + }), + ); + expect(analyticsApiSpy.captureEvent).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + action: 'click', + subject: 'test 5', + context: { + pluginId: 'some-plugin', + extensionId: 'App', + }, + }), + ); + }); + + it('captures deferred navigate event with expected context when second event is also deferrable', () => { + // User navigates to a gathered mountpoint with multiple plugins + trackerUnderTest.setContext({ + ...defaultContext, + }); + trackerUnderTest.captureEvent('navigate', '/page-6'); + + // A routable extension is rendered with specific plugin context + trackerUnderTest.setContext({ + pluginId: 'another-plugin', + extensionId: 'App', + }); + trackerUnderTest.captureEvent(routableExtensionRenderedEvent, ''); + + // User navigates to another gathered mountpoint with multiple plugins + trackerUnderTest.setContext({ + ...defaultContext, + }); + trackerUnderTest.captureEvent('navigate', '/page-6-2'); + + expect(analyticsApiSpy.captureEvent).toHaveBeenCalledTimes(1); + expect(analyticsApiSpy.captureEvent).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + action: 'navigate', + subject: '/page-6', + context: { + pluginId: 'another-plugin', + extensionId: 'App', + }, + }), + ); + }); + + it('captures deferred navigate event with default context when no extension is rendered in between', () => { + // User navigates to a gathered mountpoint with multiple plugins + trackerUnderTest.setContext({ + ...defaultContext, + }); + trackerUnderTest.captureEvent('navigate', '/page-7'); + + // A non-navigate event with no routable extension render in between + trackerUnderTest.captureEvent('click', 'test 7'); + + expect(analyticsApiSpy.captureEvent).toHaveBeenCalledTimes(2); + expect(analyticsApiSpy.captureEvent).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + action: 'navigate', + subject: '/page-7', + context: defaultContext, + }), + ); + expect(analyticsApiSpy.captureEvent).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + action: 'click', + subject: 'test 7', + context: defaultContext, + }), + ); + }); + + it('captures deferred navigate event with expected context across separate trackers', () => { + // User navigates to a gathered mountpoint with multiple plugins + trackerUnderTest.setContext({ + ...defaultContext, + }); + trackerUnderTest.captureEvent('navigate', '/page-8'); + + // A routable extension is rendered with specific plugin context and + // captured via a separate tracker instance. + const anotherTracker = new Tracker(analyticsApiSpy, { + pluginId: 'the-plugin', + extensionId: 'App', + }); + anotherTracker.captureEvent(routableExtensionRenderedEvent, ''); + + // A non-navigate event is captured + const aThirdTracker = new Tracker(analyticsApiSpy); + aThirdTracker.captureEvent('click', 'test 8'); + + expect(analyticsApiSpy.captureEvent).toHaveBeenCalledTimes(2); + expect(analyticsApiSpy.captureEvent).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + action: 'navigate', + subject: '/page-8', + context: { + pluginId: 'the-plugin', + extensionId: 'App', + }, + }), + ); + expect(analyticsApiSpy.captureEvent).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + action: 'click', + subject: 'test 8', + }), + ); + }); + + it('replaces extension context with "App" when capturing deferred events', () => { + // User navigates to a gathered mountpoint with multiple plugins + trackerUnderTest.setContext({ + ...defaultContext, + }); + trackerUnderTest.captureEvent('navigate', '/page-9'); + + // A routable extension is rendered with specific plugin context + trackerUnderTest.setContext({ + pluginId: 'very-plugin', + extensionId: 'ShouldBeReplaced', + }); + trackerUnderTest.captureEvent(routableExtensionRenderedEvent, ''); + + // A non-navigate event + trackerUnderTest.captureEvent('click', 'test 9'); + + // Extension context should have been replaced with just "App" + expect(analyticsApiSpy.captureEvent).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + action: 'navigate', + subject: '/page-9', + context: { + pluginId: 'very-plugin', + extensionId: 'App', + }, + }), + ); + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/analytics/Tracker.ts b/packages/frontend-plugin-api/src/analytics/Tracker.ts new file mode 100644 index 0000000000..dd0d770354 --- /dev/null +++ b/packages/frontend-plugin-api/src/analytics/Tracker.ts @@ -0,0 +1,175 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getOrCreateGlobalSingleton } from '@backstage/version-bridge'; +import { + AnalyticsApi, + AnalyticsEventAttributes, + AnalyticsTracker, +} from '../apis'; +import { AnalyticsContextValue } from './'; + +type TempGlobalEvents = { + /** + * Stores the most recent "gathered" mountpoint navigation. + */ + mostRecentGatheredNavigation?: { + action: string; + subject: string; + value?: number; + attributes?: AnalyticsEventAttributes; + context: AnalyticsContextValue; + }; + /** + * Stores the most recent routable extension render. + */ + mostRecentRoutableExtensionRender?: { + context: AnalyticsContextValue; + }; + /** + * Tracks whether or not a beforeunload event listener has already been + * registered. + */ + beforeUnloadRegistered: boolean; +}; + +/** + * Temporary global store for select event data. Used to make `navigate` events + * more accurate when gathered mountpoints are used. + */ +const globalEvents = getOrCreateGlobalSingleton( + 'core-plugin-api:analytics-tracker-events', + () => ({ + mostRecentGatheredNavigation: undefined, + mostRecentRoutableExtensionRender: undefined, + beforeUnloadRegistered: false, + }), +); + +/** + * Internal-only event representing when a routable extension is rendered. + */ +export const routableExtensionRenderedEvent = '_ROUTABLE-EXTENSION-RENDERED'; + +export class Tracker implements AnalyticsTracker { + constructor( + private readonly analyticsApi: AnalyticsApi, + private context: AnalyticsContextValue = { + pluginId: 'root', + extensionId: 'App', + }, + ) { + // Only register a single beforeunload event across all trackers. + if (!globalEvents.beforeUnloadRegistered) { + // Before the page unloads, attempt to capture any deferred navigation + // events that haven't yet been captured. + addEventListener( + 'beforeunload', + () => { + if (globalEvents.mostRecentGatheredNavigation) { + this.analyticsApi.captureEvent({ + ...globalEvents.mostRecentGatheredNavigation, + ...globalEvents.mostRecentRoutableExtensionRender, + }); + globalEvents.mostRecentGatheredNavigation = undefined; + globalEvents.mostRecentRoutableExtensionRender = undefined; + } + }, + { once: true, passive: true }, + ); + + // Prevent duplicate handlers from being registered. + globalEvents.beforeUnloadRegistered = true; + } + } + + setContext(context: AnalyticsContextValue) { + this.context = context; + } + + captureEvent( + action: string, + subject: string, + { + value, + attributes, + }: { value?: number; attributes?: AnalyticsEventAttributes } = {}, + ) { + // Never pass internal "_routeNodeType" context value. + const context = this.context; + + // Never fire the special "_routable-extension-rendered" internal event. + if (action === routableExtensionRenderedEvent) { + // But keep track of it if we're delaying a `navigate` event for a + // a gathered route node type. + if (globalEvents.mostRecentGatheredNavigation) { + globalEvents.mostRecentRoutableExtensionRender = { + context: { + ...context, + extensionId: 'App', + }, + }; + } + return; + } + + // If we are about to fire a real event, and we have an un-fired gathered + // mountpoint navigation on the global store, we need to fire the navigate + // event first, so this real event happens accurately after the navigation. + if (globalEvents.mostRecentGatheredNavigation) { + try { + this.analyticsApi.captureEvent({ + ...globalEvents.mostRecentGatheredNavigation, + ...globalEvents.mostRecentRoutableExtensionRender, + }); + } catch (e) { + // eslint-disable-next-line no-console + console.warn('Error during analytics event capture. %o', e); + } + + // Clear the global stores. + globalEvents.mostRecentGatheredNavigation = undefined; + globalEvents.mostRecentRoutableExtensionRender = undefined; + } + + // Never directly fire a navigation event on a gathered route with default + // contextual details. + if (action === 'navigate' && context.pluginId === 'root') { + // Instead, set it on the global store. + globalEvents.mostRecentGatheredNavigation = { + action, + subject, + value, + attributes, + context, + }; + return; + } + + try { + this.analyticsApi.captureEvent({ + action, + subject, + value, + attributes, + context, + }); + } catch (e) { + // eslint-disable-next-line no-console + console.warn('Error during analytics event capture. %o', e); + } + } +} diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/index.ts b/packages/frontend-plugin-api/src/analytics/index.ts similarity index 72% rename from plugins/kubernetes-react/src/utils/clusterLinks/index.ts rename to packages/frontend-plugin-api/src/analytics/index.ts index 90a30e8277..651e8e105e 100644 --- a/plugins/kubernetes-react/src/utils/clusterLinks/index.ts +++ b/packages/frontend-plugin-api/src/analytics/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,6 @@ * limitations under the License. */ -export { - formatClusterLink, - type FormatClusterLinkOptions, -} from './formatClusterLink'; -export { clusterLinksFormatters } from './formatters'; +export { AnalyticsContext } from './AnalyticsContext'; +export type { AnalyticsContextValue, CommonAnalyticsContext } from './types'; +export { useAnalytics } from './useAnalytics'; diff --git a/packages/frontend-plugin-api/src/analytics/types.ts b/packages/frontend-plugin-api/src/analytics/types.ts new file mode 100644 index 0000000000..21347e2fc7 --- /dev/null +++ b/packages/frontend-plugin-api/src/analytics/types.ts @@ -0,0 +1,44 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Common analytics context attributes. + * + * @public + */ +export type CommonAnalyticsContext = { + /** + * The nearest known parent plugin where the event was captured. + */ + pluginId: string; + + /** + * The nearest known parent extension where the event was captured. + */ + /** + * The nearest known parent extension where the event was captured. + */ + extensionId: string; +}; + +/** + * Analytics context envelope. + * + * @public + */ +export type AnalyticsContextValue = CommonAnalyticsContext & { + [param in string]: string | boolean | number | undefined; +}; diff --git a/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx b/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx new file mode 100644 index 0000000000..beb16b1fbb --- /dev/null +++ b/packages/frontend-plugin-api/src/analytics/useAnalytics.test.tsx @@ -0,0 +1,61 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { renderHook } from '@testing-library/react'; +import { useAnalytics } from './useAnalytics'; +import { analyticsApiRef } from '@backstage/core-plugin-api'; +import { TestApiProvider } from '@backstage/test-utils'; +import React from 'react'; + +describe('useAnalytics', () => { + it('returns tracker with no implementation defined', () => { + // useApi throws an error because the Analytics API is not implemented + // But the result should still have a captureEvent method. + const { result } = renderHook(() => useAnalytics(), {}); + expect(result.current.captureEvent).toBeDefined(); + }); + + it('returns tracker from defined analytics api', () => { + const captureEvent = jest.fn(); + + // Calling the captureEvent method of the underlying implementation should + // pass along the given event as well as the default context. + const { result } = renderHook(() => useAnalytics(), { + wrapper: ({ children }) => ( + // Simulate useApi returning a valid tracker. + + {children} + + ), + }); + result.current.captureEvent('an action', 'a subject', { + value: 42, + attributes: { some: 'value' }, + }); + expect(captureEvent).toHaveBeenCalledWith({ + action: 'an action', + subject: 'a subject', + value: 42, + attributes: { + some: 'value', + }, + context: { + extensionId: 'App', + pluginId: 'root', + }, + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx b/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx new file mode 100644 index 0000000000..f3b5998de4 --- /dev/null +++ b/packages/frontend-plugin-api/src/analytics/useAnalytics.tsx @@ -0,0 +1,57 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useApi } from '@backstage/core-plugin-api'; +import { useAnalyticsContext } from './AnalyticsContext'; +import { analyticsApiRef, AnalyticsTracker, AnalyticsApi } from '../apis'; +import { useRef } from 'react'; +import { Tracker } from './Tracker'; + +function useAnalyticsApi(): AnalyticsApi { + try { + return useApi(analyticsApiRef); + } catch { + return { captureEvent: () => {} }; + } +} + +/** + * Gets a pre-configured analytics tracker. + * + * @public + */ +export function useAnalytics(): AnalyticsTracker { + const trackerRef = useRef(null); + const context = useAnalyticsContext(); + // Our goal is to make this API truly optional for any/all consuming code + // (including tests). This hook runs last to ensure hook order is, as much as + // possible, maintained. + const analyticsApi = useAnalyticsApi(); + + function getTracker(): Tracker { + if (trackerRef.current === null) { + trackerRef.current = new Tracker(analyticsApi); + } + return trackerRef.current; + } + + const tracker = getTracker(); + // this is not ideal, but it allows to memoize the tracker + // without explicitly set the context as dependency. + tracker.setContext(context); + + return tracker; +} diff --git a/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts new file mode 100644 index 0000000000..15b09482b7 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts @@ -0,0 +1,131 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; +import { AnalyticsContextValue } from '../../analytics/types'; + +/** + * Represents an event worth tracking in an analytics system that could inform + * how users of a Backstage instance are using its features. + * + * @public + */ +export type AnalyticsEvent = { + /** + * A string that identifies the event being tracked by the type of action the + * event represents. Be careful not to encode extra metadata in this string + * that should instead be placed in the Analytics Context or attributes. + * Examples include: + * + * - view + * - click + * - filter + * - search + * - hover + * - scroll + */ + action: string; + + /** + * A string that uniquely identifies the object that the action is being + * taken on. Examples include: + * + * - The path of the page viewed + * - The url of the link clicked + * - The value that was filtered by + * - The text that was searched for + */ + subject: string; + + /** + * An optional numeric value relevant to the event that could be aggregated + * by analytics tools. Examples include: + * + * - The index or position of the clicked element in an ordered list + * - The percentage of an element that has been scrolled through + * - The amount of time that has elapsed since a fixed point + * - A satisfaction score on a fixed scale + */ + value?: number; + + /** + * Optional, additional attributes (representing dimensions or metrics) + * specific to the event that could be forwarded on to analytics systems. + */ + attributes?: AnalyticsEventAttributes; + + /** + * Contextual metadata relating to where the event was captured and by whom. + * This could include information about the route, plugin, or extension in + * which an event was captured. + */ + context: AnalyticsContextValue; +}; + +/** + * A structure allowing other arbitrary metadata to be provided by analytics + * event emitters. + * + * @public + */ +export type AnalyticsEventAttributes = { + [attribute in string]: string | boolean | number; +}; + +/** + * Represents a tracker with methods that can be called to track events in a + * configured analytics service. + * + * @public + */ +export type AnalyticsTracker = { + captureEvent: ( + action: string, + subject: string, + options?: { + value?: number; + attributes?: AnalyticsEventAttributes; + }, + ) => void; +}; + +/** + * The Analytics API is used to track user behavior in a Backstage instance. + * + * @remarks + * + * To instrument your App or Plugin, retrieve an analytics tracker using the + * useAnalytics() hook. This will return a pre-configured AnalyticsTracker + * with relevant methods for instrumentation. + * + * @public + */ +export type AnalyticsApi = { + /** + * Primary event handler responsible for compiling and forwarding events to + * an analytics system. + */ + captureEvent(event: AnalyticsEvent): void; +}; + +/** + * The API reference of {@link AnalyticsApi}. + * + * @public + */ +export const analyticsApiRef: ApiRef = createApiRef({ + id: 'core.analytics', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts new file mode 100644 index 0000000000..c5e9d2c44f --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts @@ -0,0 +1,49 @@ +/* + * 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 { ComponentType } from 'react'; +import { createApiRef, useApi } from '@backstage/core-plugin-api'; +import { ComponentRef } from '../../components'; + +/** + * API for looking up components based on component refs. + * + * @public + */ +export interface ComponentsApi { + // TODO: Should component refs also provide the default implementation so that we're guaranteed to get a component? + getComponent(ref: ComponentRef): ComponentType; +} + +/** + * The `ApiRef` of {@link ComponentsApi}. + * + * @public + */ +export const componentsApiRef = createApiRef({ + id: 'core.components', +}); + +/** + * @public + * Returns the component associated with the given ref. + */ +export function useComponentRef( + ref: ComponentRef, +): ComponentType { + const componentsApi = useApi(componentsApiRef); + return componentsApi.getComponent(ref); +} diff --git a/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts new file mode 100644 index 0000000000..aa5b7f4fac --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/definitions/IconsApi.ts @@ -0,0 +1,38 @@ +/* + * 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 { createApiRef } from '@backstage/core-plugin-api'; +import { IconComponent } from '../../icons'; + +/** + * API for accessing app icons. + * + * @public + */ +export interface IconsApi { + getIcon(key: string): IconComponent | undefined; + + listIconKeys(): string[]; +} + +/** + * The `ApiRef` of {@link IconsApi}. + * + * @public + */ +export const iconsApiRef = createApiRef({ + id: 'core.icons', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts b/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts new file mode 100644 index 0000000000..06388cc966 --- /dev/null +++ b/packages/frontend-plugin-api/src/apis/definitions/RouteResolutionApi.ts @@ -0,0 +1,75 @@ +/* + * Copyright 2024 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 { + AnyRouteRefParams, + RouteRef, + SubRouteRef, + ExternalRouteRef, +} from '../../routing'; +import { createApiRef } from '@backstage/core-plugin-api'; + +/** + * TS magic for handling route parameters. + * + * @remarks + * + * The extra TS magic here is to require a single params argument if the RouteRef + * had at least one param defined, but require 0 arguments if there are no params defined. + * Without this we'd have to pass in empty object to all parameter-less RouteRefs + * just to make TypeScript happy, or we would have to make the argument optional in + * which case you might forget to pass it in when it is actually required. + * + * @public + */ +export type RouteFunc = ( + ...[params]: TParams extends undefined + ? readonly [] + : readonly [params: TParams] +) => string; + +/** + * @public + */ +export type RouteResolutionApiResolveOptions = { + /** + * An absolute path to use as a starting point when resolving the route. + * If no path is provided the route will be resolved from the root of the app. + */ + sourcePath?: string; +}; + +/** + * @public + */ +export interface RouteResolutionApi { + resolve( + anyRouteRef: + | RouteRef + | SubRouteRef + | ExternalRouteRef, + options?: RouteResolutionApiResolveOptions, + ): RouteFunc | undefined; +} + +/** + * The `ApiRef` of {@link RouteResolutionApi}. + * + * @public + */ +export const routeResolutionApiRef = createApiRef({ + id: 'core.route-resolution', +}); diff --git a/packages/frontend-plugin-api/src/apis/definitions/index.ts b/packages/frontend-plugin-api/src/apis/definitions/index.ts index 420942cb19..d766a8dfdf 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/index.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/index.ts @@ -34,11 +34,15 @@ export * from './auth'; export * from './AlertApi'; export * from './AppThemeApi'; +export * from './ComponentsApi'; export * from './ConfigApi'; export * from './DiscoveryApi'; export * from './ErrorApi'; export * from './FeatureFlagsApi'; export * from './FetchApi'; +export * from './IconsApi'; export * from './IdentityApi'; export * from './OAuthRequestApi'; +export * from './RouteResolutionApi'; export * from './StorageApi'; +export * from './AnalyticsApi'; diff --git a/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx b/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx index 1191b75a1d..6b457fadc9 100644 --- a/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ErrorBoundary.tsx @@ -14,35 +14,14 @@ * limitations under the License. */ -import React, { Component, PropsWithChildren } from 'react'; -// TODO: Dependency on MUI should be removed from core packages -import { Button } from '@material-ui/core'; -import { ErrorPanel } from '@backstage/core-components'; +import React, { Component, ComponentType, PropsWithChildren } from 'react'; import { BackstagePlugin } from '../wiring'; +import { CoreErrorBoundaryFallbackProps } from '../types'; -type DefaultErrorBoundaryFallbackProps = PropsWithChildren<{ +type ErrorBoundaryProps = PropsWithChildren<{ plugin?: BackstagePlugin; - error: Error; - resetError: () => void; + Fallback: ComponentType; }>; - -const DefaultErrorBoundaryFallback = ({ - plugin, - error, - resetError, -}: DefaultErrorBoundaryFallbackProps) => { - const title = `Error in ${plugin?.id}`; - - return ( - - - - ); -}; - -type ErrorBoundaryProps = PropsWithChildren<{ plugin?: BackstagePlugin }>; type ErrorBoundaryState = { error?: Error }; /** @internal */ @@ -62,12 +41,11 @@ export class ErrorBoundary extends Component< render() { const { error } = this.state; - const { plugin, children } = this.props; + const { plugin, children, Fallback } = this.props; if (error) { - // TODO: use a configurable error boundary fallback return ( - { - const id = 'plugin.extension'; const routeRef = createRouteRef(); return createExtension({ - id, - attachTo: { id: 'core.routes', input: 'routes' }, + name: 'test', + attachTo: { id: 'app/routes', input: 'routes' }, output: { element: coreExtensionData.reactElement, path: coreExtensionData.routePath, routeRef: coreExtensionData.routeRef.optional(), }, - factory({ source }) { + factory({ node }) { return { routeRef, path: '/', - element: ( - - {element} - - ), + element: {element}, }; }, }); @@ -88,15 +83,18 @@ describe('ExtensionBoundary', () => { ), ).render(); - await waitFor(() => - expect(analyticsApiMock.getEvents()[0]).toMatchObject({ + await waitFor(() => { + const event = analyticsApiMock + .getEvents() + .find(e => e.subject === subject); + + expect(event).toMatchObject({ action, subject, context: { - extension: 'plugin.extension', - routeRef: 'unknown', + extensionId: 'test', }, - }), - ); + }); + }); }); }); diff --git a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx index a9ea297216..9a8eb5b750 100644 --- a/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx +++ b/packages/frontend-plugin-api/src/components/ExtensionBoundary.tsx @@ -14,13 +14,18 @@ * limitations under the License. */ -import React, { PropsWithChildren, ReactNode, useEffect } from 'react'; +import React, { + PropsWithChildren, + ReactNode, + Suspense, + useEffect, +} from 'react'; import { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api'; -import { BackstagePlugin } from '../wiring'; import { ErrorBoundary } from './ErrorBoundary'; -import { ExtensionSuspense } from './ExtensionSuspense'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker'; +import { AppNode, useComponentRef } from '../apis'; +import { coreComponentRefs } from './coreComponentRefs'; type RouteTrackerProps = PropsWithChildren<{ disableTracking?: boolean; @@ -44,29 +49,32 @@ const RouteTracker = (props: RouteTrackerProps) => { /** @public */ export interface ExtensionBoundaryProps { - id: string; - source?: BackstagePlugin; + node: AppNode; routable?: boolean; children: ReactNode; } /** @public */ export function ExtensionBoundary(props: ExtensionBoundaryProps) { - const { id, source, routable, children } = props; + const { node, routable, children } = props; + + const plugin = node.spec.source; + const Progress = useComponentRef(coreComponentRefs.progress); + const fallback = useComponentRef(coreComponentRefs.errorBoundaryFallback); // Skipping "routeRef" attribute in the new system, the extension "id" should provide more insight const attributes = { - extension: id, - pluginId: source?.id, + extensionId: node.spec.id, + pluginId: node.spec.source?.id, }; return ( - - + }> + {children} - + ); } diff --git a/packages/frontend-plugin-api/src/components/coreComponentRefs.ts b/packages/frontend-plugin-api/src/components/coreComponentRefs.ts new file mode 100644 index 0000000000..64412b8710 --- /dev/null +++ b/packages/frontend-plugin-api/src/components/coreComponentRefs.ts @@ -0,0 +1,43 @@ +/* + * 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 { + CoreErrorBoundaryFallbackProps, + CoreNotFoundErrorPageProps, + CoreProgressProps, +} from '../types'; +import { createComponentRef } from './createComponentRef'; + +const coreProgressComponentRef = createComponentRef({ + id: 'core.components.progress', +}); + +const coreNotFoundErrorPageComponentRef = + createComponentRef({ + id: 'core.components.notFoundErrorPage', + }); + +const coreErrorBoundaryFallbackComponentRef = + createComponentRef({ + id: 'core.components.errorBoundaryFallback', + }); + +/** @public */ +export const coreComponentRefs = { + progress: coreProgressComponentRef, + notFoundErrorPage: coreNotFoundErrorPageComponentRef, + errorBoundaryFallback: coreErrorBoundaryFallbackComponentRef, +}; diff --git a/packages/frontend-plugin-api/src/components/createComponentRef.test.tsx b/packages/frontend-plugin-api/src/components/createComponentRef.test.tsx new file mode 100644 index 0000000000..9a84fe08fa --- /dev/null +++ b/packages/frontend-plugin-api/src/components/createComponentRef.test.tsx @@ -0,0 +1,25 @@ +/* + * 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 { createComponentRef } from './createComponentRef'; + +describe('createComponentRef', () => { + it('can be created and read', () => { + const ref = createComponentRef({ id: 'foo' }); + expect(ref.id).toBe('foo'); + expect(String(ref)).toBe('ComponentRef{id=foo}'); + }); +}); diff --git a/packages/frontend-plugin-api/src/components/createComponentRef.tsx b/packages/frontend-plugin-api/src/components/createComponentRef.tsx new file mode 100644 index 0000000000..88181dd079 --- /dev/null +++ b/packages/frontend-plugin-api/src/components/createComponentRef.tsx @@ -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. + */ + +/** @public */ +export type ComponentRef = { + id: string; + T: T; +}; + +/** @public */ +export function createComponentRef(options: { + id: string; +}): ComponentRef { + const { id } = options; + return { + id, + toString() { + return `ComponentRef{id=${id}}`; + }, + } as ComponentRef; +} diff --git a/packages/frontend-plugin-api/src/components/index.ts b/packages/frontend-plugin-api/src/components/index.ts index 7056a59271..5066144000 100644 --- a/packages/frontend-plugin-api/src/components/index.ts +++ b/packages/frontend-plugin-api/src/components/index.ts @@ -18,3 +18,5 @@ export { ExtensionBoundary, type ExtensionBoundaryProps, } from './ExtensionBoundary'; +export { coreComponentRefs } from './coreComponentRefs'; +export { createComponentRef, type ComponentRef } from './createComponentRef'; diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts index e90c0b6b85..c4d0239094 100644 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts @@ -26,14 +26,16 @@ describe('createApiExtension', () => { factory: () => ({ foo: 'bar' }), }); - const extension = createApiExtension({ - factory, - }); - - expect(extension).toEqual({ - $$type: '@backstage/Extension', - id: 'apis.test', - attachTo: { id: 'core', input: 'apis' }, + expect( + createApiExtension({ + factory, + }), + ).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'api', + namespace: 'test', + attachTo: { id: 'app', input: 'apis' }, disabled: false, configSchema: undefined, inputs: {}, @@ -45,6 +47,7 @@ describe('createApiExtension', () => { }), }, factory: expect.any(Function), + toString: expect.any(Function), }); }); @@ -65,9 +68,11 @@ describe('createApiExtension', () => { }); // boo expect(extension).toEqual({ - $$type: '@backstage/Extension', - id: 'apis.test', - attachTo: { id: 'core', input: 'apis' }, + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'api', + namespace: 'test', + attachTo: { id: 'app', input: 'apis' }, disabled: false, configSchema: undefined, inputs: {}, @@ -79,6 +84,7 @@ describe('createApiExtension', () => { }), }, factory: expect.any(Function), + toString: expect.any(Function), }); }); }); diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts index 7b6dded022..1680332960 100644 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts @@ -17,9 +17,9 @@ import { AnyApiFactory, AnyApiRef } from '@backstage/core-plugin-api'; import { PortableSchema } from '../schema'; import { - ExtensionInputValues, + ResolvedExtensionInputs, createExtension, - coreExtensionData, + createExtensionDataRef, } from '../wiring'; import { AnyExtensionInputMap } from '../wiring/createExtension'; import { Expand } from '../types'; @@ -34,7 +34,7 @@ export function createApiExtension< api: AnyApiRef; factory: (options: { config: TConfig; - inputs: Expand>; + inputs: Expand>; }) => AnyApiFactory; } | { @@ -51,12 +51,15 @@ export function createApiExtension< 'api' in options ? options.api : (factory as { api: AnyApiRef }).api; return createExtension({ - id: `apis.${apiRef.id}`, - attachTo: { id: 'core', input: 'apis' }, + kind: 'api', + // Since ApiRef IDs use a global namespace we use the namespace here in order to override + // potential plugin IDs and always end up with the format `api:` + namespace: apiRef.id, + attachTo: { id: 'app', input: 'apis' }, inputs: extensionInputs, configSchema, output: { - api: coreExtensionData.apiFactory, + api: createApiExtension.factoryDataRef, }, factory({ config, inputs }) { if (typeof factory === 'function') { @@ -66,3 +69,9 @@ export function createApiExtension< }, }); } + +/** @public */ +export namespace createApiExtension { + export const factoryDataRef = + createExtensionDataRef('core.api.factory'); +} diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx new file mode 100644 index 0000000000..c8bf6ee355 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.test.tsx @@ -0,0 +1,107 @@ +/* + * 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 { createExtensionTester } from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { createSchemaFromZod } from '../schema/createSchemaFromZod'; +import { coreExtensionData } from '../wiring/coreExtensionData'; +import { createExtension } from '../wiring/createExtension'; +import { createExtensionInput } from '../wiring/createExtensionInput'; +import { createAppRootElementExtension } from './createAppRootElementExtension'; + +describe('createAppRootElementExtension', () => { + it('works with simple options and just an element', async () => { + const extension = createAppRootElementExtension({ + element:
Hello
, + }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'app-root-element', + attachTo: { id: 'app/root', input: 'elements' }, + disabled: false, + inputs: {}, + output: { + element: expect.anything(), + }, + factory: expect.any(Function), + toString: expect.any(Function), + }); + + createExtensionTester(extension).render(); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + }); + + it('works with complex options and a callback', async () => { + const schema = createSchemaFromZod(z => z.object({ name: z.string() })); + + const extension = createAppRootElementExtension({ + namespace: 'ns', + name: 'test', + configSchema: schema, + attachTo: { id: 'other', input: 'slot' }, + disabled: true, + inputs: { + children: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + element: ({ inputs, config }) => ( +
+ Hello, {config.name}, {inputs.children.length} +
+ ), + }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'app-root-element', + namespace: 'ns', + name: 'test', + attachTo: { id: 'other', input: 'slot' }, + configSchema: schema, + disabled: true, + inputs: { + children: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + output: { + element: expect.anything(), + }, + factory: expect.any(Function), + toString: expect.any(Function), + }); + + createExtensionTester(extension, { config: { name: 'Robin' } }) + .add( + createExtension({ + attachTo: { id: 'app-root-element:ns/test', input: 'children' }, + output: { element: coreExtensionData.reactElement }, + factory: () => ({ element:
}), + }), + ) + .render(); + + await expect( + screen.findByText('Hello, Robin, 1'), + ).resolves.toBeInTheDocument(); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts new file mode 100644 index 0000000000..8be82d5540 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createAppRootElementExtension.ts @@ -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. + */ + +import { JSX } from 'react'; +import { PortableSchema } from '../schema/types'; +import { Expand } from '../types'; +import { coreExtensionData } from '../wiring/coreExtensionData'; +import { + AnyExtensionInputMap, + ExtensionDefinition, + ResolvedExtensionInputs, + createExtension, +} from '../wiring/createExtension'; + +/** + * Creates an extension that renders a React element at the app root, outside of + * the app layout. This is useful for example for shared popups and similar. + * + * @public + */ +export function createAppRootElementExtension< + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options: { + namespace?: string; + name?: string; + attachTo?: { id: string; input: string }; + configSchema?: PortableSchema; + disabled?: boolean; + inputs?: TInputs; + element: + | JSX.Element + | ((options: { + inputs: Expand>; + config: TConfig; + }) => JSX.Element); +}): ExtensionDefinition { + return createExtension({ + kind: 'app-root-element', + namespace: options.namespace, + name: options.name, + attachTo: options.attachTo ?? { id: 'app/root', input: 'elements' }, + configSchema: options.configSchema, + disabled: options.disabled, + inputs: options.inputs, + output: { + element: coreExtensionData.reactElement, + }, + factory({ inputs, config }) { + return { + element: + typeof options.element === 'function' + ? options.element({ inputs, config }) + : options.element, + }; + }, + }); +} diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx new file mode 100644 index 0000000000..d21cfa7583 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.test.tsx @@ -0,0 +1,119 @@ +/* + * 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 { createExtensionTester } from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { createSchemaFromZod } from '../schema/createSchemaFromZod'; +import { coreExtensionData } from '../wiring/coreExtensionData'; +import { createExtension } from '../wiring/createExtension'; +import { createExtensionInput } from '../wiring/createExtensionInput'; +import { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; +import { createPageExtension } from './createPageExtension'; + +describe('createAppRootWrapperExtension', () => { + it('works with simple options and no props', async () => { + const extension = createAppRootWrapperExtension({ + Component: () =>
Hello
, + }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'app-root-wrapper', + attachTo: { id: 'app/root', input: 'wrappers' }, + disabled: false, + inputs: {}, + output: { + component: expect.anything(), + }, + factory: expect.any(Function), + toString: expect.any(Function), + }); + + createExtensionTester( + createPageExtension({ + defaultPath: '/', + loader: async () =>
, + }), + ) + .add(extension) + .render(); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + }); + + it('works with complex options and props', async () => { + const schema = createSchemaFromZod(z => z.object({ name: z.string() })); + + const extension = createAppRootWrapperExtension({ + namespace: 'ns', + name: 'test', + configSchema: schema, + disabled: true, + inputs: { + children: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + Component: ({ inputs, config, children }) => ( +
+ {children} +
+ ), + }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'app-root-wrapper', + namespace: 'ns', + name: 'test', + attachTo: { id: 'app/root', input: 'wrappers' }, + configSchema: schema, + disabled: true, + inputs: { + children: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + output: { + component: expect.anything(), + }, + factory: expect.any(Function), + toString: expect.any(Function), + }); + + createExtensionTester( + createPageExtension({ + defaultPath: '/', + loader: async () =>
Hello
, + }), + ) + .add(extension, { config: { name: 'Robin' } }) + .add( + createExtension({ + attachTo: { id: 'app-root-wrapper:ns/test', input: 'children' }, + output: { element: coreExtensionData.reactElement }, + factory: () => ({ element:
}), + }), + ) + .render(); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + await expect(screen.findByTestId('Robin-1')).resolves.toBeInTheDocument(); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx new file mode 100644 index 0000000000..bbc51806af --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createAppRootWrapperExtension.tsx @@ -0,0 +1,84 @@ +/* + * 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, { ComponentType, PropsWithChildren } from 'react'; +import { PortableSchema } from '../schema/types'; +import { + AnyExtensionInputMap, + ExtensionDefinition, + ResolvedExtensionInputs, + createExtension, +} from '../wiring/createExtension'; +import { createExtensionDataRef } from '../wiring/createExtensionDataRef'; +import { Expand } from '../types'; + +/** + * Creates an extension that renders a React wrapper at the app root, enclosing + * the app layout. This is useful for example for adding global React contexts + * and similar. + * + * @public + */ +export function createAppRootWrapperExtension< + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options: { + namespace?: string; + name?: string; + attachTo?: { id: string; input: string }; + configSchema?: PortableSchema; + disabled?: boolean; + inputs?: TInputs; + Component: ComponentType< + PropsWithChildren<{ + inputs: Expand>; + config: TConfig; + }> + >; +}): ExtensionDefinition { + return createExtension({ + kind: 'app-root-wrapper', + namespace: options.namespace, + name: options.name, + attachTo: options.attachTo ?? { id: 'app/root', input: 'wrappers' }, + configSchema: options.configSchema, + disabled: options.disabled, + inputs: options.inputs, + output: { + component: createAppRootWrapperExtension.componentDataRef, + }, + factory({ inputs, config }) { + const Component = (props: PropsWithChildren<{}>) => { + return ( + + {props.children} + + ); + }; + return { + component: Component, + }; + }, + }); +} + +/** @public */ +export namespace createAppRootWrapperExtension { + export const componentDataRef = + createExtensionDataRef>>( + 'app.root.wrapper', + ); +} diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx new file mode 100644 index 0000000000..2efa106750 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx @@ -0,0 +1,98 @@ +/* + * 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, { lazy, ComponentType } from 'react'; +import { + AnyExtensionInputMap, + ResolvedExtensionInputs, + createExtension, + createExtensionDataRef, +} from '../wiring'; +import { Expand } from '../types'; +import { PortableSchema } from '../schema'; +import { ExtensionBoundary, ComponentRef } from '../components'; + +/** @public */ +export function createComponentExtension< + TProps extends {}, + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options: { + ref: ComponentRef; + name?: string; + disabled?: boolean; + inputs?: TInputs; + configSchema?: PortableSchema; + loader: + | { + lazy: (values: { + config: TConfig; + inputs: Expand>; + }) => Promise>; + } + | { + sync: (values: { + config: TConfig; + inputs: Expand>; + }) => ComponentType; + }; +}) { + return createExtension({ + kind: 'component', + namespace: options.ref.id, + name: options.name, + attachTo: { id: 'app', input: 'components' }, + inputs: options.inputs, + disabled: options.disabled, + configSchema: options.configSchema, + output: { + component: createComponentExtension.componentDataRef, + }, + factory({ config, inputs, node }) { + let ExtensionComponent: ComponentType; + + if ('sync' in options.loader) { + ExtensionComponent = options.loader.sync({ config, inputs }); + } else { + const lazyLoader = options.loader.lazy; + ExtensionComponent = lazy(() => + lazyLoader({ config, inputs }).then(Component => ({ + default: Component, + })), + ) as unknown as ComponentType; + } + + return { + component: { + ref: options.ref, + impl: props => ( + + + + ), + }, + }; + }, + }); +} + +/** @public */ +export namespace createComponentExtension { + export const componentDataRef = createExtensionDataRef<{ + ref: ComponentRef; + impl: ComponentType; + }>('core.component.component'); +} diff --git a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx index 84fbe5ffee..4b92d4dace 100644 --- a/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createNavItemExtension.tsx @@ -16,7 +16,7 @@ import { IconComponent } from '@backstage/core-plugin-api'; import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { coreExtensionData, createExtension } from '../wiring'; +import { createExtension, createExtensionDataRef } from '../wiring'; import { RouteRef } from '../routing'; /** @@ -24,22 +24,25 @@ import { RouteRef } from '../routing'; * @public */ export function createNavItemExtension(options: { - id: string; + namespace?: string; + name?: string; routeRef: RouteRef; title: string; icon: IconComponent; }) { - const { id, routeRef, title, icon } = options; + const { routeRef, title, icon, namespace, name } = options; return createExtension({ - id, - attachTo: { id: 'core.nav', input: 'items' }, + namespace, + name, + kind: 'nav-item', + attachTo: { id: 'app/nav', input: 'items' }, configSchema: createSchemaFromZod(z => z.object({ title: z.string().default(title), }), ), output: { - navTarget: coreExtensionData.navTarget, + navTarget: createNavItemExtension.targetDataRef, }, factory: ({ config }) => ({ navTarget: { @@ -50,3 +53,13 @@ export function createNavItemExtension(options: { }), }); } + +/** @public */ +export namespace createNavItemExtension { + // TODO(Rugvip): Should this be broken apart into separate refs? title/icon/routeRef + export const targetDataRef = createExtensionDataRef<{ + title: string; + icon: IconComponent; + routeRef: RouteRef; + }>('core.nav-item.target'); +} diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx new file mode 100644 index 0000000000..8c54743f1d --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.test.tsx @@ -0,0 +1,47 @@ +/* + * 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 { createNavLogoExtension } from './createNavLogoExtension'; + +jest.mock('@backstage/core-plugin-api', () => ({ + ...jest.requireActual('@backstage/core-plugin-api'), +})); + +describe('createNavLogoExtension', () => { + it('creates the extension properly', () => { + expect( + createNavLogoExtension({ + name: 'test', + logoFull:
Logo Full
, + logoIcon:
Logo Icon
, + }), + ).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'nav-logo', + name: 'test', + attachTo: { id: 'app/nav', input: 'logos' }, + disabled: false, + inputs: {}, + output: { + logos: expect.anything(), + }, + factory: expect.any(Function), + toString: expect.any(Function), + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx new file mode 100644 index 0000000000..3d66b26d6b --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createNavLogoExtension.tsx @@ -0,0 +1,55 @@ +/* + * 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 { createExtension, createExtensionDataRef } from '../wiring'; + +/** + * Helper for creating extensions for a nav logos. + * @public + */ +export function createNavLogoExtension(options: { + name?: string; + namespace?: string; + logoIcon: JSX.Element; + logoFull: JSX.Element; +}) { + const { logoIcon, logoFull } = options; + return createExtension({ + kind: 'nav-logo', + name: options?.name, + namespace: options?.namespace, + attachTo: { id: 'app/nav', input: 'logos' }, + output: { + logos: createNavLogoExtension.logoElementsDataRef, + }, + factory: () => { + return { + logos: { + logoIcon, + logoFull, + }, + }; + }, + }); +} + +/** @public */ +export namespace createNavLogoExtension { + export const logoElementsDataRef = createExtensionDataRef<{ + logoIcon?: JSX.Element; + logoFull?: JSX.Element; + }>('core.nav-logo.logo-elements'); +} diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx index 81843558da..374400fce8 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx @@ -15,16 +15,12 @@ */ import React from 'react'; -import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { useAnalytics } from '@backstage/core-plugin-api'; import { waitFor } from '@testing-library/react'; import { PortableSchema } from '../schema'; -import { - coreExtensionData, - createExtensionInput, - createPlugin, -} from '../wiring'; +import { coreExtensionData, createExtensionInput } from '../wiring'; import { createPageExtension } from './createPageExtension'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; jest.mock('@backstage/core-plugin-api', () => ({ ...jest.requireActual('@backstage/core-plugin-api'), @@ -40,14 +36,16 @@ describe('createPageExtension', () => { expect( createPageExtension({ - id: 'test', + name: 'test', configSchema, loader: async () =>
, }), ).toEqual({ - $$type: '@backstage/Extension', - id: 'test', - attachTo: { id: 'core.routes', input: 'routes' }, + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + name: 'test', + kind: 'page', + attachTo: { id: 'app/routes', input: 'routes' }, configSchema: expect.anything(), disabled: false, inputs: {}, @@ -57,11 +55,12 @@ describe('createPageExtension', () => { routeRef: expect.anything(), }, factory: expect.any(Function), + toString: expect.any(Function), }); expect( createPageExtension({ - id: 'test', + name: 'test', attachTo: { id: 'other', input: 'place' }, disabled: true, configSchema, @@ -73,8 +72,10 @@ describe('createPageExtension', () => { loader: async () =>
, }), ).toEqual({ - $$type: '@backstage/Extension', - id: 'test', + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + name: 'test', + kind: 'page', attachTo: { id: 'other', input: 'place' }, configSchema: expect.anything(), disabled: true, @@ -89,18 +90,21 @@ describe('createPageExtension', () => { routeRef: expect.anything(), }, factory: expect.any(Function), + toString: expect.any(Function), }); expect( createPageExtension({ - id: 'test', + name: 'test', defaultPath: '/here', loader: async () =>
, }), ).toEqual({ - $$type: '@backstage/Extension', - id: 'test', - attachTo: { id: 'core.routes', input: 'routes' }, + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + name: 'test', + kind: 'page', + attachTo: { id: 'app/routes', input: 'routes' }, configSchema: expect.anything(), disabled: false, inputs: {}, @@ -110,6 +114,7 @@ describe('createPageExtension', () => { routeRef: expect.anything(), }, factory: expect.any(Function), + toString: expect.any(Function), }); }); @@ -120,19 +125,12 @@ describe('createPageExtension', () => { captureEvent, }); - const extension = createPageExtension({ - id: 'plugin.page', - defaultPath: '/', - loader: async () =>
Component
, - }); - - const output = extension.factory({ - source: createPlugin({ id: 'plugin ' }), - config: { path: '/' }, - inputs: {}, - }); - - renderWithEffects(wrapInTestApp(output.element as unknown as JSX.Element)); + createExtensionTester( + createPageExtension({ + defaultPath: '/', + loader: async () =>
Component
, + }), + ).render(); await waitFor(() => expect(captureEvent).toHaveBeenCalledWith( diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx index f5e258a1e4..dfe549f32b 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx @@ -20,12 +20,12 @@ import { createSchemaFromZod, PortableSchema } from '../schema'; import { coreExtensionData, createExtension, - Extension, - ExtensionInputValues, + ResolvedExtensionInputs, AnyExtensionInputMap, } from '../wiring'; import { RouteRef } from '../routing'; import { Expand } from '../types'; +import { ExtensionDefinition } from '../wiring/createExtension'; /** * Helper for creating extensions for a routable React page component. @@ -44,19 +44,18 @@ export function createPageExtension< configSchema: PortableSchema; } ) & { - id: string; + namespace?: string; + name?: string; attachTo?: { id: string; input: string }; disabled?: boolean; inputs?: TInputs; routeRef?: RouteRef; loader: (options: { config: TConfig; - inputs: Expand>; + inputs: Expand>; }) => Promise; }, -): Extension { - const { id } = options; - +): ExtensionDefinition { const configSchema = 'configSchema' in options ? options.configSchema @@ -65,8 +64,10 @@ export function createPageExtension< ) as PortableSchema); return createExtension({ - id, - attachTo: options.attachTo ?? { id: 'core.routes', input: 'routes' }, + kind: 'page', + namespace: options.namespace, + name: options.name, + attachTo: options.attachTo ?? { id: 'app/routes', input: 'routes' }, configSchema, inputs: options.inputs, disabled: options.disabled, @@ -75,7 +76,7 @@ export function createPageExtension< path: coreExtensionData.routePath, routeRef: coreExtensionData.routeRef.optional(), }, - factory({ config, inputs, source }) { + factory({ config, inputs, node }) { const ExtensionComponent = lazy(() => options .loader({ config, inputs }) @@ -86,7 +87,7 @@ export function createPageExtension< path: config.path, routeRef: options.routeRef, element: ( - + ), diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx new file mode 100644 index 0000000000..7109e1a8aa --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createRouterExtension.test.tsx @@ -0,0 +1,167 @@ +/* + * 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 { createSpecializedApp } from '@backstage/frontend-app-api'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { MockConfigApi } from '@backstage/test-utils'; +import { MemoryRouter } from 'react-router-dom'; +import { createSchemaFromZod } from '../schema/createSchemaFromZod'; +import { coreExtensionData } from '../wiring/coreExtensionData'; +import { createExtension } from '../wiring/createExtension'; +import { createExtensionInput } from '../wiring/createExtensionInput'; +import { createExtensionOverrides } from '../wiring/createExtensionOverrides'; +import { createPageExtension } from './createPageExtension'; +import { createRouterExtension } from './createRouterExtension'; + +describe('createRouterExtension', () => { + it('works with simple options and no props', async () => { + const extension = createRouterExtension({ + namespace: 'test', + Component: ({ children }) => ( + +
{children}
+
+ ), + }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'app-router-component', + namespace: 'test', + attachTo: { id: 'app/root', input: 'router' }, + disabled: false, + inputs: {}, + output: { + component: expect.anything(), + }, + factory: expect.any(Function), + toString: expect.any(Function), + }); + + const app = createSpecializedApp({ + features: [ + createExtensionOverrides({ + extensions: [ + extension, + createPageExtension({ + namespace: 'test', + defaultPath: '/', + loader: async () =>
, + }), + ], + }), + ], + }); + + render(app.createRoot()); + + await expect( + screen.findByTestId('test-contents'), + ).resolves.toBeInTheDocument(); + await expect( + screen.findByTestId('test-router'), + ).resolves.toBeInTheDocument(); + }); + + it('works with complex options and props', async () => { + const schema = createSchemaFromZod(z => z.object({ name: z.string() })); + + const extension = createRouterExtension({ + namespace: 'test', + name: 'test', + configSchema: schema, + inputs: { + children: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + Component: ({ inputs, config, children }) => ( + +
+ {children} +
+
+ ), + }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'app-router-component', + namespace: 'test', + name: 'test', + attachTo: { id: 'app/root', input: 'router' }, + configSchema: schema, + disabled: false, + inputs: { + children: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + output: { + component: expect.anything(), + }, + factory: expect.any(Function), + toString: expect.any(Function), + }); + + const app = createSpecializedApp({ + features: [ + createExtensionOverrides({ + extensions: [ + extension, + createExtension({ + namespace: 'test', + attachTo: { + id: 'app-router-component:test/test', + input: 'children', + }, + output: { element: coreExtensionData.reactElement }, // doesn't matter + factory: () => ({ element:
}), + }), + createPageExtension({ + namespace: 'test', + defaultPath: '/', + loader: async () =>
, + }), + ], + }), + ], + config: new MockConfigApi({ + app: { + extensions: [ + { + 'app-router-component:test/test': { config: { name: 'Robin' } }, + }, + ], + }, + }), + }); + + render(app.createRoot()); + + await expect( + screen.findByTestId('test-contents'), + ).resolves.toBeInTheDocument(); + await expect( + screen.findByTestId('test-router-Robin-1'), + ).resolves.toBeInTheDocument(); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx new file mode 100644 index 0000000000..32d61f7e60 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createRouterExtension.tsx @@ -0,0 +1,84 @@ +/* + * 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, { ComponentType, PropsWithChildren } from 'react'; +import { PortableSchema } from '../schema/types'; +import { + AnyExtensionInputMap, + ExtensionDefinition, + ResolvedExtensionInputs, + createExtension, +} from '../wiring/createExtension'; +import { createExtensionDataRef } from '../wiring/createExtensionDataRef'; +import { Expand } from '../types'; + +/** + * Creates an extension that replaces the router implementation at the app root. + * This is useful to be able to for example replace the BrowserRouter with a + * MemoryRouter in tests, or to add additional props to a BrowserRouter. + * + * @public + */ +export function createRouterExtension< + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options: { + namespace?: string; + name?: string; + attachTo?: { id: string; input: string }; + configSchema?: PortableSchema; + disabled?: boolean; + inputs?: TInputs; + Component: ComponentType< + PropsWithChildren<{ + inputs: Expand>; + config: TConfig; + }> + >; +}): ExtensionDefinition { + return createExtension({ + kind: 'app-router-component', + namespace: options.namespace, + name: options.name, + attachTo: options.attachTo ?? { id: 'app/root', input: 'router' }, + configSchema: options.configSchema, + disabled: options.disabled, + inputs: options.inputs, + output: { + component: createRouterExtension.componentDataRef, + }, + factory({ inputs, config }) { + const Component = (props: PropsWithChildren<{}>) => { + return ( + + {props.children} + + ); + }; + return { + component: Component, + }; + }, + }); +} + +/** @public */ +export namespace createRouterExtension { + export const componentDataRef = + createExtensionDataRef>>( + 'app.router.wrapper', + ); +} diff --git a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx new file mode 100644 index 0000000000..d8835ae788 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.test.tsx @@ -0,0 +1,47 @@ +/* + * 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 { createExtensionTester } from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import { createSignInPageExtension } from './createSignInPageExtension'; +import { coreExtensionData, createExtension } from '../wiring'; + +describe('createSignInPageExtension', () => { + it('renders a sign-in page', async () => { + const SignInPage = createSignInPageExtension({ + name: 'test', + loader: async () => () =>
, + }); + + createExtensionTester( + createExtension({ + name: 'dummy', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { + element: coreExtensionData.reactElement, + }, + factory: () => ({ element:
}), + }), + ) + .add(SignInPage) + .render(); + + await expect( + screen.findByTestId('sign-in-page'), + ).resolves.toBeInTheDocument(); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx new file mode 100644 index 0000000000..b525cc3a7b --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createSignInPageExtension.tsx @@ -0,0 +1,83 @@ +/* + * 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, { ComponentType, lazy } from 'react'; +import { ExtensionBoundary } from '../components'; +import { PortableSchema } from '../schema'; +import { + createExtension, + ResolvedExtensionInputs, + AnyExtensionInputMap, + createExtensionDataRef, + ExtensionDefinition, +} from '../wiring'; +import { Expand } from '../types'; +import { SignInPageProps } from '@backstage/core-plugin-api'; + +/** + * + * @public + */ +export function createSignInPageExtension< + TConfig extends {}, + TInputs extends AnyExtensionInputMap, +>(options: { + namespace?: string; + name?: string; + attachTo?: { id: string; input: string }; + configSchema?: PortableSchema; + disabled?: boolean; + inputs?: TInputs; + loader: (options: { + config: TConfig; + inputs: Expand>; + }) => Promise>; +}): ExtensionDefinition { + return createExtension({ + kind: 'sign-in-page', + namespace: options?.namespace, + name: options?.name, + attachTo: options.attachTo ?? { id: 'app/root', input: 'signInPage' }, + configSchema: options.configSchema, + inputs: options.inputs, + disabled: options.disabled, + output: { + component: createSignInPageExtension.componentDataRef, + }, + factory({ config, inputs, node }) { + const ExtensionComponent = lazy(() => + options + .loader({ config, inputs }) + .then(component => ({ default: component })), + ); + + return { + component: props => ( + + + + ), + }; + }, + }); +} + +/** @public */ +export namespace createSignInPageExtension { + export const componentDataRef = createExtensionDataRef< + ComponentType + >('core.sign-in-page.component'); +} diff --git a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts b/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts index d41121d51c..cc6dbdabe9 100644 --- a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts @@ -14,17 +14,25 @@ * limitations under the License. */ -import { createExtension, coreExtensionData } from '../wiring'; +import { createExtension, createExtensionDataRef } from '../wiring'; import { AppTheme } from '@backstage/core-plugin-api'; /** @public */ export function createThemeExtension(theme: AppTheme) { return createExtension({ - id: `themes.${theme.id}`, - attachTo: { id: 'core', input: 'themes' }, + kind: 'theme', + namespace: 'app', + name: theme.id, + attachTo: { id: 'app', input: 'themes' }, output: { - theme: coreExtensionData.theme, + theme: createThemeExtension.themeDataRef, }, factory: () => ({ theme }), }); } + +/** @public */ +export namespace createThemeExtension { + export const themeDataRef = + createExtensionDataRef('core.theme.theme'); +} diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts new file mode 100644 index 0000000000..975901b8e5 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.test.ts @@ -0,0 +1,134 @@ +/* + * 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 { + createTranslationRef, + createTranslationMessages, + createTranslationResource, +} from '@backstage/core-plugin-api/alpha'; +import { createTranslationExtension } from './createTranslationExtension'; + +const translationRef = createTranslationRef({ + id: 'test', + messages: { + a: 'a', + b: 'b', + }, +}); + +describe('createTranslationExtension', () => { + it('creates a translation message extension', () => { + const messages = createTranslationMessages({ + ref: translationRef, + messages: { + a: 'A', + }, + }); + const extension = createTranslationExtension({ resource: messages }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'translation', + namespace: 'test', + attachTo: { id: 'app', input: 'translations' }, + disabled: false, + inputs: {}, + output: { + resource: createTranslationExtension.translationDataRef, + }, + factory: expect.any(Function), + toString: expect.any(Function), + }); + + expect((extension as any).factory({} as any)).toEqual({ + resource: messages, + }); + }); + + it('creates a translation resource extension', () => { + const resource = createTranslationResource({ + ref: translationRef, + translations: { + sv: () => + Promise.resolve({ + default: createTranslationMessages({ + ref: translationRef, + messages: { + a: 'Ä', + b: 'Ö', + }, + }), + }), + }, + }); + const extension = createTranslationExtension({ resource }); + + expect(extension).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'translation', + namespace: 'test', + attachTo: { id: 'app', input: 'translations' }, + disabled: false, + inputs: {}, + output: { + resource: createTranslationExtension.translationDataRef, + }, + factory: expect.any(Function), + toString: expect.any(Function), + }); + + expect((extension as any).factory({} as any)).toEqual({ resource }); + }); + + it('creates a translation resource extension with a name', () => { + expect( + createTranslationExtension({ + name: 'sv', + resource: createTranslationResource({ + ref: translationRef, + translations: { + sv: () => + Promise.resolve({ + default: createTranslationMessages({ + ref: translationRef, + messages: { + a: 'Ä', + b: 'Ö', + }, + }), + }), + }, + }), + }), + ).toEqual({ + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: 'translation', + namespace: 'test', + name: 'sv', + attachTo: { id: 'app', input: 'translations' }, + disabled: false, + inputs: {}, + output: { + resource: createTranslationExtension.translationDataRef, + }, + factory: expect.any(Function), + toString: expect.any(Function), + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts new file mode 100644 index 0000000000..8497a64b88 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createTranslationExtension.ts @@ -0,0 +1,42 @@ +/* + * 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 { TranslationMessages, TranslationResource } from '../translation'; +import { createExtension, createExtensionDataRef } from '../wiring'; + +/** @public */ +export function createTranslationExtension(options: { + name?: string; + resource: TranslationResource | TranslationMessages; +}) { + return createExtension({ + kind: 'translation', + namespace: options.resource.id, + name: options.name, + attachTo: { id: 'app', input: 'translations' }, + output: { + resource: createTranslationExtension.translationDataRef, + }, + factory: () => ({ resource: options.resource }), + }); +} + +/** @public */ +export namespace createTranslationExtension { + export const translationDataRef = createExtensionDataRef< + TranslationResource | TranslationMessages + >('core.translation.translation'); +} diff --git a/packages/frontend-plugin-api/src/extensions/index.ts b/packages/frontend-plugin-api/src/extensions/index.ts index 00cf4f919c..562cb728cb 100644 --- a/packages/frontend-plugin-api/src/extensions/index.ts +++ b/packages/frontend-plugin-api/src/extensions/index.ts @@ -15,6 +15,13 @@ */ export { createApiExtension } from './createApiExtension'; +export { createAppRootElementExtension } from './createAppRootElementExtension'; +export { createAppRootWrapperExtension } from './createAppRootWrapperExtension'; +export { createRouterExtension } from './createRouterExtension'; export { createPageExtension } from './createPageExtension'; export { createNavItemExtension } from './createNavItemExtension'; +export { createNavLogoExtension } from './createNavLogoExtension'; +export { createSignInPageExtension } from './createSignInPageExtension'; export { createThemeExtension } from './createThemeExtension'; +export { createComponentExtension } from './createComponentExtension'; +export { createTranslationExtension } from './createTranslationExtension'; diff --git a/packages/frontend-plugin-api/src/index.ts b/packages/frontend-plugin-api/src/index.ts index 3248e79945..263380b1f8 100644 --- a/packages/frontend-plugin-api/src/index.ts +++ b/packages/frontend-plugin-api/src/index.ts @@ -20,6 +20,7 @@ * @packageDocumentation */ +export * from './analytics'; export * from './apis'; export * from './components'; export * from './extensions'; @@ -27,4 +28,11 @@ export * from './icons'; export * from './routing'; export * from './schema'; export * from './apis/system'; +export * from './translation'; export * from './wiring'; + +export type { + CoreProgressProps, + CoreNotFoundErrorPageProps, + CoreErrorBoundaryFallbackProps, +} from './types'; diff --git a/packages/frontend-plugin-api/src/routing/index.ts b/packages/frontend-plugin-api/src/routing/index.ts index 6c25f725b5..ea733394c6 100644 --- a/packages/frontend-plugin-api/src/routing/index.ts +++ b/packages/frontend-plugin-api/src/routing/index.ts @@ -21,5 +21,5 @@ export { createExternalRouteRef, type ExternalRouteRef, } from './ExternalRouteRef'; -export { useRouteRef, type RouteFunc } from './useRouteRef'; +export { useRouteRef } from './useRouteRef'; export { useRouteRefParams } from './useRouteRefParams'; diff --git a/packages/frontend-plugin-api/src/routing/useRouteRef.test.tsx b/packages/frontend-plugin-api/src/routing/useRouteRef.test.tsx index dcd56f81a6..4f43cf8d01 100644 --- a/packages/frontend-plugin-api/src/routing/useRouteRef.test.tsx +++ b/packages/frontend-plugin-api/src/routing/useRouteRef.test.tsx @@ -21,6 +21,8 @@ import { createVersionedContextForTesting } from '@backstage/version-bridge'; import { useRouteRef } from './useRouteRef'; import { createRouteRef } from './RouteRef'; import { createBrowserHistory } from 'history'; +import { TestApiProvider } from '@backstage/test-utils'; +import { routeResolutionApiRef } from '../apis'; describe('v1 consumer', () => { const context = createVersionedContextForTesting('routing-context'); @@ -31,13 +33,14 @@ describe('v1 consumer', () => { it('should resolve routes', () => { const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); const routeRef = createRouteRef(); const renderedHook = renderHook(() => useRouteRef(routeRef), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( - + + + ), }); @@ -46,14 +49,13 @@ describe('v1 consumer', () => { expect(resolve).toHaveBeenCalledWith( routeRef, expect.objectContaining({ - pathname: '/my-page', + sourcePath: '/my-page', }), ); }); it('re-resolves the routeFunc when the search parameters change', () => { const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); const routeRef = createRouteRef(); const history = createBrowserHistory(); @@ -61,11 +63,13 @@ describe('v1 consumer', () => { const { rerender } = renderHook(() => useRouteRef(routeRef), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( - + + + ), }); @@ -79,7 +83,7 @@ describe('v1 consumer', () => { it('does not re-resolve the routeFunc the location pathname does not change', () => { const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); + const api = { resolve }; const routeRef = createRouteRef(); const history = createBrowserHistory(); @@ -87,11 +91,13 @@ describe('v1 consumer', () => { const { rerender } = renderHook(() => useRouteRef(routeRef), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( - + + + ), }); @@ -105,7 +111,7 @@ describe('v1 consumer', () => { it('does not re-resolve the routeFunc when the search parameter changes', () => { const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); + const api = { resolve }; const routeRef = createRouteRef(); const history = createBrowserHistory(); @@ -113,11 +119,13 @@ describe('v1 consumer', () => { const { rerender } = renderHook(() => useRouteRef(routeRef), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( - + + + ), }); @@ -131,7 +139,7 @@ describe('v1 consumer', () => { it('does not re-resolve the routeFunc when the hash parameter changes', () => { const resolve = jest.fn(() => () => '/hello'); - context.set({ 1: { resolve } }); + const api = { resolve }; const routeRef = createRouteRef(); const history = createBrowserHistory(); @@ -139,11 +147,13 @@ describe('v1 consumer', () => { const { rerender } = renderHook(() => useRouteRef(routeRef), { wrapper: ({ children }: React.PropsWithChildren<{}>) => ( - + + + ), }); diff --git a/packages/frontend-plugin-api/src/routing/useRouteRef.tsx b/packages/frontend-plugin-api/src/routing/useRouteRef.tsx index dfcb110930..1b096a6b5d 100644 --- a/packages/frontend-plugin-api/src/routing/useRouteRef.tsx +++ b/packages/frontend-plugin-api/src/routing/useRouteRef.tsx @@ -15,44 +15,12 @@ */ import { useMemo } from 'react'; -import { matchRoutes, useLocation } from 'react-router-dom'; -import { useVersionedContext } from '@backstage/version-bridge'; +import { useLocation } from 'react-router-dom'; import { AnyRouteRefParams } from './types'; import { RouteRef } from './RouteRef'; import { SubRouteRef } from './SubRouteRef'; import { ExternalRouteRef } from './ExternalRouteRef'; - -/** - * TS magic for handling route parameters. - * - * @remarks - * - * The extra TS magic here is to require a single params argument if the RouteRef - * had at least one param defined, but require 0 arguments if there are no params defined. - * Without this we'd have to pass in empty object to all parameter-less RouteRefs - * just to make TypeScript happy, or we would have to make the argument optional in - * which case you might forget to pass it in when it is actually required. - * - * @public - */ -export type RouteFunc = ( - ...[params]: TParams extends undefined - ? readonly [] - : readonly [params: TParams] -) => string; - -/** - * @internal - */ -export interface RouteResolver { - resolve( - anyRouteRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, - sourceLocation: Parameters[1], - ): RouteFunc | undefined; -} +import { RouteFunc, routeResolutionApiRef, useApi } from '../apis'; /** * React hook for constructing URLs to routes. @@ -105,26 +73,13 @@ export function useRouteRef( | ExternalRouteRef, ): RouteFunc | undefined { const { pathname } = useLocation(); - const versionedContext = useVersionedContext<{ 1: RouteResolver }>( - 'routing-context', - ); - if (!versionedContext) { - throw new Error('Routing context is not available'); - } + const routeResolutionApi = useApi(routeResolutionApiRef); - const resolver = versionedContext.atVersion(1); const routeFunc = useMemo( - () => resolver && resolver.resolve(routeRef, { pathname }), - [resolver, routeRef, pathname], + () => routeResolutionApi.resolve(routeRef, { sourcePath: pathname }), + [routeResolutionApi, routeRef, pathname], ); - if (!versionedContext) { - throw new Error('useRouteRef used outside of routing context'); - } - if (!resolver) { - throw new Error('RoutingContext v1 not available'); - } - const isOptional = 'optional' in routeRef && routeRef.optional; if (!routeFunc && !isOptional) { throw new Error(`No path for ${routeRef}`); diff --git a/packages/frontend-plugin-api/src/translation/index.ts b/packages/frontend-plugin-api/src/translation/index.ts new file mode 100644 index 0000000000..ef3ccccbe3 --- /dev/null +++ b/packages/frontend-plugin-api/src/translation/index.ts @@ -0,0 +1,28 @@ +/* + * 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 { + type TranslationMessages, + type TranslationMessagesOptions, + type TranslationResource, + type TranslationResourceOptions, + type TranslationRef, + type TranslationRefOptions, + createTranslationMessages, + createTranslationResource, + createTranslationRef, + useTranslationRef, +} from '@backstage/core-plugin-api/alpha'; diff --git a/packages/frontend-plugin-api/src/types.ts b/packages/frontend-plugin-api/src/types.ts index 8b6f02a942..4d0fb1ac4b 100644 --- a/packages/frontend-plugin-api/src/types.ts +++ b/packages/frontend-plugin-api/src/types.ts @@ -14,9 +14,27 @@ * limitations under the License. */ +import { ReactNode } from 'react'; +import { BackstagePlugin } from './wiring'; + // TODO(Rugvip): This might be a quite useful utility type, maybe add to @backstage/types? /** * Utility type to expand type aliases into their equivalent type. * @ignore */ export type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; + +/** @public */ +export type CoreProgressProps = {}; + +/** @public */ +export type CoreNotFoundErrorPageProps = { + children?: ReactNode; +}; + +/** @public */ +export type CoreErrorBoundaryFallbackProps = { + plugin?: BackstagePlugin; + error: Error; + resetError: () => void; +}; diff --git a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts index 023eab57e1..a6ff41f6b0 100644 --- a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts +++ b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts @@ -15,27 +15,12 @@ */ import { JSX } from 'react'; -import { - AnyApiFactory, - AppTheme, - IconComponent, -} from '@backstage/core-plugin-api'; -import { createExtensionDataRef } from './createExtensionDataRef'; import { RouteRef } from '../routing'; - -/** @public */ -export type NavTarget = { - title: string; - icon: IconComponent; - routeRef: RouteRef; -}; +import { createExtensionDataRef } from './createExtensionDataRef'; /** @public */ export const coreExtensionData = { reactElement: createExtensionDataRef('core.reactElement'), routePath: createExtensionDataRef('core.routing.path'), - apiFactory: createExtensionDataRef('core.api.factory'), routeRef: createExtensionDataRef('core.routing.ref'), - navTarget: createExtensionDataRef('core.nav.target'), - theme: createExtensionDataRef('core.theme'), }; diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 071272d58f..42ab21db80 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -25,7 +25,7 @@ function unused(..._any: any[]) {} describe('createExtension', () => { it('should create an extension with a simple output', () => { const baseConfig = { - id: 'test', + namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: { foo: stringData, @@ -39,7 +39,7 @@ describe('createExtension', () => { }; }, }); - expect(extension.id).toBe('test'); + expect(extension.namespace).toBe('test'); // When declared as an error function without a block the TypeScript errors // are a more specific and will point at the property that is problematic. @@ -163,7 +163,7 @@ describe('createExtension', () => { it('should create an extension with a some optional output', () => { const baseConfig = { - id: 'test', + namespace: 'test', attachTo: { id: 'root', input: 'default' }, output: { foo: stringData, @@ -176,7 +176,7 @@ describe('createExtension', () => { foo: 'bar', }), }); - expect(extension.id).toBe('test'); + expect(extension.namespace).toBe('test'); createExtension({ ...baseConfig, @@ -233,7 +233,7 @@ describe('createExtension', () => { it('should create an extension with input', () => { const extension = createExtension({ - id: 'test', + namespace: 'test', attachTo: { id: 'root', input: 'default' }, inputs: { mixed: createExtensionInput({ @@ -251,34 +251,34 @@ describe('createExtension', () => { foo: stringData, }, factory({ inputs }) { - const a1: string = inputs.mixed?.[0].required; + const a1: string = inputs.mixed?.[0].output.required; // @ts-expect-error - const a2: number = inputs.mixed?.[0].required; + const a2: number = inputs.mixed?.[0].output.required; // @ts-expect-error - const a3: any = inputs.mixed?.[0].nonExistent; + const a3: any = inputs.mixed?.[0].output.nonExistent; unused(a1, a2, a3); - const b1: string | undefined = inputs.mixed?.[0].optional; + const b1: string | undefined = inputs.mixed?.[0].output.optional; // @ts-expect-error - const b2: string = inputs.mixed?.[0].optional; + const b2: string = inputs.mixed?.[0].output.optional; // @ts-expect-error - const b3: number = inputs.mixed?.[0].optional; + const b3: number = inputs.mixed?.[0].output.optional; // @ts-expect-error - const b4: number | undefined = inputs.mixed?.[0].optional; + const b4: number | undefined = inputs.mixed?.[0].output.optional; unused(b1, b2, b3, b4); - const c1: string = inputs.onlyRequired?.[0].required; + const c1: string = inputs.onlyRequired?.[0].output.required; // @ts-expect-error - const c2: number = inputs.onlyRequired?.[0].required; + const c2: number = inputs.onlyRequired?.[0].output.required; unused(c1, c2); - const d1: string | undefined = inputs.onlyOptional?.[0].optional; + const d1: string | undefined = inputs.onlyOptional?.[0].output.optional; // @ts-expect-error - const d2: string = inputs.onlyOptional?.[0].optional; + const d2: string = inputs.onlyOptional?.[0].output.optional; // @ts-expect-error - const d3: number = inputs.onlyOptional?.[0].optional; + const d3: number = inputs.onlyOptional?.[0].output.optional; // @ts-expect-error - const d4: number | undefined = inputs.onlyOptional?.[0].optional; + const d4: number | undefined = inputs.onlyOptional?.[0].output.optional; unused(d1, d2, d3, d4); return { @@ -286,6 +286,9 @@ describe('createExtension', () => { }; }, }); - expect(extension.id).toBe('test'); + expect(extension.namespace).toBe('test'); + expect(String(extension)).toBe( + 'ExtensionDefinition{namespace=test,attachTo=root@default}', + ); }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index ff011a5875..be76fbc069 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -14,11 +14,11 @@ * limitations under the License. */ +import { AppNode } from '../apis'; import { PortableSchema } from '../schema'; import { Expand } from '../types'; import { ExtensionDataRef } from './createExtensionDataRef'; import { ExtensionInput } from './createExtensionInput'; -import { BackstagePlugin } from './createPlugin'; /** @public */ export type AnyExtensionDataMap = { @@ -52,18 +52,28 @@ export type ExtensionDataValues = { }; /** - * Converts an extension input map into the matching concrete input values type. + * Convert a single extension input into a matching resolved input. * @public */ -export type ExtensionInputValues< +export type ResolvedExtensionInput = + { + node: AppNode; + output: ExtensionDataValues; + }; + +/** + * Converts an extension input map into a matching collection of resolved inputs. + * @public + */ +export type ResolvedExtensionInputs< TInputs extends { [name in string]: ExtensionInput }, > = { [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton'] - ? Array>> + ? Array>> : false extends TInputs[InputName]['config']['optional'] - ? Expand> + ? Expand> : Expand< - ExtensionDataValues | undefined + ResolvedExtensionInput | undefined >; }; @@ -73,38 +83,63 @@ export interface CreateExtensionOptions< TInputs extends AnyExtensionInputMap, TConfig, > { - id: string; + kind?: string; + namespace?: string; + name?: string; attachTo: { id: string; input: string }; disabled?: boolean; inputs?: TInputs; output: TOutput; configSchema?: PortableSchema; factory(options: { - source?: BackstagePlugin; + node: AppNode; config: TConfig; - inputs: Expand>; + inputs: Expand>; }): Expand>; } /** @public */ -export interface Extension { - $$type: '@backstage/Extension'; - id: string; - attachTo: { id: string; input: string }; - disabled: boolean; - inputs: AnyExtensionInputMap; - output: AnyExtensionDataMap; - configSchema?: PortableSchema; +export interface ExtensionDefinition { + $$type: '@backstage/ExtensionDefinition'; + readonly kind?: string; + readonly namespace?: string; + readonly name?: string; + readonly attachTo: { id: string; input: string }; + readonly disabled: boolean; + readonly configSchema?: PortableSchema; +} + +/** @internal */ +export interface InternalExtensionDefinition + extends ExtensionDefinition { + readonly version: 'v1'; + readonly inputs: AnyExtensionInputMap; + readonly output: AnyExtensionDataMap; factory(options: { - source?: BackstagePlugin; + node: AppNode; config: TConfig; - inputs: Record< - string, - undefined | Record | Array> - >; + inputs: ResolvedExtensionInputs; }): ExtensionDataValues; } +/** @internal */ +export function toInternalExtensionDefinition( + overrides: ExtensionDefinition, +): InternalExtensionDefinition { + const internal = overrides as InternalExtensionDefinition; + if (internal.$$type !== '@backstage/ExtensionDefinition') { + throw new Error( + `Invalid extension definition instance, bad type '${internal.$$type}'`, + ); + } + if (internal.version !== 'v1') { + throw new Error( + `Invalid extension definition instance, bad version '${internal.version}'`, + ); + } + return internal; +} + /** @public */ export function createExtension< TOutput extends AnyExtensionDataMap, @@ -112,18 +147,38 @@ export function createExtension< TConfig = never, >( options: CreateExtensionOptions, -): Extension { +): ExtensionDefinition { return { - ...options, + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + kind: options.kind, + namespace: options.namespace, + name: options.name, + attachTo: options.attachTo, disabled: options.disabled ?? false, - $$type: '@backstage/Extension', inputs: options.inputs ?? {}, + output: options.output, + configSchema: options.configSchema, factory({ inputs, ...rest }) { // TODO: Simplify this, but TS wouldn't infer the input type for some reason return options.factory({ - inputs: inputs as Expand>, + inputs: inputs as Expand>, ...rest, }); }, - }; + toString() { + const parts: string[] = []; + if (options.kind) { + parts.push(`kind=${options.kind}`); + } + if (options.namespace) { + parts.push(`namespace=${options.namespace}`); + } + if (options.name) { + parts.push(`name=${options.name}`); + } + parts.push(`attachTo=${options.attachTo.id}@${options.attachTo.input}`); + return `ExtensionDefinition{${parts.join(',')}}`; + }, + } as InternalExtensionDefinition; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.test.ts new file mode 100644 index 0000000000..f7a0dc82d0 --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.test.ts @@ -0,0 +1,28 @@ +/* + * 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 { createExtensionDataRef } from './createExtensionDataRef'; + +describe('createExtensionDataRef', () => { + it('can be created and read', () => { + const ref = createExtensionDataRef('foo'); + expect(ref.id).toBe('foo'); + expect(String(ref)).toBe('ExtensionDataRef{id=foo,optional=false}'); + const refOptional = ref.optional(); + expect(refOptional.id).toBe('foo'); + expect(String(refOptional)).toBe('ExtensionDataRef{id=foo,optional=true}'); + }); +}); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts index d0d96e85f9..a7f4e4374e 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts @@ -43,7 +43,14 @@ export function createExtensionDataRef( $$type: '@backstage/ExtensionDataRef', config: {}, optional() { - return { ...this, config: { ...this.config, optional: true } }; + return { + ...this, + config: { ...this.config, optional: true }, + }; }, - } as ConfigurableExtensionDataRef; + toString() { + const optional = Boolean(this.config.optional); + return `ExtensionDataRef{id=${id},optional=${optional}}`; + }, + } as ConfigurableExtensionDataRef; } diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts index eb69f954e4..aa00247222 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.test.ts @@ -26,6 +26,8 @@ describe('createExtensionOverrides', () => { { "$$type": "@backstage/ExtensionOverrides", "extensions": [], + "featureFlags": [], + "toString": [Function], "version": "v1", } `); @@ -36,8 +38,22 @@ describe('createExtensionOverrides', () => { createExtensionOverrides({ extensions: [ createExtension({ - id: 'a', - attachTo: { id: 'core', input: 'apis' }, + name: 'a', + attachTo: { id: 'app', input: 'apis' }, + output: {}, + factory: () => ({}), + }), + createExtension({ + namespace: 'b', + attachTo: { id: 'app', input: 'apis' }, + output: {}, + factory: () => ({}), + }), + createExtension({ + kind: 'k', + namespace: 'c', + name: 'n', + attachTo: { id: 'app', input: 'apis' }, output: {}, factory: () => ({}), }), @@ -50,16 +66,51 @@ describe('createExtensionOverrides', () => { { "$$type": "@backstage/Extension", "attachTo": { - "id": "core", + "id": "app", "input": "apis", }, + "configSchema": undefined, "disabled": false, "factory": [Function], "id": "a", "inputs": {}, "output": {}, + "toString": [Function], + "version": "v1", + }, + { + "$$type": "@backstage/Extension", + "attachTo": { + "id": "app", + "input": "apis", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "id": "b", + "inputs": {}, + "output": {}, + "toString": [Function], + "version": "v1", + }, + { + "$$type": "@backstage/Extension", + "attachTo": { + "id": "app", + "input": "apis", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "id": "k:c/n", + "inputs": {}, + "output": {}, + "toString": [Function], + "version": "v1", }, ], + "featureFlags": [], + "toString": [Function], "version": "v1", } `); @@ -69,8 +120,8 @@ describe('createExtensionOverrides', () => { const overrides = createExtensionOverrides({ extensions: [ createExtension({ - id: 'a', - attachTo: { id: 'core', input: 'apis' }, + namespace: 'a', + attachTo: { id: 'app', input: 'apis' }, output: {}, factory: () => ({}), }), diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts index 2c64229eb9..1f88038944 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionOverrides.ts @@ -14,32 +14,44 @@ * limitations under the License. */ -import { Extension } from './createExtension'; +import { ExtensionDefinition } from './createExtension'; +import { + Extension, + resolveExtensionDefinition, +} from './resolveExtensionDefinition'; +import { ExtensionOverrides, FeatureFlagConfig } from './types'; /** @public */ export interface ExtensionOverridesOptions { - extensions: Extension[]; -} - -/** @public */ -export interface ExtensionOverrides { - $$type: '@backstage/ExtensionOverrides'; + extensions: ExtensionDefinition[]; + featureFlags?: FeatureFlagConfig[]; } /** @internal */ export interface InternalExtensionOverrides extends ExtensionOverrides { - version: string; - extensions: Extension[]; + readonly version: 'v1'; + readonly extensions: Extension[]; + readonly featureFlags: FeatureFlagConfig[]; } /** @public */ export function createExtensionOverrides( options: ExtensionOverridesOptions, ): ExtensionOverrides { + const extensions = options.extensions.map(def => + resolveExtensionDefinition(def), + ); + const featureFlags = options.featureFlags ?? []; return { $$type: '@backstage/ExtensionOverrides', version: 'v1', - extensions: options.extensions, + extensions, + featureFlags, + toString() { + const ex = extensions.map(String).join(','); + const ff = featureFlags.map(f => f.name).join(','); + return `ExtensionOverrides{extensions=[${ex}],featureFlags=[${ff}]}`; + }, } as InternalExtensionOverrides; } @@ -50,12 +62,12 @@ export function toInternalExtensionOverrides( const internal = overrides as InternalExtensionOverrides; if (internal.$$type !== '@backstage/ExtensionOverrides') { throw new Error( - `Invalid translation resource, bad type '${internal.$$type}'`, + `Invalid extension overrides instance, bad type '${internal.$$type}'`, ); } if (internal.version !== 'v1') { throw new Error( - `Invalid translation resource, bad version '${internal.version}'`, + `Invalid extension overrides instance, bad version '${internal.version}'`, ); } return internal; diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts index 42648fcd96..c180dc9719 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts @@ -18,58 +18,45 @@ import React from 'react'; import { createApp } from '@backstage/frontend-app-api'; import { screen } from '@testing-library/react'; import { createSchemaFromZod } from '../schema/createSchemaFromZod'; -import { createPlugin, BackstagePlugin } from './createPlugin'; +import { createPlugin } from './createPlugin'; import { JsonObject } from '@backstage/types'; import { createExtension } from './createExtension'; import { createExtensionDataRef } from './createExtensionDataRef'; import { coreExtensionData } from './coreExtensionData'; import { MockConfigApi, renderWithEffects } from '@backstage/test-utils'; import { createExtensionInput } from './createExtensionInput'; +import { BackstagePlugin } from './types'; const nameExtensionDataRef = createExtensionDataRef('name'); -const TechRadarPage = createExtension({ - id: 'plugin.techradar.page', - attachTo: { id: 'test.output', input: 'names' }, +const Extension1 = createExtension({ + name: '1', + attachTo: { id: 'test/output', input: 'names' }, output: { name: nameExtensionDataRef, }, factory() { - return { name: 'TechRadar' }; + return { name: 'extension-1' }; }, }); -const CatalogPage = createExtension({ - id: 'plugin.catalog.page', - attachTo: { id: 'test.output', input: 'names' }, +const Extension2 = createExtension({ + name: '2', + attachTo: { id: 'test/output', input: 'names' }, output: { name: nameExtensionDataRef, }, configSchema: createSchemaFromZod(z => - z.object({ name: z.string().default('Catalog') }), + z.object({ name: z.string().default('extension-2') }), ), factory({ config }) { return { name: config.name }; }, }); -const TechDocsAddon = createExtension({ - id: 'plugin.techdocs.addon.example', - attachTo: { id: 'plugin.techdocs.page', input: 'addons' }, - output: { - name: nameExtensionDataRef, - }, - configSchema: createSchemaFromZod(z => - z.object({ name: z.string().default('TechDocsAddon') }), - ), - factory({ config }) { - return { name: config.name }; - }, -}); - -const TechDocsPage = createExtension({ - id: 'plugin.techdocs.page', - attachTo: { id: 'test.output', input: 'names' }, +const Extension3 = createExtension({ + name: '3', + attachTo: { id: 'test/output', input: 'names' }, inputs: { addons: createExtensionInput({ name: nameExtensionDataRef, @@ -79,13 +66,43 @@ const TechDocsPage = createExtension({ name: nameExtensionDataRef, }, factory({ inputs }) { - return { name: `TechDocs-${inputs.addons.map(n => n.name).join('-')}` }; + return { + name: `extension-3:${inputs.addons.map(n => n.output.name).join('-')}`, + }; + }, +}); + +const Child = createExtension({ + name: 'child', + attachTo: { id: 'test/3', input: 'addons' }, + output: { + name: nameExtensionDataRef, + }, + configSchema: createSchemaFromZod(z => + z.object({ name: z.string().default('child') }), + ), + factory({ config }) { + return { name: config.name }; + }, +}); + +const Child2 = createExtension({ + name: 'child2', + attachTo: { id: 'test/3', input: 'addons' }, + output: { + name: nameExtensionDataRef, + }, + configSchema: createSchemaFromZod(z => + z.object({ name: z.string().default('child2') }), + ), + factory({ config }) { + return { name: config.name }; }, }); const outputExtension = createExtension({ - id: 'test.output', - attachTo: { id: 'core', input: 'root' }, + name: 'output', + attachTo: { id: 'app', input: 'root' }, inputs: { names: createExtensionInput({ name: nameExtensionDataRef, @@ -97,7 +114,7 @@ const outputExtension = createExtension({ factory({ inputs }) { return { element: React.createElement('span', {}, [ - `Names: ${inputs.names.map(n => n.name).join(', ')}`, + `Names: ${inputs.names.map(n => n.output.name).join(', ')}`, ]), }; }, @@ -112,46 +129,41 @@ function createTestAppRoot({ }) { return createApp({ features, - configLoader: async () => new MockConfigApi(config), + configLoader: async () => ({ config: new MockConfigApi(config) }), }).createRoot(); } describe('createPlugin', () => { it('should create an empty plugin', () => { - const plugin = createPlugin({ id: 'empty' }); + const plugin = createPlugin({ id: 'test' }); expect(plugin).toBeDefined(); + expect(String(plugin)).toBe('Plugin{id=test}'); }); it('should create a plugin with extension instances', async () => { const plugin = createPlugin({ - id: 'empty', - extensions: [TechRadarPage, CatalogPage, outputExtension], + id: 'test', + extensions: [Extension1, Extension2, outputExtension], }); expect(plugin).toBeDefined(); await renderWithEffects( createTestAppRoot({ features: [plugin], - config: { app: { extensions: [{ 'core.layout': false }] } }, + config: { app: { extensions: [{ 'app/root': false }] } }, }), ); await expect( - screen.findByText('Names: TechRadar, Catalog'), + screen.findByText('Names: extension-1, extension-2'), ).resolves.toBeInTheDocument(); }); it('should create a plugin with nested extension instances', async () => { const plugin = createPlugin({ - id: 'empty', - extensions: [ - TechRadarPage, - CatalogPage, - TechDocsPage, - TechDocsAddon, - outputExtension, - ], + id: 'test', + extensions: [Extension1, Extension2, Extension3, Child, outputExtension], }); expect(plugin).toBeDefined(); @@ -161,10 +173,10 @@ describe('createPlugin', () => { config: { app: { extensions: [ - { 'core.layout': false }, + { 'app/root': false }, { - 'plugin.catalog.page': { - config: { name: 'CatalogRenamed' }, + 'test/2': { + config: { name: 'extension-2-renamed' }, }, }, ], @@ -175,8 +187,63 @@ describe('createPlugin', () => { await expect( screen.findByText( - 'Names: TechRadar, CatalogRenamed, TechDocs-TechDocsAddon', + 'Names: extension-1, extension-2-renamed, extension-3:child', ), ).resolves.toBeInTheDocument(); }); + + it('should create a plugin with nested extension instances and multiple children', async () => { + const plugin = createPlugin({ + id: 'test', + extensions: [ + Extension1, + Extension2, + Extension3, + Child, + Child2, + outputExtension, + ], + }); + expect(plugin).toBeDefined(); + + await renderWithEffects( + createTestAppRoot({ + features: [plugin], + config: { + app: { + extensions: [{ 'app/root': false }], + }, + }, + }), + ); + + await expect( + screen.findByText( + 'Names: extension-1, extension-2, extension-3:child-child2', + ), + ).resolves.toBeInTheDocument(); + }); + + it('should throw on duplicate extensions', async () => { + expect(() => + createPlugin({ + id: 'test', + extensions: [Extension1, Extension1], + }), + ).toThrow("Plugin 'test' provided duplicate extensions: test/1"); + + expect(() => + createPlugin({ + id: 'test', + extensions: [ + Extension1, + Extension2, + Extension2, + Extension3, + Extension3, + Extension3, + ], + }), + ).toThrow("Plugin 'test' provided duplicate extensions: test/2, test/3"); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.ts index 84edd954ef..797c46e58e 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.ts @@ -14,14 +14,17 @@ * limitations under the License. */ -import { Extension } from './createExtension'; -import { ExternalRouteRef, RouteRef } from '../routing'; - -/** @public */ -export type AnyRoutes = { [name in string]: RouteRef }; - -/** @public */ -export type AnyExternalRoutes = { [name in string]: ExternalRouteRef }; +import { ExtensionDefinition } from './createExtension'; +import { + Extension, + resolveExtensionDefinition, +} from './resolveExtensionDefinition'; +import { + AnyExternalRoutes, + AnyRoutes, + BackstagePlugin, + FeatureFlagConfig, +} from './types'; /** @public */ export interface PluginOptions< @@ -31,19 +34,18 @@ export interface PluginOptions< id: string; routes?: Routes; externalRoutes?: ExternalRoutes; - extensions?: Extension[]; + extensions?: ExtensionDefinition[]; + featureFlags?: FeatureFlagConfig[]; } /** @public */ -export interface BackstagePlugin< +export interface InternalBackstagePlugin< Routes extends AnyRoutes = AnyRoutes, ExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, -> { - $$type: '@backstage/BackstagePlugin'; - id: string; - extensions: Extension[]; - routes: Routes; - externalRoutes: ExternalRoutes; +> extends BackstagePlugin { + readonly version: 'v1'; + readonly extensions: Extension[]; + readonly featureFlags: FeatureFlagConfig[]; } /** @public */ @@ -53,11 +55,51 @@ export function createPlugin< >( options: PluginOptions, ): BackstagePlugin { + const extensions = (options.extensions ?? []).map(def => + resolveExtensionDefinition(def, { namespace: options.id }), + ); + + const extensionIds = extensions.map(e => e.id); + if (extensionIds.length !== new Set(extensionIds).size) { + const duplicates = Array.from( + new Set( + extensionIds.filter((id, index) => extensionIds.indexOf(id) !== index), + ), + ); + // TODO(Rugvip): This could provide some more information about the kind + name of the extensions + throw new Error( + `Plugin '${options.id}' provided duplicate extensions: ${duplicates.join( + ', ', + )}`, + ); + } + return { - ...options, + $$type: '@backstage/BackstagePlugin', + version: 'v1', + id: options.id, routes: options.routes ?? ({} as Routes), externalRoutes: options.externalRoutes ?? ({} as ExternalRoutes), - extensions: options.extensions ?? [], - $$type: '@backstage/BackstagePlugin', - }; + featureFlags: options.featureFlags ?? [], + extensions, + toString() { + return `Plugin{id=${options.id}}`; + }, + } as InternalBackstagePlugin; +} + +/** @internal */ +export function toInternalBackstagePlugin( + plugin: BackstagePlugin, +): InternalBackstagePlugin { + const internal = plugin as InternalBackstagePlugin; + if (internal.$$type !== '@backstage/BackstagePlugin') { + throw new Error(`Invalid plugin instance, bad type '${internal.$$type}'`); + } + if (internal.version !== 'v1') { + throw new Error( + `Invalid plugin instance, bad version '${internal.version}'`, + ); + } + return internal; } diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index ec685d6c81..61f821c7ce 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -14,13 +14,14 @@ * limitations under the License. */ -export { coreExtensionData, type NavTarget } from './coreExtensionData'; +export { coreExtensionData } from './coreExtensionData'; export { createExtension, - type Extension, + type ExtensionDefinition, type CreateExtensionOptions, type ExtensionDataValues, - type ExtensionInputValues, + type ResolvedExtensionInput, + type ResolvedExtensionInputs, type AnyExtensionInputMap, type AnyExtensionDataMap, } from './createExtension'; @@ -33,15 +34,17 @@ export { type ExtensionDataRef, type ConfigurableExtensionDataRef, } from './createExtensionDataRef'; -export { - createPlugin, - type BackstagePlugin, - type PluginOptions, - type AnyRoutes, - type AnyExternalRoutes, -} from './createPlugin'; +export { createPlugin, type PluginOptions } from './createPlugin'; export { createExtensionOverrides, - type ExtensionOverrides, type ExtensionOverridesOptions, } from './createExtensionOverrides'; +export { type Extension } from './resolveExtensionDefinition'; +export { + type AnyRoutes, + type AnyExternalRoutes, + type BackstagePlugin, + type ExtensionOverrides, + type FeatureFlagConfig, + type FrontendFeature, +} from './types'; diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts new file mode 100644 index 0000000000..8970cfe168 --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.test.ts @@ -0,0 +1,58 @@ +/* + * 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 { ExtensionDefinition } from './createExtension'; +import { resolveExtensionDefinition } from './resolveExtensionDefinition'; + +const baseDef = { + $$type: '@backstage/ExtensionDefinition', + version: 'v1', + attachTo: { id: '', input: '' }, + disabled: false, +}; + +describe('resolveExtensionDefinition', () => { + it.each([ + [{ namespace: 'ns' }, 'ns'], + [{ namespace: 'n' }, 'n'], + [{ namespace: 'ns', name: 'n' }, 'ns/n'], + [{ kind: 'k', namespace: 'ns' }, 'k:ns'], + [{ kind: 'k', namespace: 'ns', name: 'n' }, 'k:ns/n'], + ])(`should resolve extension IDs %s`, (definition, expected) => { + const resolved = resolveExtensionDefinition({ + ...baseDef, + ...definition, + } as ExtensionDefinition); + expect(resolved.id).toBe(expected); + expect(String(resolved)).toBe(`Extension{id=${expected}}`); + }); + + it('should fail to resolve extension ID without namespace', () => { + expect(() => + resolveExtensionDefinition({ + ...baseDef, + kind: 'k', + } as ExtensionDefinition), + ).toThrow( + 'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=k namespace=undefined name=undefined', + ); + expect(() => + resolveExtensionDefinition(baseDef as ExtensionDefinition), + ).toThrow( + 'Extension must declare an explicit namespace or name as it could not be resolved from context, kind=undefined namespace=undefined name=undefined', + ); + }); +}); diff --git a/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts new file mode 100644 index 0000000000..0a44de4ab4 --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts @@ -0,0 +1,95 @@ +/* + * 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 { AppNode } from '../apis'; +import { + AnyExtensionDataMap, + AnyExtensionInputMap, + ExtensionDataValues, + ExtensionDefinition, + ResolvedExtensionInputs, + toInternalExtensionDefinition, +} from './createExtension'; +import { PortableSchema } from '../schema'; + +/** @public */ +export interface Extension { + $$type: '@backstage/Extension'; + readonly id: string; + readonly attachTo: { id: string; input: string }; + readonly disabled: boolean; + readonly configSchema?: PortableSchema; +} + +/** @internal */ +export interface InternalExtension extends Extension { + readonly version: 'v1'; + readonly inputs: AnyExtensionInputMap; + readonly output: AnyExtensionDataMap; + factory(options: { + node: AppNode; + config: TConfig; + inputs: ResolvedExtensionInputs; + }): ExtensionDataValues; +} + +/** @internal */ +export function toInternalExtension( + overrides: Extension, +): InternalExtension { + const internal = overrides as InternalExtension; + if (internal.$$type !== '@backstage/Extension') { + throw new Error( + `Invalid extension instance, bad type '${internal.$$type}'`, + ); + } + if (internal.version !== 'v1') { + throw new Error( + `Invalid extension instance, bad version '${internal.version}'`, + ); + } + return internal; +} + +/** @internal */ +export function resolveExtensionDefinition( + definition: ExtensionDefinition, + context?: { namespace?: string }, +): Extension { + const internalDefinition = toInternalExtensionDefinition(definition); + const { name, kind, namespace: _, ...rest } = internalDefinition; + const namespace = internalDefinition.namespace ?? context?.namespace; + + const namePart = + name && namespace ? `${namespace}/${name}` : namespace || name; + if (!namePart) { + throw new Error( + `Extension must declare an explicit namespace or name as it could not be resolved from context, kind=${kind} namespace=${namespace} name=${name}`, + ); + } + + const id = kind ? `${kind}:${namePart}` : namePart; + + return { + ...rest, + $$type: '@backstage/Extension', + version: 'v1', + id, + toString() { + return `Extension{id=${id}}`; + }, + } as Extension; +} diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts new file mode 100644 index 0000000000..7e0650e06c --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -0,0 +1,52 @@ +/* + * 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 { ExternalRouteRef, RouteRef, SubRouteRef } from '../routing'; + +/** + * Feature flag configuration. + * + * @public + */ +export type FeatureFlagConfig = { + /** Feature flag name */ + name: string; +}; + +/** @public */ +export type AnyRoutes = { [name in string]: RouteRef | SubRouteRef }; + +/** @public */ +export type AnyExternalRoutes = { [name in string]: ExternalRouteRef }; + +/** @public */ +export interface BackstagePlugin< + Routes extends AnyRoutes = AnyRoutes, + ExternalRoutes extends AnyExternalRoutes = AnyExternalRoutes, +> { + readonly $$type: '@backstage/BackstagePlugin'; + readonly id: string; + readonly routes: Routes; + readonly externalRoutes: ExternalRoutes; +} + +/** @public */ +export interface ExtensionOverrides { + readonly $$type: '@backstage/ExtensionOverrides'; +} + +/** @public */ +export type FrontendFeature = BackstagePlugin | ExtensionOverrides; diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index d112822d13..31799581f6 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,112 @@ # @backstage/frontend-test-utils +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.6.0-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/test-utils@1.5.0-next.0 + - @backstage/types@1.1.1 + +## 0.1.1 + +### Patch Changes + +- f7566f9: Updates to reflect the `app/router` extension having been renamed to `app/root`. +- 516fd3e: Updated README to reflect release status +- c97fa1c: Added `elements`, `wrappers`, and `router` inputs to `app/root`, that let you add things to the root of the React tree above the layout. You can use the `createAppRootElementExtension`, `createAppRootWrapperExtension`, and `createRouterExtension` extension creator, respectively, to conveniently create such extensions. These are all optional, and if you do not supply a router a default one will be used (`BrowserRouter` in regular runs, `MemoryRouter` in tests/CI). +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/frontend-app-api@0.5.0 + - @backstage/test-utils@1.4.7 + - @backstage/types@1.1.1 + +## 0.1.1-next.2 + +### Patch Changes + +- 516fd3e: Updated README to reflect release status +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/frontend-app-api@0.4.1-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.4.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/test-utils@1.4.7-next.1 + - @backstage/types@1.1.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/frontend-app-api@0.4.1-next.0 + - @backstage/test-utils@1.4.7-next.0 + - @backstage/types@1.1.1 + +## 0.1.0 + +### Minor Changes + +- 59fabd5: New testing utility library for `@backstage/frontend-app-api` and `@backstage/frontend-plugin-api`. +- af7bc3e: Switched all core extensions to instead use the namespace `'app'`. + +### Patch Changes + +- 59fabd5: Added `createExtensionTester` for rendering extensions in tests. +- 7e4b0db: The `createExtensionTester` helper is now able to render more than one route in the test app. +- 818eea4: Updates for compatibility with the new extension IDs. +- b9aa6e4: Migrate `renderInTestApp` to `@backstage/frontend-test-utils` for testing individual React components in an app. +- e539735: Updates for `core.router` addition. +- c21c9cf: Re-export mock API implementations as well as `TestApiProvider`, `TestApiRegistry`, `withLogCollector`, and `setupRequestMockHandlers` from `@backstage/test-utils`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/frontend-app-api@0.4.0 + - @backstage/test-utils@1.4.6 + - @backstage/types@1.1.1 + +## 0.1.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.4.0-next.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/test-utils@1.4.6-next.2 + - @backstage/types@1.1.1 + +## 0.1.0-next.2 + +### Patch Changes + +- 818eea4: Updates for compatibility with the new extension IDs. +- b9aa6e4: Migrate `renderInTestApp` to `@backstage/frontend-test-utils` for testing individual React components in an app. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/frontend-app-api@0.4.0-next.2 + - @backstage/test-utils@1.4.6-next.2 + - @backstage/types@1.1.1 + +## 0.1.0-next.1 + +### Patch Changes + +- e539735435: Updates for `core.router` addition. +- c21c9cf07b: Re-export mock API implementations as well as `TestApiProvider`, `TestApiRegistry`, `withLogCollector`, and `setupRequestMockHandlers` from `@backstage/test-utils`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/frontend-app-api@0.4.0-next.1 + - @backstage/test-utils@1.4.6-next.1 + - @backstage/types@1.1.1 + ## 0.1.0-next.0 ### Minor Changes diff --git a/packages/frontend-test-utils/README.md b/packages/frontend-test-utils/README.md index 50f853d9ed..455d112773 100644 --- a/packages/frontend-test-utils/README.md +++ b/packages/frontend-test-utils/README.md @@ -1,5 +1,7 @@ # @backstage/frontend-test-utils +**The [new frontend system](https://backstage.io/docs/frontend-system/) that this package is part of is in alpha, and we do not yet recommend using it for production deployments** + Contains utilities that can be used when testing frontend features such as extensions. ## Installation diff --git a/packages/frontend-test-utils/api-report.md b/packages/frontend-test-utils/api-report.md index 8d7837b90a..2605499a96 100644 --- a/packages/frontend-test-utils/api-report.md +++ b/packages/frontend-test-utils/api-report.md @@ -3,23 +3,44 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { Extension } from '@backstage/frontend-plugin-api'; +/// + +import { AnalyticsApi } from '@backstage/frontend-plugin-api'; +import { AnalyticsEvent } from '@backstage/frontend-plugin-api'; +import { ErrorWithContext } from '@backstage/test-utils'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { JsonObject } from '@backstage/types'; +import { MockConfigApi } from '@backstage/test-utils'; +import { MockErrorApi } from '@backstage/test-utils'; +import { MockErrorApiOptions } from '@backstage/test-utils'; +import { MockFetchApi } from '@backstage/test-utils'; +import { MockFetchApiOptions } from '@backstage/test-utils'; +import { MockPermissionApi } from '@backstage/test-utils'; +import { MockStorageApi } from '@backstage/test-utils'; +import { MockStorageBucket } from '@backstage/test-utils'; import { RenderResult } from '@testing-library/react'; +import { RouteRef } from '@backstage/frontend-plugin-api'; +import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { TestApiProvider } from '@backstage/test-utils'; +import { TestApiProviderProps } from '@backstage/test-utils'; +import { TestApiRegistry } from '@backstage/test-utils'; +import { withLogCollector } from '@backstage/test-utils'; // @public (undocumented) export function createExtensionTester( - subject: Extension, + subject: ExtensionDefinition, options?: { config?: TConfig; }, ): ExtensionTester; +export { ErrorWithContext }; + // @public (undocumented) export class ExtensionTester { // (undocumented) add( - extension: Extension, + extension: ExtensionDefinition, options?: { config?: TConfig; }, @@ -27,4 +48,51 @@ export class ExtensionTester { // (undocumented) render(options?: { config?: JsonObject }): RenderResult; } + +// @public +export class MockAnalyticsApi implements AnalyticsApi { + // (undocumented) + captureEvent(event: AnalyticsEvent): void; + // (undocumented) + getEvents(): AnalyticsEvent[]; +} + +export { MockConfigApi }; + +export { MockErrorApi }; + +export { MockErrorApiOptions }; + +export { MockFetchApi }; + +export { MockFetchApiOptions }; + +export { MockPermissionApi }; + +export { MockStorageApi }; + +export { MockStorageBucket }; + +// @public +export function renderInTestApp( + element: JSX.Element, + options?: TestAppOptions, +): RenderResult; + +export { setupRequestMockHandlers }; + +export { TestApiProvider }; + +export { TestApiProviderProps }; + +export { TestApiRegistry }; + +// @public +export type TestAppOptions = { + mountedRoutes?: { + [path: string]: RouteRef; + }; +}; + +export { withLogCollector }; ``` diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 2434eec37c..8b9b4382e8 100644 --- a/packages/frontend-test-utils/package.json +++ b/packages/frontend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-test-utils", - "version": "0.1.0-next.0", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,7 +24,8 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@testing-library/jest-dom": "^6.0.0" + "@testing-library/jest-dom": "^6.0.0", + "@types/react": "*" }, "files": [ "dist" @@ -37,6 +38,7 @@ }, "peerDependencies": { "@testing-library/react": "^12.1.3 || ^13.0.0 || ^14.0.0", - "react": "^16.13.1 || ^17.0.0 || ^18.0.0" + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-router-dom": "6.0.0-beta.0 || ^6.3.0" } } diff --git a/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.test.ts b/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.test.ts new file mode 100644 index 0000000000..a84ffd91aa --- /dev/null +++ b/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { MockAnalyticsApi } from './MockAnalyticsApi'; + +describe('MockAnalyticsApi', () => { + const context = { + pluginId: 'some-plugin', + extensionId: 'some-extension', + }; + + it('should collect events', () => { + const api = new MockAnalyticsApi(); + + api.captureEvent({ action: 'action-1', subject: 'subject-1', context }); + api.captureEvent({ + action: 'action-2', + subject: 'subject-2', + value: 42, + context, + }); + api.captureEvent({ + action: 'action-3', + subject: 'subject-3', + value: 1337, + attributes: { some: 'context' }, + context, + }); + + expect(api.getEvents()[0]).toMatchObject({ + subject: 'subject-1', + action: 'action-1', + context, + }); + expect(api.getEvents()[1]).toMatchObject({ + subject: 'subject-2', + action: 'action-2', + value: 42, + context, + }); + expect(api.getEvents()[2]).toMatchObject({ + subject: 'subject-3', + action: 'action-3', + value: 1337, + context, + attributes: { + some: 'context', + }, + }); + }); +}); diff --git a/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.ts b/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.ts new file mode 100644 index 0000000000..f6ccf4e9d1 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/AnalyticsApi/MockAnalyticsApi.ts @@ -0,0 +1,43 @@ +/* + * 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 { AnalyticsApi, AnalyticsEvent } from '@backstage/frontend-plugin-api'; + +/** + * Mock implementation of {@link frontend-plugin-api#AnalyticsApi} with helpers to ensure that events are sent correctly. + * Use getEvents in tests to verify captured events. + * + * @public + */ +export class MockAnalyticsApi implements AnalyticsApi { + private events: AnalyticsEvent[] = []; + + captureEvent(event: AnalyticsEvent) { + const { action, subject, value, attributes, context } = event; + + this.events.push({ + action, + subject, + context, + ...(value !== undefined ? { value } : {}), + ...(attributes !== undefined ? { attributes } : {}), + }); + } + + getEvents(): AnalyticsEvent[] { + return this.events; + } +} diff --git a/plugins/scaffolder/src/legacy/MultistepJsonForm/FieldOverrides/index.ts b/packages/frontend-test-utils/src/apis/AnalyticsApi/index.ts similarity index 91% rename from plugins/scaffolder/src/legacy/MultistepJsonForm/FieldOverrides/index.ts rename to packages/frontend-test-utils/src/apis/AnalyticsApi/index.ts index d6d0bf3499..dc5fd062aa 100644 --- a/plugins/scaffolder/src/legacy/MultistepJsonForm/FieldOverrides/index.ts +++ b/packages/frontend-test-utils/src/apis/AnalyticsApi/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { DescriptionField } from './DescriptionField'; + +export { MockAnalyticsApi } from './MockAnalyticsApi'; diff --git a/packages/frontend-test-utils/src/apis/index.ts b/packages/frontend-test-utils/src/apis/index.ts new file mode 100644 index 0000000000..1230be9be0 --- /dev/null +++ b/packages/frontend-test-utils/src/apis/index.ts @@ -0,0 +1,29 @@ +/* + * 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 { + MockConfigApi, + type ErrorWithContext, + MockErrorApi, + type MockErrorApiOptions, + MockFetchApi, + type MockFetchApiOptions, + MockPermissionApi, + MockStorageApi, + type MockStorageBucket, +} from '@backstage/test-utils'; + +export { MockAnalyticsApi } from './AnalyticsApi/MockAnalyticsApi'; diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx index 946dd3cb0c..f59d44f6d7 100644 --- a/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx +++ b/packages/frontend-test-utils/src/app/createExtensionTester.test.tsx @@ -14,55 +14,219 @@ * limitations under the License. */ +import React, { useCallback } from 'react'; +import { Link } from 'react-router-dom'; +import { fireEvent, screen, waitFor } from '@testing-library/react'; import { + analyticsApiRef, + configApiRef, coreExtensionData, + createApiExtension, + createApiFactory, createExtension, + createSchemaFromZod, + useAnalytics, + useApi, } from '@backstage/frontend-plugin-api'; -import { screen } from '@testing-library/react'; -import React from 'react'; +import { MockAnalyticsApi } from '../apis'; import { createExtensionTester } from './createExtensionTester'; describe('createExtensionTester', () => { - it('should render a simple extension', async () => { - createExtensionTester( - createExtension({ - id: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - output: { element: coreExtensionData.reactElement }, - factory: () => ({ element:
test
}), - }), - ).render(); + const defaultDefinition = { + namespace: 'test', + attachTo: { id: 'ignored', input: 'ignored' }, + output: { element: coreExtensionData.reactElement }, + factory: () => ({ element:
test
}), + }; + it('should render a simple extension', async () => { + const extension = createExtension(defaultDefinition); + const tester = createExtensionTester(extension); + tester.render(); await expect(screen.findByText('test')).resolves.toBeInTheDocument(); }); it('should render an extension even if disabled by default', async () => { - createExtensionTester( - createExtension({ - id: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - disabled: true, - output: { element: coreExtensionData.reactElement }, - factory: () => ({ element:
test
}), - }), - ).render(); - + const extension = createExtension({ + ...defaultDefinition, + disabled: true, + }); + const tester = createExtensionTester(extension); + tester.render(); await expect(screen.findByText('test')).resolves.toBeInTheDocument(); }); it("should fail to render an extension that doesn't output a react element", async () => { - expect(() => - createExtensionTester( - createExtension({ - id: 'test', - attachTo: { id: 'ignored', input: 'ignored' }, - disabled: true, - output: { path: coreExtensionData.routePath }, - factory: () => ({ path: '/foo' }), - }), - ).render(), - ).toThrow( - "Failed to instantiate extension 'core', input 'root' did not receive required extension data 'core.reactElement' from extension 'test'", + const extension = createExtension({ + ...defaultDefinition, + output: { path: coreExtensionData.routePath }, + factory: () => ({ path: '/foo' }), + }); + const tester = createExtensionTester(extension); + expect(() => tester.render()).toThrowErrorMatchingInlineSnapshot( + `"Failed to instantiate extension 'app/routes', extension 'test' could not be attached because its output data ('core.routing.path') does not match what the input 'routes' requires ('core.routing.path', 'core.reactElement')"`, + ); + }); + + it('should render multiple extensions', async () => { + const indexPageExtension = createExtension({ + ...defaultDefinition, + factory: () => ({ + element: ( +
+ Index page See details +
+ ), + }), + }); + const detailsPageExtension = createExtension({ + ...defaultDefinition, + name: 'details', + attachTo: { id: 'app/routes', input: 'routes' }, + output: { + path: coreExtensionData.routePath, + element: coreExtensionData.reactElement, + }, + factory: () => ({ path: '/details', element:
Details page
}), + }); + + const tester = createExtensionTester(indexPageExtension); + tester.add(detailsPageExtension); + tester.render(); + + await expect(screen.findByText('Index page')).resolves.toBeInTheDocument(); + + fireEvent.click(screen.getByRole('link', { name: 'See details' })); + + await expect( + screen.findByText('Details page'), + ).resolves.toBeInTheDocument(); + }); + + it('should accepts a custom config', async () => { + const indexPageExtension = createExtension({ + ...defaultDefinition, + configSchema: createSchemaFromZod(z => + z.object({ title: z.string().optional() }), + ), + factory: ({ config }) => { + const Component = () => { + const configApi = useApi(configApiRef); + const appTitle = configApi.getOptionalString('app.title'); + return ( +
+

{appTitle ?? 'Backstafe app'}

+

{config.title ?? 'Index page'}

+ See details +
+ ); + }; + return { + element: , + }; + }, + }); + + const detailsPageExtension = createExtension({ + ...defaultDefinition, + name: 'details', + attachTo: { id: 'app/routes', input: 'routes' }, + configSchema: createSchemaFromZod(z => + z.object({ title: z.string().optional() }), + ), + output: { + path: coreExtensionData.routePath, + element: coreExtensionData.reactElement, + }, + factory: ({ config }) => ({ + path: '/details', + element:
{config.title ?? 'Details page'}
, + }), + }); + + const tester = createExtensionTester(indexPageExtension, { + config: { title: 'Custom index' }, + }); + + tester.add(detailsPageExtension, { + config: { title: 'Custom details' }, + }); + + tester.render({ + config: { + app: { + title: 'Custom app', + }, + }, + }); + + await expect( + screen.findByRole('heading', { name: 'Custom app' }), + ).resolves.toBeInTheDocument(); + + await expect( + screen.findByRole('heading', { name: 'Custom index' }), + ).resolves.toBeInTheDocument(); + + fireEvent.click(screen.getByRole('link', { name: 'See details' })); + + await expect( + screen.findByText('Custom details'), + ).resolves.toBeInTheDocument(); + }); + + it('should capture click events in analytics', async () => { + // Mocking the analytics api implementation + const analyticsApiMock = new MockAnalyticsApi(); + + const analyticsApiOverride = createApiExtension({ + factory: createApiFactory({ + api: analyticsApiRef, + deps: {}, + factory: () => analyticsApiMock, + }), + }); + + const indexPageExtension = createExtension({ + ...defaultDefinition, + factory: () => { + const Component = () => { + const analyticsApi = useAnalytics(); + const handleClick = useCallback(() => { + analyticsApi.captureEvent('click', 'See details'); + }, [analyticsApi]); + return ( +
+ Index Page + +
+ ); + }; + + return { + element: , + }; + }, + }); + + const tester = createExtensionTester(indexPageExtension); + + // Overriding the analytics api extension + tester.add(analyticsApiOverride); + + tester.render(); + + fireEvent.click(await screen.findByRole('button', { name: 'See details' })); + + await waitFor(() => + expect(analyticsApiMock.getEvents()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + action: 'click', + subject: 'See details', + }), + ]), + ), ); }); }); diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.ts b/packages/frontend-test-utils/src/app/createExtensionTester.ts deleted file mode 100644 index 34833dfc69..0000000000 --- a/packages/frontend-test-utils/src/app/createExtensionTester.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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 { createSpecializedApp } from '@backstage/frontend-app-api'; -import { Extension, createPlugin } from '@backstage/frontend-plugin-api'; -import { MockConfigApi } from '@backstage/test-utils'; -import { JsonArray, JsonObject, JsonValue } from '@backstage/types'; -import { RenderResult, render } from '@testing-library/react'; - -/** @public */ -export class ExtensionTester { - /** @internal */ - static forSubject( - subject: Extension, - options?: { config?: TConfig }, - ): ExtensionTester { - const tester = new ExtensionTester(); - tester.add(subject, options); - return tester; - } - - readonly #extensions = new Array<{ - extension: Extension; - config?: JsonValue; - }>(); - - add( - extension: Extension, - options?: { config?: TConfig }, - ): ExtensionTester { - this.#extensions.push({ - extension, - config: options?.config as JsonValue, - }); - - return this; - } - - render(options?: { config?: JsonObject }): RenderResult { - const { config = {} } = options ?? {}; - - const [subject, ...rest] = this.#extensions; - if (!subject) { - throw new Error( - 'No subject found. At least one extension should be added to the tester.', - ); - } - - const extensionsConfig: JsonArray = [ - ...rest.map(entry => ({ - [entry.extension.id]: { - config: entry.config, - }, - })), - { - [subject.extension.id]: { - attachTo: { id: 'core', input: 'root' }, - config: subject.config, - disabled: false, - }, - }, - { - 'core.layout': false, - }, - { - 'core.nav': false, - }, - { - 'core.routes': false, - }, - ]; - - const finalConfig = { - ...config, - app: { - ...(typeof config.app === 'object' ? config.app : undefined), - extensions: extensionsConfig, - }, - }; - - const app = createSpecializedApp({ - features: [ - createPlugin({ - id: 'test', - extensions: this.#extensions.map(entry => entry.extension), - }), - ], - config: new MockConfigApi(finalConfig), - }); - - return render(app.createRoot()); - } -} - -/** @public */ -export function createExtensionTester( - subject: Extension, - options?: { config?: TConfig }, -): ExtensionTester { - return ExtensionTester.forSubject(subject, options); -} diff --git a/packages/frontend-test-utils/src/app/createExtensionTester.tsx b/packages/frontend-test-utils/src/app/createExtensionTester.tsx new file mode 100644 index 0000000000..a98879fbba --- /dev/null +++ b/packages/frontend-test-utils/src/app/createExtensionTester.tsx @@ -0,0 +1,203 @@ +/* + * 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 { MemoryRouter, Link } from 'react-router-dom'; +import { RenderResult, render } from '@testing-library/react'; +import { createSpecializedApp } from '@backstage/frontend-app-api'; +import { + ExtensionDefinition, + IconComponent, + RouteRef, + coreExtensionData, + createExtension, + createExtensionInput, + createExtensionOverrides, + createNavItemExtension, + createRouterExtension, + useRouteRef, +} from '@backstage/frontend-plugin-api'; +import { MockConfigApi } from '@backstage/test-utils'; +import { JsonArray, JsonObject, JsonValue } from '@backstage/types'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/createExtension'; + +const NavItem = (props: { + routeRef: RouteRef; + title: string; + icon: IconComponent; +}) => { + const { routeRef, title, icon: Icon } = props; + const to = useRouteRef(routeRef)(); + return ( +
  • + + {title} + +
  • + ); +}; + +const TestAppNavExtension = createExtension({ + namespace: 'app', + name: 'nav', + attachTo: { id: 'app/layout', input: 'nav' }, + inputs: { + items: createExtensionInput({ + target: createNavItemExtension.targetDataRef, + }), + }, + output: { + element: coreExtensionData.reactElement, + }, + factory({ inputs }) { + return { + element: ( + + ), + }; + }, +}); + +/** @public */ +export class ExtensionTester { + /** @internal */ + static forSubject( + subject: ExtensionDefinition, + options?: { config?: TConfig }, + ): ExtensionTester { + const tester = new ExtensionTester(); + const { output, factory, ...rest } = toInternalExtensionDefinition(subject); + // attaching to app/routes to render as index route + const extension = createExtension({ + ...rest, + attachTo: { id: 'app/routes', input: 'routes' }, + output: { + ...output, + path: coreExtensionData.routePath, + }, + factory: params => ({ + ...factory(params), + path: '/', + }), + }); + tester.add(extension, options); + return tester; + } + + readonly #extensions = new Array<{ + id: string; + definition: ExtensionDefinition; + config?: JsonValue; + }>(); + + add( + extension: ExtensionDefinition, + options?: { config?: TConfig }, + ): ExtensionTester { + const { name, namespace } = extension; + + const definition = { + ...extension, + // setting name "test" as fallback + name: !namespace && !name ? 'test' : name, + }; + + const { id } = resolveExtensionDefinition(definition); + + this.#extensions.push({ + id, + definition, + config: options?.config as JsonValue, + }); + + return this; + } + + render(options?: { config?: JsonObject }): RenderResult { + const { config = {} } = options ?? {}; + + const [subject, ...rest] = this.#extensions; + if (!subject) { + throw new Error( + 'No subject found. At least one extension should be added to the tester.', + ); + } + + const extensionsConfig: JsonArray = [ + ...rest.map(extension => ({ + [extension.id]: { + config: extension.config, + }, + })), + { + [subject.id]: { + config: subject.config, + disabled: false, + }, + }, + ]; + + const finalConfig = { + ...config, + app: { + ...(typeof config.app === 'object' ? config.app : undefined), + extensions: extensionsConfig, + }, + }; + + const app = createSpecializedApp({ + features: [ + createExtensionOverrides({ + extensions: [ + ...this.#extensions.map(extension => extension.definition), + TestAppNavExtension, + createRouterExtension({ + namespace: 'test', + Component: ({ children }) => ( + {children} + ), + }), + ], + }), + ], + config: new MockConfigApi(finalConfig), + }); + + return render(app.createRoot()); + } +} + +/** @public */ +export function createExtensionTester( + subject: ExtensionDefinition, + options?: { config?: TConfig }, +): ExtensionTester { + return ExtensionTester.forSubject(subject, options); +} diff --git a/packages/frontend-test-utils/src/app/index.ts b/packages/frontend-test-utils/src/app/index.ts index 3152fac6e7..2351561022 100644 --- a/packages/frontend-test-utils/src/app/index.ts +++ b/packages/frontend-test-utils/src/app/index.ts @@ -18,3 +18,5 @@ export { createExtensionTester, type ExtensionTester, } from './createExtensionTester'; + +export { renderInTestApp, type TestAppOptions } from './renderInTestApp'; diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx new file mode 100644 index 0000000000..a56ae2f140 --- /dev/null +++ b/packages/frontend-test-utils/src/app/renderInTestApp.test.tsx @@ -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 React, { useCallback } from 'react'; +import { screen, fireEvent } from '@testing-library/react'; +import { + MockAnalyticsApi, + TestApiProvider, +} from '@backstage/frontend-test-utils'; +import { analyticsApiRef, useAnalytics } from '@backstage/frontend-plugin-api'; +import { renderInTestApp } from './renderInTestApp'; + +describe('renderInTestApp', () => { + it('should render the given component in a page', async () => { + const IndexPage = () =>
    Index Page
    ; + renderInTestApp(); + expect(screen.getByText('Index Page')).toBeInTheDocument(); + }); + + it('should works with apis provider', async () => { + const IndexPage = () => { + const analyticsApi = useAnalytics(); + const handleClick = useCallback(() => { + analyticsApi.captureEvent('click', 'See details'); + }, [analyticsApi]); + return ( +
    + Index Page + + See details + +
    + ); + }; + + const analyticsApiMock = new MockAnalyticsApi(); + + renderInTestApp( + + + , + ); + + fireEvent.click(screen.getByRole('link', { name: 'See details' })); + + expect(analyticsApiMock.getEvents()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + action: 'click', + subject: 'See details', + }), + ]), + ); + }); +}); diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx new file mode 100644 index 0000000000..e0483f68d8 --- /dev/null +++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx @@ -0,0 +1,88 @@ +/* + * 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 { + RouteRef, + coreExtensionData, + createExtension, +} from '@backstage/frontend-plugin-api'; +import { createExtensionTester } from './createExtensionTester'; + +/** + * Options to customize the behavior of the test app. + * @public + */ +export type TestAppOptions = { + /** + * An object of paths to mount route ref on, with the key being the path and the value + * being the RouteRef that the path will be bound to. This allows the route refs to be + * used by `useRouteRef` in the rendered elements. + * + * @example + * ```ts + * renderInTestApp(, { + * mountedRoutes: { + * '/my-path': myRouteRef, + * } + * }) + * // ... + * const link = useRouteRef(myRouteRef) + * ``` + */ + mountedRoutes?: { [path: string]: RouteRef }; +}; + +/** + * @public + * Renders the given element in a test app, for use in unit tests. + */ +export function renderInTestApp( + element: JSX.Element, + options?: TestAppOptions, +) { + const extension = createExtension({ + namespace: 'test', + attachTo: { id: 'app', input: 'root' }, + output: { + element: coreExtensionData.reactElement, + }, + factory: () => ({ element }), + }); + const tester = createExtensionTester(extension); + + if (options?.mountedRoutes) { + for (const [path, routeRef] of Object.entries(options.mountedRoutes)) { + // TODO(Rugvip): add support for external route refs + tester.add( + createExtension({ + kind: 'test-route', + name: path, + attachTo: { id: 'app/root', input: 'elements' }, + output: { + element: coreExtensionData.reactElement, + path: coreExtensionData.routePath, + routeRef: coreExtensionData.routeRef, + }, + factory() { + return { element: , path, routeRef }; + }, + }), + ); + } + } + return tester.render(); +} diff --git a/packages/frontend-test-utils/src/index.ts b/packages/frontend-test-utils/src/index.ts index 6a28cc2abe..0dab8e75b7 100644 --- a/packages/frontend-test-utils/src/index.ts +++ b/packages/frontend-test-utils/src/index.ts @@ -20,4 +20,12 @@ * Contains utilities that can be used when testing frontend features such as extensions. */ +export * from './apis'; export * from './app'; + +export { TestApiProvider, TestApiRegistry } from '@backstage/test-utils'; +export type { TestApiProviderProps } from '@backstage/test-utils'; + +export { withLogCollector } from '@backstage/test-utils'; + +export { setupRequestMockHandlers } from '@backstage/test-utils'; diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 21a4a75e75..fd6a47e4e7 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/integration-react +## 1.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + +## 1.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + +## 1.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + +## 1.1.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/config@1.1.1 + ## 1.1.22-next.0 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index f3e83da77e..f1592628ab 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration-react", "description": "Frontend package for managing integrations towards external systems", - "version": "1.1.22-next.0", + "version": "1.1.23", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index a3392a512d..8dd9ec3915 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/integration +## 1.8.0 + +### Minor Changes + +- 870db76: Implemented `readTree` for Gitea provider to support TechDocs functionality + +### Patch Changes + +- 99fb541: Updated dependency `@azure/identity` to `^4.0.0`. +- Updated dependencies + - @backstage/config@1.1.1 + +## 1.8.0-next.1 + +### Patch Changes + +- 99fb54183b: Updated dependency `@azure/identity` to `^4.0.0`. +- Updated dependencies + - @backstage/config@1.1.1 + ## 1.8.0-next.0 ### Minor Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index e6d56110c9..7e57293903 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration", "description": "Helpers for managing integrations towards external systems", - "version": "1.8.0-next.0", + "version": "1.8.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,7 +33,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@azure/identity": "^3.2.1", + "@azure/identity": "^4.0.0", "@backstage/config": "workspace:^", "@octokit/auth-app": "^4.0.0", "@octokit/rest": "^19.0.3", diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index b439c08fc6..3d54277485 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,156 @@ # @backstage/repo-tools +## 0.6.0-next.0 + +### Minor Changes + +- 04907c3: Updates the OpenAPI client template to support the new format for identifying plugin ID. You should now use `info.title` like so, + + ```diff + info: + + title: yourPluginId + - title: @internal/plugin-*-backend + + servers: + - / + - - yourPluginId + ``` + +- b10c603: Add support for `oneOf` in client generated by `schema openapi generate-client`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/cli-node@0.2.2 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## 0.5.2 + +### Patch Changes + +- 883782e: Updated the OpenAPI template to export the `TypedResponse` interface so that client code can leverage it +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/cli-node@0.2.2 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## 0.5.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/cli-node@0.2.2-next.0 + +## 0.5.2-next.1 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/cli-node@0.2.2-next.0 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## 0.5.2-next.0 + +### Patch Changes + +- 883782e: Updated the OpenAPI template to export the `TypedResponse` interface so that client code can leverage it +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.1 + - @backstage/errors@1.2.3 + +## 0.5.0 + +### Minor Changes + +- aea8f8d: **BREAKING**: API Reports generated for sub-path exports now place the name as a suffix rather than prefix, for example `api-report-alpha.md` instead of `alpha-api-report.md`. When upgrading to this version you'll need to re-create any such API reports and delete the old ones. +- 3834067: Adds a new command `schema openapi generate-client` that creates a Typescript client with Backstage flavor, including the discovery API and fetch API. This command doesn't currently generate a complete client and needs to be wrapped or exported manually by a separate Backstage plugin. See `@backstage/catalog-client/src/generated` for example output. + +### Patch Changes + +- f909e9d: Includes templates in @backstage/repo-tools package and use them in the CLI +- da3c4db: Updates the `schema openapi generate-client` command to export all generated types from the generated directory. +- 7959f23: The `api-reports` command now checks for api report files that no longer apply. + If it finds such files, it's treated basically the same as report errors do, and + the check fails. + + For example, if you had an `api-report-alpha.md` but then removed the alpha + export, the reports generator would now report that this file needs to be + deleted. + +- f49e237: Fixed a bug where `schema openapi init` created an invalid test command. +- f91be2c: Updated dependency `@stoplight/types` to `^14.0.0`. +- 45bfb20: Execute `openapi-generator-cli` from `@backstage/repo-tools` directory to force it to use our openapitools.json config file. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/cli-node@0.2.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + +## 0.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/errors@1.2.3 + +## 0.5.0-next.1 + +### Patch Changes + +- f909e9d: Includes templates in @backstage/repo-tools package and use them in the CLI +- da3c4db: Updates the `schema openapi generate-client` command to export all generated types from the generated directory. +- 7959f23: The `api-reports` command now checks for api report files that no longer apply. + If it finds such files, it's treated basically the same as report errors do, and + the check fails. + + For example, if you had an `api-report-alpha.md` but then removed the alpha + export, the reports generator would now report that this file needs to be + deleted. + +- f49e237: Fixed a bug where `schema openapi init` created an invalid test command. +- f91be2c: Updated dependency `@stoplight/types` to `^14.0.0`. +- 45bfb20: Execute `openapi-generator-cli` from `@backstage/repo-tools` directory to force it to use our openapitools.json config file. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/errors@1.2.3 + +## 0.5.0-next.0 + +### Minor Changes + +- aea8f8d329: **BREAKING**: API Reports generated for sub-path exports now place the name as a suffix rather than prefix, for example `api-report-alpha.md` instead of `alpha-api-report.md`. When upgrading to this version you'll need to re-create any such API reports and delete the old ones. +- 38340678c3: Adds a new command `schema openapi generate-client` that creates a Typescript client with Backstage flavor, including the discovery API and fetch API. This command doesn't currently generate a complete client and needs to be wrapped or exported manually by a separate Backstage plugin. See `@backstage/catalog-client/src/generated` for example output. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.0 + - @backstage/errors@1.2.3 + ## 0.4.0 ### Minor Changes diff --git a/packages/repo-tools/openapitools.json b/packages/repo-tools/openapitools.json new file mode 100644 index 0000000000..65c0d8ead7 --- /dev/null +++ b/packages/repo-tools/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "../../node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "6.5.0" + } +} diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 3bd76a6e60..98af4149c3 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/repo-tools", "description": "CLI for Backstage repo tooling ", - "version": "0.4.0", + "version": "0.6.0-next.0", "publishConfig": { "access": "public" }, @@ -32,6 +32,7 @@ "dependencies": { "@apidevtools/swagger-parser": "^10.1.0", "@apisyouwonthate/style-guide": "^1.4.0", + "@backstage/backend-common": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/cli-common": "workspace:^", "@backstage/cli-node": "workspace:^", @@ -39,13 +40,14 @@ "@manypkg/get-packages": "^1.1.3", "@microsoft/api-documenter": "^7.22.33", "@microsoft/api-extractor": "^7.36.4", + "@openapitools/openapi-generator-cli": "^2.7.0", "@stoplight/spectral-core": "^1.18.0", "@stoplight/spectral-formatters": "^1.1.0", "@stoplight/spectral-functions": "^1.7.2", "@stoplight/spectral-parsers": "^1.0.2", "@stoplight/spectral-rulesets": "^1.18.0", "@stoplight/spectral-runtime": "^1.1.2", - "@stoplight/types": "^13.14.0", + "@stoplight/types": "^14.0.0", "chalk": "^4.0.0", "codeowners-utils": "^1.0.2", "commander": "^9.1.0", @@ -61,13 +63,12 @@ "yaml-diff-patch": "^2.0.0" }, "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/types": "workspace:^", "@types/is-glob": "^4.0.2", - "@types/mock-fs": "^4.13.0", "@types/node": "^18.17.8", - "@types/prettier": "^2.0.0", - "mock-fs": "^5.2.0" + "@types/prettier": "^2.0.0" }, "peerDependencies": { "@microsoft/api-extractor-model": "*", @@ -84,7 +85,9 @@ }, "files": [ "bin", - "dist/**/*.js" + "dist/**/*.js", + "templates", + "openapitools.json" ], "nodemonConfig": { "watch": "./src", 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 ce3e6f5b20..b8c241bb0b 100644 --- a/packages/repo-tools/src/commands/api-reports/api-extractor.ts +++ b/packages/repo-tools/src/commands/api-reports/api-extractor.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { groupBy } from 'lodash'; import { resolve as resolvePath, relative as relativePath, @@ -363,7 +364,9 @@ export async function runApiExtraction({ } const warnings = new Array(); - for (const { packageDir, name } of packageEntryPoints) { + for (const [packageDir, group] of Object.entries( + groupBy(packageEntryPoints, ep => ep.packageDir), + )) { console.log(`## Processing ${packageDir}`); const noBail = Array.isArray(allowWarnings) ? allowWarnings.some(aw => aw === packageDir || minimatch(packageDir, aw)) @@ -374,209 +377,242 @@ export async function runApiExtraction({ './dist-types', packageDir, ); + const names = group.map(ep => ep.name); - const prefix = name === 'index' ? '' : `${name}-`; - const reportFileName = `${prefix}api-report.md`; - const reportPath = resolvePath(projectFolder, reportFileName); + const remainingReportFiles = new Set( + fs + .readdirSync(projectFolder) + .filter( + filename => + filename.match(/^(.+)-api-report\.md$/) || + filename.match(/^api-report(-.+)?\.md$/), + ), + ); - const warningCountBefore = await countApiReportWarnings(reportPath); + for (const name of names) { + const suffix = name === 'index' ? '' : `-${name}`; + const reportFileName = `api-report${suffix}.md`; + const reportPath = resolvePath(projectFolder, reportFileName); + remainingReportFiles.delete(reportFileName); - const extractorConfig = ExtractorConfig.prepare({ - configObject: { - mainEntryPointFilePath: resolvePath(packageFolder, `src/${name}.d.ts`), - bundledPackages: [], + const warningCountBefore = await countApiReportWarnings(reportPath); - compiler: { - tsconfigFilePath, - }, - - apiReport: { - enabled: true, - reportFileName, - reportFolder: projectFolder, - reportTempFolder: resolvePath( - outputDir, - `${prefix}`, + const extractorConfig = ExtractorConfig.prepare({ + configObject: { + mainEntryPointFilePath: resolvePath( + packageFolder, + `src/${name}.d.ts`, ), - }, + bundledPackages: [], - docModel: { - // TODO(Rugvip): This skips docs for non-index entry points. We can try to work around it, but - // most likely it makes sense to wait for API Extractor to natively support exports. - enabled: name === 'index', - apiJsonFilePath: resolvePath( - outputDir, - `${prefix}.api.json`, - ), - }, + compiler: { + tsconfigFilePath, + }, - dtsRollup: { - enabled: false, - }, + apiReport: { + enabled: true, + reportFileName, + reportFolder: projectFolder, + reportTempFolder: resolvePath( + outputDir, + `${suffix}`, + ), + }, - tsdocMetadata: { - enabled: false, - }, + docModel: { + // TODO(Rugvip): This skips docs for non-index entry points. We can try to work around it, but + // most likely it makes sense to wait for API Extractor to natively support exports. + enabled: name === 'index', + apiJsonFilePath: resolvePath( + outputDir, + `${suffix}.api.json`, + ), + }, - messages: { - // Silence compiler warnings, as these will prevent the CI build to work - compilerMessageReporting: { - default: { - logLevel: 'none' as ExtractorLogLevel.None, - // These contain absolute file paths, so can't be included in the report - // addToApiReportFile: true, - }, - }, - extractorMessageReporting: { - default: { - logLevel: 'warning' as ExtractorLogLevel.Warning, - addToApiReportFile: true, - }, - ...messagesConf, - }, - tsdocMessageReporting: { - default: { - logLevel: 'warning' as ExtractorLogLevel.Warning, - addToApiReportFile: true, + dtsRollup: { + enabled: false, + }, + + tsdocMetadata: { + enabled: false, + }, + + messages: { + // Silence compiler warnings, as these will prevent the CI build to work + compilerMessageReporting: { + default: { + logLevel: 'none' as ExtractorLogLevel.None, + // These contain absolute file paths, so can't be included in the report + // addToApiReportFile: true, + }, + }, + extractorMessageReporting: { + default: { + logLevel: 'warning' as ExtractorLogLevel.Warning, + addToApiReportFile: true, + }, + ...messagesConf, + }, + tsdocMessageReporting: { + default: { + logLevel: 'warning' as ExtractorLogLevel.Warning, + addToApiReportFile: true, + }, }, }, + + newlineKind: 'lf', + + projectFolder, }, - - newlineKind: 'lf', - - projectFolder, - }, - configObjectFullPath: projectFolder, - packageJsonFullPath: resolvePath(projectFolder, 'package.json'), - tsdocConfigFile: await getTsDocConfig(), - ignoreMissingEntryPoint: true, - }); - - // The `packageFolder` needs to point to the location within `dist-types` in order for relative - // paths to be logged. Unfortunately the `prepare` method above derives it from the `packageJsonFullPath`, - // which needs to point to the actual file, so we override `packageFolder` afterwards. - ( - extractorConfig as { - packageFolder: string; - } - ).packageFolder = packageFolder; - - if (!compilerState) { - compilerState = CompilerState.create(extractorConfig, { - additionalEntryPoints: entryPoints, + configObjectFullPath: projectFolder, + packageJsonFullPath: resolvePath(projectFolder, 'package.json'), + tsdocConfigFile: await getTsDocConfig(), + ignoreMissingEntryPoint: true, }); - } - // Message verbosity can't be configured, so just skip the check instead - (Extractor as any)._checkCompilerCompatibility = () => {}; - - let shouldLogInstructions = false; - let conflictingFile: undefined | string = undefined; - - // Invoke API Extractor - const extractorResult = Extractor.invoke(extractorConfig, { - localBuild: isLocalBuild, - showVerboseMessages: false, - showDiagnostics: false, - messageCallback(message) { - if (message.text.includes('The API report file is missing')) { - shouldLogInstructions = true; + // The `packageFolder` needs to point to the location within `dist-types` in order for relative + // paths to be logged. Unfortunately the `prepare` method above derives it from the `packageJsonFullPath`, + // which needs to point to the actual file, so we override `packageFolder` afterwards. + ( + extractorConfig as { + packageFolder: string; } - if ( - message.text.includes( - 'You have changed the public API signature for this project.', - ) - ) { - shouldLogInstructions = true; - const match = message.text.match( - /Please copy the file "(.*)" to "api-report\.md"/, - ); - if (match) { - conflictingFile = match[1]; + ).packageFolder = packageFolder; + + if (!compilerState) { + compilerState = CompilerState.create(extractorConfig, { + additionalEntryPoints: entryPoints, + }); + } + + // Message verbosity can't be configured, so just skip the check instead + (Extractor as any)._checkCompilerCompatibility = () => {}; + + let shouldLogInstructions = false; + let conflictingFile: undefined | string = undefined; + + // Invoke API Extractor + const extractorResult = Extractor.invoke(extractorConfig, { + localBuild: isLocalBuild, + showVerboseMessages: false, + showDiagnostics: false, + messageCallback(message) { + if (message.text.includes('The API report file is missing')) { + shouldLogInstructions = true; } - } - }, - compilerState, - }); - - // This release tag validation makes sure that the release tag of known entry points match expectations. - // The root index entrypoint is only allowed @public exports, while /alpha and /beta only allow @alpha and @beta. - if ( - validateReleaseTags && - fs.pathExistsSync(extractorConfig.reportFilePath) - ) { - if (['index', 'alpha', 'beta'].includes(name)) { - const report = await fs.readFile( - extractorConfig.reportFilePath, - 'utf8', - ); - const lines = report.split(/\r?\n/); - const expectedTag = name === 'index' ? 'public' : name; - for (let i = 0; i < lines.length; i += 1) { - const line = lines[i]; - const match = line.match(/^\/\/ @(alpha|beta|public)/); - if (match && match[1] !== expectedTag) { - // Because of limitations in the type script rollup logic we need to allow public exports from the other release stages - // TODO(Rugvip): Try to work around the need for this exception - if (expectedTag !== 'public' && match[1] === 'public') { - continue; - } - throw new Error( - `Unexpected release tag ${match[1]} in ${ - extractorConfig.reportFilePath - } at line ${i + 1}`, + if ( + message.text.includes( + 'You have changed the public API signature for this project.', + ) + ) { + shouldLogInstructions = true; + const match = message.text.match( + /Please copy the file "(.*)" to "api-report\.md"/, ); + if (match) { + conflictingFile = match[1]; + } + } + }, + compilerState, + }); + + // This release tag validation makes sure that the release tag of known entry points match expectations. + // The root index entrypoint is only allowed @public exports, while /alpha and /beta only allow @alpha and @beta. + if ( + validateReleaseTags && + fs.pathExistsSync(extractorConfig.reportFilePath) + ) { + if (['index', 'alpha', 'beta'].includes(name)) { + const report = await fs.readFile( + extractorConfig.reportFilePath, + 'utf8', + ); + const lines = report.split(/\r?\n/); + const expectedTag = name === 'index' ? 'public' : name; + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; + const match = line.match(/^\/\/ @(alpha|beta|public)/); + if (match && match[1] !== expectedTag) { + // Because of limitations in the type script rollup logic we need to allow public exports from the other release stages + // TODO(Rugvip): Try to work around the need for this exception + if (expectedTag !== 'public' && match[1] === 'public') { + continue; + } + throw new Error( + `Unexpected release tag ${match[1]} in ${ + extractorConfig.reportFilePath + } at line ${i + 1}`, + ); + } } } } - } - - if (!extractorResult.succeeded) { - if (shouldLogInstructions) { - logApiReportInstructions(); - - if (conflictingFile) { - console.log(''); - console.log( - `The conflicting file is ${relativePath( - tmpDir, - conflictingFile, - )}, with the following content:`, - ); - console.log(''); - - const content = await fs.readFile(conflictingFile, 'utf8'); - console.log(content); + if (!extractorResult.succeeded) { + if (shouldLogInstructions) { logApiReportInstructions(); + + if (conflictingFile) { + console.log(''); + console.log( + `The conflicting file is ${relativePath( + tmpDir, + conflictingFile, + )}, with the following content:`, + ); + console.log(''); + + const content = await fs.readFile(conflictingFile, 'utf8'); + console.log(content); + + logApiReportInstructions(); + } } + + throw new Error( + `API Extractor completed with ${extractorResult.errorCount} errors` + + ` and ${extractorResult.warningCount} warnings`, + ); } - throw new Error( - `API Extractor completed with ${extractorResult.errorCount} errors` + - ` and ${extractorResult.warningCount} warnings`, - ); + const warningCountAfter = await countApiReportWarnings(reportPath); + if (noBail) { + console.log(`Skipping warnings check for ${packageDir}`); + } + if (warningCountAfter > 0 && !noBail) { + throw new Error( + `The API Report for ${packageDir} is not allowed to have warnings`, + ); + } + if (warningCountAfter === 0 && allowWarningPkg.includes(packageDir)) { + console.log( + `No need to allow warnings for ${packageDir}, it does not have any`, + ); + } + if (warningCountAfter > warningCountBefore) { + warnings.push( + `The API Report for ${packageDir} introduces new warnings. ` + + 'Please fix these warnings in order to keep the API Reports tidy.', + ); + } } - const warningCountAfter = await countApiReportWarnings(reportPath); - if (noBail) { - console.log(`Skipping warnings check for ${packageDir}`); - } - if (warningCountAfter > 0 && !noBail) { - throw new Error( - `The API Report for ${packageDir} is not allowed to have warnings`, - ); - } - if (warningCountAfter === 0 && allowWarningPkg.includes(packageDir)) { - console.log( - `No need to allow warnings for ${packageDir}, it does not have any`, - ); - } - if (warningCountAfter > warningCountBefore) { - warnings.push( - `The API Report for ${packageDir} introduces new warnings. ` + - 'Please fix these warnings in order to keep the API Reports tidy.', - ); + if (remainingReportFiles.size > 0) { + if (isLocalBuild) { + for (const f of remainingReportFiles) { + fs.rmSync(resolvePath(projectFolder, f)); + console.log(`Deleted deprecated API report ${f}`); + } + } else { + const staleList = [...remainingReportFiles] + .map(f => join(packageDir, f)) + .join(', '); + throw new Error( + `The API Report(s) ${staleList} are no longer relevant and should be deleted`, + ); + } } } diff --git a/packages/repo-tools/src/commands/api-reports/api-reports.test.ts b/packages/repo-tools/src/commands/api-reports/api-reports.test.ts index 0ffd8c5342..d1f7a3836c 100644 --- a/packages/repo-tools/src/commands/api-reports/api-reports.test.ts +++ b/packages/repo-tools/src/commands/api-reports/api-reports.test.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import mockFs from 'mock-fs'; -import { normalize, resolve as resolvePath } from 'path'; +import { createMockDirectory } from '@backstage/backend-test-utils'; +import { normalize } from 'path'; import * as pathsLib from '../../lib/paths'; import { @@ -47,38 +47,38 @@ jest.mock('./api-extractor', () => ({ const projectPaths = pathsLib.paths; +const mockDir = createMockDirectory(); + +jest.spyOn(projectPaths, 'targetRoot', 'get').mockReturnValue(mockDir.path); jest - .spyOn(projectPaths, 'targetRoot', 'get') - .mockReturnValue(normalize('/root')); -jest.spyOn(projectPaths, 'resolveTargetRoot').mockImplementation((...path) => { - return resolvePath(normalize('/root'), ...path); -}); + .spyOn(projectPaths, 'resolveTargetRoot') + .mockImplementation((...path) => mockDir.resolve(...path)); jest.spyOn(PackageGraph, 'listTargetPackages').mockResolvedValue([ { - dir: '/root/packages/package-a', + dir: normalize(mockDir.resolve('packages/package-a')), packageJson: { name: 'package-a', version: '0.0.0' }, }, { - dir: '/root/packages/package-b', + dir: normalize(mockDir.resolve('packages/package-b')), packageJson: { name: 'package-b', version: '0.0.0' }, }, { - dir: '/root/plugins/plugin-a', + dir: normalize(mockDir.resolve('plugins/plugin-a')), packageJson: { name: 'plugin-a', version: '0.0.0' }, }, { - dir: '/root/plugins/plugin-b', + dir: normalize(mockDir.resolve('plugins/plugin-b')), packageJson: { name: 'plugin-b', version: '0.0.0' }, }, { - dir: '/root/plugins/plugin-c', + dir: normalize(mockDir.resolve('plugins/plugin-c')), packageJson: { name: 'plugin-c', version: '0.0.0' }, }, ]); describe('buildApiReports', () => { beforeEach(() => { - mockFs({ + mockDir.setContent({ [projectPaths.targetRoot]: { 'package.json': JSON.stringify({ workspaces: { packages: ['packages/*', 'plugins/*'] }, @@ -109,7 +109,6 @@ describe('buildApiReports', () => { }); afterEach(() => { - mockFs.restore(); jest.clearAllMocks(); }); @@ -138,11 +137,11 @@ describe('buildApiReports', () => { normalize('plugins/plugin-b'), normalize('plugins/plugin-c'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: [], omitMessages: [], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); expect(runCliExtraction).toHaveBeenCalledWith({ packageDirs: [ @@ -171,11 +170,11 @@ describe('buildApiReports', () => { expect(runApiExtraction).toHaveBeenCalledWith({ packageDirs: [normalize('packages/package-a')], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: [], omitMessages: [], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); expect(runCliExtraction).toHaveBeenCalledWith({ packageDirs: [normalize('packages/package-a')], @@ -200,11 +199,11 @@ describe('buildApiReports', () => { normalize('packages/package-a'), normalize('packages/package-b'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: [], omitMessages: [], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); expect(runCliExtraction).toHaveBeenCalledWith({ packageDirs: [ @@ -232,11 +231,11 @@ describe('buildApiReports', () => { normalize('packages/package-a'), normalize('packages/package-b'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: [], omitMessages: [], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); expect(runCliExtraction).toHaveBeenCalledWith({ packageDirs: [ @@ -267,11 +266,11 @@ describe('buildApiReports', () => { normalize('packages/package-b'), normalize('plugins/plugin-a'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: [], omitMessages: [], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); expect(runCliExtraction).toHaveBeenCalledWith({ packageDirs: [ @@ -305,11 +304,11 @@ describe('buildApiReports', () => { normalize('plugins/plugin-b'), normalize('plugins/plugin-c'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: [], omitMessages: [], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); expect(runCliExtraction).toHaveBeenCalledWith({ packageDirs: [ @@ -338,11 +337,11 @@ describe('buildApiReports', () => { normalize('packages/package-a'), normalize('packages/package-b'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: ['packages/package-a'], omitMessages: [], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); }); @@ -359,11 +358,11 @@ describe('buildApiReports', () => { normalize('packages/package-a'), normalize('packages/package-b'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: ['packages/package-a', 'packages/package-b'], omitMessages: [], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); }); @@ -380,11 +379,11 @@ describe('buildApiReports', () => { normalize('packages/package-a'), normalize('packages/package-b'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: ['packages/package-a', 'packages/package-b'], omitMessages: [], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); }); }); @@ -402,11 +401,11 @@ describe('buildApiReports', () => { normalize('packages/package-a'), normalize('packages/package-b'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: true, omitMessages: [], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); }); }); @@ -424,11 +423,11 @@ describe('buildApiReports', () => { normalize('packages/package-a'), normalize('packages/package-b'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: [], omitMessages: ['ae-missing-release-tag'], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); }); @@ -445,11 +444,11 @@ describe('buildApiReports', () => { normalize('packages/package-a'), normalize('packages/package-b'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: [], omitMessages: ['ae-missing-release-tag', 'ae-missing-annotations'], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); }); @@ -466,11 +465,11 @@ describe('buildApiReports', () => { normalize('packages/package-a'), normalize('packages/package-b'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: [], omitMessages: ['ae-missing-release-tag', 'ae-missing-annotations'], isLocalBuild: true, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); }); }); @@ -488,11 +487,11 @@ describe('buildApiReports', () => { normalize('packages/package-a'), normalize('packages/package-b'), ], - tsconfigFilePath: resolvePath('/root/tsconfig.json'), + tsconfigFilePath: mockDir.resolve('tsconfig.json'), allowWarnings: [], omitMessages: [], isLocalBuild: false, - outputDir: resolvePath('/root/node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('node_modules/.cache/api-extractor'), }); expect(runCliExtraction).toHaveBeenCalledWith({ packageDirs: [ @@ -513,8 +512,8 @@ describe('buildApiReports', () => { await buildApiReports(paths, opts); expect(buildDocs).toHaveBeenCalledWith({ - inputDir: resolvePath('/root/node_modules/.cache/api-extractor'), - outputDir: resolvePath('/root/docs/reference'), + inputDir: mockDir.resolve('node_modules/.cache/api-extractor'), + outputDir: mockDir.resolve('docs/reference'), }); }); }); diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 4f9ea87708..d9c6d892f5 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -32,14 +32,18 @@ function registerSchemaCommand(program: Command) { .description( 'Verify that all OpenAPI schemas are valid and have a matching `schemas/openapi.generated.ts` file.', ) - .action(lazy(() => import('./openapi/verify').then(m => m.bulkCommand))); + .action( + lazy(() => import('./openapi/schema/verify').then(m => m.bulkCommand)), + ); openApiCommand .command('generate [paths...]') .description( 'Generates a Typescript file from an OpenAPI yaml spec. For use with the `@backstage/backend-openapi-utils` ApiRouter type.', ) - .action(lazy(() => import('./openapi/generate').then(m => m.bulkCommand))); + .action( + lazy(() => import('./openapi/schema/generate').then(m => m.bulkCommand)), + ); openApiCommand .command('lint [paths...]') @@ -60,6 +64,16 @@ function registerSchemaCommand(program: Command) { .command('init ') .description('Creates any config needed for the test command.') .action(lazy(() => import('./openapi/test/init').then(m => m.default))); + + openApiCommand + .command('generate-client') + .requiredOption('--input-spec ') + .requiredOption('--output-directory ') + .action( + lazy(() => + import('./openapi/client/generate').then(m => m.singleCommand), + ), + ); } export function registerCommands(program: Command) { diff --git a/packages/repo-tools/src/commands/openapi/client/generate.ts b/packages/repo-tools/src/commands/openapi/client/generate.ts new file mode 100644 index 0000000000..f827f1005f --- /dev/null +++ b/packages/repo-tools/src/commands/openapi/client/generate.ts @@ -0,0 +1,100 @@ +/* + * 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 chalk from 'chalk'; +import { resolve } from 'path'; +import { OPENAPI_IGNORE_FILES, OUTPUT_PATH } from '../constants'; +import { paths as cliPaths } from '../../../lib/paths'; +import { mkdirpSync } from 'fs-extra'; +import fs from 'fs-extra'; +import { exec } from '../../../lib/exec'; +import { resolvePackagePath } from '@backstage/backend-common'; + +async function generate(spec: string, outputDirectory: string) { + const resolvedOpenapiPath = resolve(spec); + const resolvedOutputDirectory = resolve(outputDirectory, OUTPUT_PATH); + mkdirpSync(resolvedOutputDirectory); + + await fs.mkdirp(resolvedOutputDirectory); + + await fs.writeFile( + resolve(resolvedOutputDirectory, '.openapi-generator-ignore'), + OPENAPI_IGNORE_FILES.join('\n'), + ); + + await exec( + 'node', + [ + resolvePackagePath('@openapitools/openapi-generator-cli', 'main.js'), + 'generate', + '-i', + resolvedOpenapiPath, + '-o', + resolvedOutputDirectory, + '-g', + 'typescript', + '-c', + resolvePackagePath( + '@backstage/repo-tools', + 'templates/typescript-backstage.yaml', + ), + '--generator-key', + 'v3.0', + ], + { + maxBuffer: Number.MAX_VALUE, + cwd: resolvePackagePath('@backstage/repo-tools'), + env: { + ...process.env, + }, + }, + ); + + await exec( + `yarn backstage-cli package lint --fix ${resolvedOutputDirectory}`, + ); + + const prettier = cliPaths.resolveTargetRoot('node_modules/.bin/prettier'); + if (prettier) { + await exec(`${prettier} --write ${resolvedOutputDirectory}`); + } + + fs.removeSync(resolve(resolvedOutputDirectory, '.openapi-generator-ignore')); + + fs.rmSync(resolve(resolvedOutputDirectory, '.openapi-generator'), { + recursive: true, + force: true, + }); +} + +export async function singleCommand({ + inputSpec, + outputDirectory, +}: { + inputSpec: string; + outputDirectory: string; +}): Promise { + try { + await generate(inputSpec, outputDirectory); + console.log(chalk.green(`Generated client for ${inputSpec}`)); + } catch (err) { + console.log(); + console.log(chalk.red(`Client generation failed in ${outputDirectory}:`)); + console.log(err); + + process.exit(1); + } +} diff --git a/packages/repo-tools/src/commands/openapi/constants.ts b/packages/repo-tools/src/commands/openapi/constants.ts index defef4ab53..4936a5aee7 100644 --- a/packages/repo-tools/src/commands/openapi/constants.ts +++ b/packages/repo-tools/src/commands/openapi/constants.ts @@ -19,3 +19,45 @@ export const YAML_SCHEMA_PATH = 'src/schema/openapi.yaml'; export const TS_MODULE = 'src/schema/openapi.generated'; export const TS_SCHEMA_PATH = `${TS_MODULE}.ts`; + +export const GENERATOR_VERSION = `1.0.0`; +export const GENERATOR_NAME = 'typescript-backstage'; +export const GENERATOR_FILE = `packages/template-openapi-plugin-client/generator/target/${GENERATOR_NAME}-openapi-generator-${GENERATOR_VERSION}.jar`; + +export const OUTPUT_PATH = 'src/generated'; + +export const OPENAPI_IGNORE_FILES = [ + // Get rid of the default files. + '*.md', + '*.mustache', + // The rest of these have to be explicit, otherwise they get added if this was a *.* + 'apis/baseapi.ts', + 'apis/exception.ts', + 'auth/*', + 'http/*', + 'middleware.ts', + 'servers.ts', + 'util.ts', + 'configuration.ts', + 'rxjsStub.ts', + '.gitignore', + + // Override the created version. + 'apis/*.ts', + '!apis/*.client.ts', + 'models/*.ts', + '!models/*.model.ts', + + // Always include index.ts files. + '!index.ts', + '!**/index.ts', + + // Weird API typings. + 'types/ObjectParamAPI.ts', + 'types/ObservableAPI.ts', + 'types/PromiseAPI.ts', + + 'git_push.sh', + 'package.json', + 'tsconfig.json', +]; diff --git a/packages/repo-tools/src/commands/openapi/generate.ts b/packages/repo-tools/src/commands/openapi/schema/generate.ts similarity index 95% rename from packages/repo-tools/src/commands/openapi/generate.ts rename to packages/repo-tools/src/commands/openapi/schema/generate.ts index 52b8e5b840..cd9f572f2d 100644 --- a/packages/repo-tools/src/commands/openapi/generate.ts +++ b/packages/repo-tools/src/commands/openapi/schema/generate.ts @@ -18,9 +18,9 @@ import fs from 'fs-extra'; import YAML from 'js-yaml'; import chalk from 'chalk'; import { resolve } from 'path'; -import { paths as cliPaths } from '../../lib/paths'; -import { runner } from './runner'; -import { TS_SCHEMA_PATH, YAML_SCHEMA_PATH } from './constants'; +import { paths as cliPaths } from '../../../lib/paths'; +import { runner } from '../runner'; +import { TS_SCHEMA_PATH, YAML_SCHEMA_PATH } from '../constants'; import { promisify } from 'util'; import { exec as execCb } from 'child_process'; diff --git a/packages/repo-tools/src/commands/openapi/verify.ts b/packages/repo-tools/src/commands/openapi/schema/verify.ts similarity index 93% rename from packages/repo-tools/src/commands/openapi/verify.ts rename to packages/repo-tools/src/commands/openapi/schema/verify.ts index 2808e72702..7120dd67ad 100644 --- a/packages/repo-tools/src/commands/openapi/verify.ts +++ b/packages/repo-tools/src/commands/openapi/schema/verify.ts @@ -21,9 +21,9 @@ import { join } from 'path'; import chalk from 'chalk'; import { relative as relativePath, resolve as resolvePath } from 'path'; import Parser from '@apidevtools/swagger-parser'; -import { runner } from './runner'; -import { paths as cliPaths } from '../../lib/paths'; -import { TS_MODULE, TS_SCHEMA_PATH, YAML_SCHEMA_PATH } from './constants'; +import { runner } from '../runner'; +import { paths as cliPaths } from '../../../lib/paths'; +import { TS_MODULE, TS_SCHEMA_PATH, YAML_SCHEMA_PATH } from '../constants'; async function verify(directoryPath: string) { const openapiPath = join(directoryPath, YAML_SCHEMA_PATH); diff --git a/packages/repo-tools/src/commands/openapi/test/init.ts b/packages/repo-tools/src/commands/openapi/test/init.ts index 13c3aeb2b6..a38416aabb 100644 --- a/packages/repo-tools/src/commands/openapi/test/init.ts +++ b/packages/repo-tools/src/commands/openapi/test/init.ts @@ -35,7 +35,7 @@ async function init(directoryPath: string) { ); } const opticConfigFilePath = join(directoryPath, 'optic.yml'); - if (!(await fs.pathExists(opticConfigFilePath))) { + if (await fs.pathExists(opticConfigFilePath)) { throw new Error(`This directory already has an optic.yml file. Exiting.`); } await fs.writeFile( @@ -56,7 +56,7 @@ capture: # 🔧 Specify a command that will generate traffic command: yarn backstage-cli package test --no-watch ${ROUTER_TEST_PATHS.map( e => `"${e}"`, - ).join(',')} + ).join(' ')} `, ); if (await cliPaths.resolveTargetRoot('node_modules/.bin/prettier')) { diff --git a/packages/repo-tools/templates/typescript-backstage.yaml b/packages/repo-tools/templates/typescript-backstage.yaml new file mode 100644 index 0000000000..a2cb61c121 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage.yaml @@ -0,0 +1,35 @@ +templateDir: templates/typescript-backstage + +files: + api.mustache: + templateType: API + # For some reason, they check for destinationFilename differences. We have to change the ending to override the file. + destinationFilename: .client.ts + model.mustache: + templateType: Model + destinationFilename: .model.ts + modelGeneric.mustache: + templateType: SupportingFiles + modelOneOf.mustache: + templateType: SupportingFiles + modelGenericAdditionalProperties.mustache: + templateType: SupportingFiles + modelGenericEnums.mustache: + templateType: SupportingFiles + modelAlias.mustache: + templateType: SupportingFiles + modelEnum.mustache: + templateType: SupportingFiles + modelTaggedUnion.mustache: + templateType: SupportingFiles + models/models_all.mustache: + templateType: SupportingFiles + destinationFilename: models/index.ts + types/fetch.ts: {} + types/discovery.ts: {} + apis/index.mustache: + templateType: SupportingFiles + destinationFilename: apis/index.ts + pluginId.mustache: + templateType: SupportingFiles + destinationFilename: pluginId.ts diff --git a/packages/repo-tools/templates/typescript-backstage/api.mustache b/packages/repo-tools/templates/typescript-backstage/api.mustache new file mode 100644 index 0000000000..8043d6e2f4 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/api.mustache @@ -0,0 +1,119 @@ +{{>licenseInfo}} +import { DiscoveryApi } from '../types/discovery'; +import { FetchApi } from '../types/fetch'; +import crossFetch from 'cross-fetch'; +import {pluginId} from '../pluginId'; +import * as parser from 'uri-template'; + +{{#imports}} +import { {{classname}} } from '{{filename}}.model{{importFileExtension}}'; +{{/imports}} + +/** + * Wraps the Response type to convey a type on the json call. + * + * @public + */ +export type TypedResponse = Omit & { + json: () => Promise; +}; + +{{#operations}} + +/** + * Options you can pass into a request for additional information. + * + * @public + */ +export interface RequestOptions { + token?: string; +} + +/** + * {{{description}}}{{^description}}no description{{/description}} + */ +export class {{classname}}Client { + private readonly discoveryApi: DiscoveryApi; + private readonly fetchApi: FetchApi; + + constructor(options: { + discoveryApi: { getBaseUrl(pluginId: string): Promise }; + fetchApi?: { fetch: typeof fetch }; + }) { + this.discoveryApi = options.discoveryApi; + this.fetchApi = options.fetchApi || { fetch: crossFetch }; + } + + {{#operation}} + /** + {{#notes}} + * {{¬es}} + {{/notes}} + {{#summary}} + * {{&summary}} + {{/summary}} + {{#allParams}} + * @param {{paramName}} {{description}} + {{/allParams}} + */ + public async {{nickname}}( + // @ts-ignore + request: { + {{#hasPathParams}} + path: { + {{#pathParams}} + {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, + {{/pathParams}} + }, + {{/hasPathParams}} + {{#hasBodyParam}} + {{#bodyParam}} + body: {{{dataType}}}, + {{/bodyParam}} + {{/hasBodyParam}} + {{#hasQueryParams}} + query: { + {{#queryParams}} + {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, + {{/queryParams}} + }, + {{/hasQueryParams}} + {{#hasHeaderParams}} + header: { + {{#headerParams}} + {{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, + {{/headerParams}} + }, + {{/hasHeaderParams}} + }, + options?: RequestOptions + ): Promise> { + const baseUrl = await this.discoveryApi.getBaseUrl(pluginId); + + const uriTemplate = `{{{path}}}{{#hasQueryParams}}{?{{#queryParams}}{{baseName}}{{#isArray}}{{#isExplode}}*{{/isExplode}}{{/isArray}}{{^-last}},{{/-last}}{{/queryParams}}}{{/hasQueryParams}}`; + + const uri = parser.parse(uriTemplate).expand({ + {{#pathParams}} + {{baseName}}: request.path.{{paramName}}, + {{/pathParams}} + {{#hasQueryParams}} + ...request.query, + {{/hasQueryParams}} + }) + + return await this.fetchApi.fetch(`${baseUrl}${uri}`, { + headers: { + {{#hasHeaderParams}} + ...request.header, + {{/hasHeaderParams}} + 'Content-Type': 'application/json', + ...(options?.token && { Authorization: `Bearer ${options?.token}` }), + }, + method: '{{httpMethod}}', + {{#hasBodyParam}} body: JSON.stringify(request.body), {{/hasBodyParam}} + }); + } + + {{/operation}} +} +{{/operations}} diff --git a/packages/repo-tools/templates/typescript-backstage/apis/index.mustache b/packages/repo-tools/templates/typescript-backstage/apis/index.mustache new file mode 100644 index 0000000000..4c8996c5ee --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/apis/index.mustache @@ -0,0 +1,3 @@ +// + +export * from './DefaultApi.client'; diff --git a/packages/repo-tools/templates/typescript-backstage/index.mustache b/packages/repo-tools/templates/typescript-backstage/index.mustache new file mode 100644 index 0000000000..b95e48ff08 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/index.mustache @@ -0,0 +1,4 @@ +// + +export * from './apis'; +export * from './models'; \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/licenseInfo.mustache b/packages/repo-tools/templates/typescript-backstage/licenseInfo.mustache new file mode 100644 index 0000000000..afc90f6227 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/licenseInfo.mustache @@ -0,0 +1,7 @@ +// + +/** + * {{{appName}}}{{#version}}@{{{.}}}{{/version}} + * + * NOTE: This class is auto generated, do not edit the class manually. + */ \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/model.mustache b/packages/repo-tools/templates/typescript-backstage/model.mustache new file mode 100644 index 0000000000..97d7858cb2 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/model.mustache @@ -0,0 +1,17 @@ +{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/model.mustache#L17. }} +{{>licenseInfo}} +{{#models}} +{{#model}} +{{#tsImports}} +import { {{classname}} } from '{{filename}}.model'; +{{/tsImports}} + + +{{#description}} +/** + * {{{.}}} + */ +{{/description}} +{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#isAlias}}{{>modelAlias}}{{/isAlias}}{{^isAlias}}{{#taggedUnions}}{{>modelTaggedUnion}}{{/taggedUnions}}{{^taggedUnions}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{^oneOf}}{{>modelGeneric}}{{/oneOf}}{{/taggedUnions}}{{/isAlias}}{{/isEnum}} +{{/model}} +{{/models}} diff --git a/packages/repo-tools/templates/typescript-backstage/modelAlias.mustache b/packages/repo-tools/templates/typescript-backstage/modelAlias.mustache new file mode 100644 index 0000000000..351dc37c78 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/modelAlias.mustache @@ -0,0 +1,3 @@ +{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelAlias.mustache }} + +export type {{classname}} = {{dataType}}; \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/modelEnum.mustache b/packages/repo-tools/templates/typescript-backstage/modelEnum.mustache new file mode 100644 index 0000000000..0deab70f02 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/modelEnum.mustache @@ -0,0 +1,22 @@ +{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelEnum.mustache }} + +{{#stringEnums}} +export enum {{classname}} { +{{#allowableValues}} +{{#enumVars}} + {{name}} = {{{value}}}{{^-last}},{{/-last}} +{{/enumVars}} +{{/allowableValues}} +} +{{/stringEnums}} +{{^stringEnums}} +export type {{classname}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}}; + +export const {{classname}} = { +{{#allowableValues}} +{{#enumVars}} + {{name}}: {{{value}}} as {{classname}}{{^-last}},{{/-last}} +{{/enumVars}} +{{/allowableValues}} +}; +{{/stringEnums}} diff --git a/packages/repo-tools/templates/typescript-backstage/modelGeneric.mustache b/packages/repo-tools/templates/typescript-backstage/modelGeneric.mustache new file mode 100644 index 0000000000..d51eeaed80 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/modelGeneric.mustache @@ -0,0 +1,38 @@ +{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelGeneric.mustache }} + +{{#models}} +{{#model}} + +{{#description}} +/** +* {{{.}}} +*/ +{{/description}} +{{^isEnum}} +export interface {{classname}} { +{{>modelGenericAdditionalProperties}} +{{#vars}} +{{#description}} + /** + * {{{.}}} + */ +{{/description}} + '{{name}}'{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}}; +{{/vars}} +} + +{{#hasEnums}} + +{{#vars}} +{{#isEnum}} +export type {{classname}}{{enumName}} ={{#allowableValues}}{{#values}} "{{.}}" {{^-last}}|{{/-last}}{{/values}}{{/allowableValues}}; +{{/isEnum}} +{{/vars}} + +{{/hasEnums}} +{{/isEnum}} +{{#isEnum}} +export type {{classname}} ={{#allowableValues}}{{#values}} "{{.}}" {{^-last}}|{{/-last}}{{/values}}{{/allowableValues}}; +{{/isEnum}} +{{/model}} +{{/models}} \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/modelGenericAdditionalProperties.mustache b/packages/repo-tools/templates/typescript-backstage/modelGenericAdditionalProperties.mustache new file mode 100644 index 0000000000..b8d7d1ceb7 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/modelGenericAdditionalProperties.mustache @@ -0,0 +1,7 @@ +{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelGenericAdditionalProperties.mustache }} + +{{#additionalPropertiesType}} + + [key: string]: {{{additionalPropertiesType}}}{{#hasVars}} | any{{/hasVars}}; + +{{/additionalPropertiesType}} \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/modelGenericEnums.mustache b/packages/repo-tools/templates/typescript-backstage/modelGenericEnums.mustache new file mode 100644 index 0000000000..8d7b49c9c3 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/modelGenericEnums.mustache @@ -0,0 +1,32 @@ +{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelGenericEnums.mustache }} + +{{#hasEnums}} + +{{^stringEnums}} +export namespace {{classname}} { +{{/stringEnums}} +{{#vars}} + {{#isEnum}} +{{#stringEnums}} +export enum {{classname}}{{enumName}} { +{{#allowableValues}} +{{#enumVars}} + {{name}} = {{{value}}}{{^-last}},{{/-last}} +{{/enumVars}} +{{/allowableValues}} +}; +{{/stringEnums}} +{{^stringEnums}} + export type {{enumName}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}}; + export const {{enumName}} = { + {{#allowableValues}} + {{#enumVars}} + {{name}}: {{{value}}} as {{enumName}}{{^-last}},{{/-last}} + {{/enumVars}} + {{/allowableValues}} + }; +{{/stringEnums}} + {{/isEnum}} +{{/vars}} +{{^stringEnums}}}{{/stringEnums}} +{{/hasEnums}} \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/modelOneOf.mustache b/packages/repo-tools/templates/typescript-backstage/modelOneOf.mustache new file mode 100644 index 0000000000..b261789c3d --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/modelOneOf.mustache @@ -0,0 +1,16 @@ +{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelOneOf.mustache }} + +{{#hasImports}} +import { + {{#imports}} + {{{.}}}, + {{/imports}} +} from './'; + +{{/hasImports}} +{{#description}} + /** + * {{{.}}} + */ +{{/description}} +export type {{classname}} = {{#oneOf}}{{{.}}}{{^-last}} | {{/-last}}{{/oneOf}}; diff --git a/packages/repo-tools/templates/typescript-backstage/modelTaggedUnion.mustache b/packages/repo-tools/templates/typescript-backstage/modelTaggedUnion.mustache new file mode 100644 index 0000000000..a3f5e00904 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/modelTaggedUnion.mustache @@ -0,0 +1,23 @@ +{{! Sourced from https://github.com/OpenAPITools/openapi-generator/blob/7347daec61b2cb8d3d28e1ed06fe8b5e682090f8/modules/openapi-generator/src/main/resources/typescript-angular/modelTaggedUnion.mustache }} + +{{#discriminator}} +export type {{classname}} = {{#children}}{{^-first}} | {{/-first}}{{classname}}{{/children}}; +{{/discriminator}} +{{^discriminator}} +{{#parent}} +export interface {{classname}} { {{>modelGenericAdditionalProperties}} +{{#allVars}} + {{#description}} + /** + * {{{.}}} + */ + {{/description}} + {{name}}{{^required}}?{{/required}}: {{#discriminatorValue}}'{{.}}'{{/discriminatorValue}}{{^discriminatorValue}}{{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{/discriminatorValue}}{{#isNullable}} | null{{/isNullable}}; +{{/allVars}} +} +{{>modelGenericEnums}} +{{/parent}} +{{^parent}} +{{>modelGeneric}} +{{/parent}} +{{/discriminator}} \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/models/models_all.mustache b/packages/repo-tools/templates/typescript-backstage/models/models_all.mustache new file mode 100644 index 0000000000..d076cfb3ff --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/models/models_all.mustache @@ -0,0 +1,7 @@ +// + +{{#models}} +{{#model}} +export * from '{{{ importPath }}}.model{{importFileExtension}}' +{{/model}} +{{/models}} \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/pluginId.mustache b/packages/repo-tools/templates/typescript-backstage/pluginId.mustache new file mode 100644 index 0000000000..a71829bb84 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/pluginId.mustache @@ -0,0 +1,2 @@ + +export const pluginId = "{{appName}}"; \ No newline at end of file diff --git a/packages/repo-tools/templates/typescript-backstage/types/discovery.ts b/packages/repo-tools/templates/typescript-backstage/types/discovery.ts new file mode 100644 index 0000000000..a7f87d3780 --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/types/discovery.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * This is a copy of the DiscoveryApi, to avoid importing core-plugin-api. + */ +export type DiscoveryApi = { + getBaseUrl(pluginId: string): Promise; +}; diff --git a/packages/repo-tools/templates/typescript-backstage/types/fetch.ts b/packages/repo-tools/templates/typescript-backstage/types/fetch.ts new file mode 100644 index 0000000000..3de56c028e --- /dev/null +++ b/packages/repo-tools/templates/typescript-backstage/types/fetch.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * This is a copy of FetchApi, to avoid importing core-plugin-api. + */ +export type FetchApi = { + fetch: typeof fetch; +}; diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index b75cb32d7a..c55d4a3e6e 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,167 @@ # techdocs-cli-embedded-app +## 0.2.91-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.17.0-next.0 + - @backstage/app-defaults@1.4.8-next.0 + - @backstage/plugin-techdocs@1.9.4-next.0 + - @backstage/cli@0.25.2-next.0 + - @backstage/test-utils@1.5.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration-react@1.1.23 + - @backstage/theme@0.5.0 + - @backstage/plugin-techdocs-react@1.1.15 + +## 0.2.90 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/cli@0.25.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-techdocs-react@1.1.15 + - @backstage/plugin-techdocs@1.9.3 + - @backstage/plugin-catalog@1.16.1 + - @backstage/app-defaults@1.4.7 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/test-utils@1.4.7 + - @backstage/theme@0.5.0 + +## 0.2.90-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.16.1-next.2 + - @backstage/plugin-techdocs@1.9.3-next.2 + - @backstage/cli@0.25.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + +## 0.2.90-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/app-defaults@1.4.7-next.1 + - @backstage/cli@0.25.1-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/test-utils@1.4.7-next.1 + - @backstage/plugin-catalog@1.16.1-next.1 + - @backstage/plugin-techdocs@1.9.3-next.1 + - @backstage/plugin-techdocs-react@1.1.15-next.1 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + +## 0.2.90-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/cli@0.25.1-next.0 + - @backstage/plugin-techdocs-react@1.1.15-next.0 + - @backstage/plugin-techdocs@1.9.3-next.0 + - @backstage/plugin-catalog@1.16.1-next.0 + - @backstage/app-defaults@1.4.7-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/test-utils@1.4.7-next.0 + - @backstage/theme@0.5.0 + +## 0.2.89 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/cli@0.25.0 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog@1.16.0 + - @backstage/core-app-api@1.11.2 + - @backstage/test-utils@1.4.6 + - @backstage/plugin-techdocs@1.9.2 + - @backstage/app-defaults@1.4.6 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-techdocs-react@1.1.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## 0.2.89-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.25.0-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/app-defaults@1.4.6-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/test-utils@1.4.6-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog@1.16.0-next.4 + - @backstage/plugin-techdocs@1.9.2-next.4 + - @backstage/plugin-techdocs-react@1.1.14-next.3 + +## 0.2.89-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.25.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-techdocs@1.9.2-next.3 + - @backstage/plugin-catalog@1.16.0-next.3 + - @backstage/app-defaults@1.4.6-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/test-utils@1.4.6-next.2 + - @backstage/plugin-techdocs-react@1.1.14-next.2 + +## 0.2.89-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/cli@0.25.0-next.1 + - @backstage/plugin-catalog@1.16.0-next.2 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/test-utils@1.4.6-next.1 + - @backstage/plugin-techdocs@1.9.2-next.2 + - @backstage/app-defaults@1.4.6-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-techdocs-react@1.1.14-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + ## 0.2.89-next.1 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 21b1ff39f4..1641562040 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.89-next.1", + "version": "0.2.91-next.0", "private": true, "backstage": { "role": "frontend" diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index bdcd94b951..9639950663 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,110 @@ # @techdocs/cli +## 1.8.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-techdocs-node@1.11.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.8.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-techdocs-node@1.11.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.8.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-techdocs-node@1.11.1-next.2 + +## 1.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.11.1-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + +## 1.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.11.1-next.0 + +## 1.8.0 + +### Minor Changes + +- d15d483: Add command `--runAsDefaultUser` for `@techdocs/cli generate` to bypass running the docker builds as host user for macOS and Linux. +- b2dccad: Support passing additional `mkdocs-server` CLI parameters (`--dirtyreload`, `--strict` and `--clean`) when run in containerized mode. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-techdocs-node@1.11.0 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + +## 1.8.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.11.0-next.3 + +## 1.8.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/plugin-techdocs-node@1.11.0-next.2 + +## 1.8.0-next.1 + +### Minor Changes + +- b2dccad7b3: Support passing additional `mkdocs-server` CLI parameters (`--dirtyreload`, `--strict` and `--clean`) when run in containerized mode. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.11.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + ## 1.8.0-next.0 ### Minor Changes diff --git a/packages/techdocs-cli/cli-report.md b/packages/techdocs-cli/cli-report.md index 8f2233b158..9777b2c4b8 100644 --- a/packages/techdocs-cli/cli-report.md +++ b/packages/techdocs-cli/cli-report.md @@ -108,6 +108,9 @@ Options: --preview-app-bundle-path --preview-app-port -c, --mkdocs-config-file-name + --mkdocs-parameter-clean + --mkdocs-parameter-dirtyreload + --mkdocs-parameter-strict -h, --help ``` diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index fa1c69d09b..f4cf7e9dc6 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.8.0-next.0", + "version": "1.8.2-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/techdocs-cli/src/commands/index.ts b/packages/techdocs-cli/src/commands/index.ts index bb469ec82a..ac9523e639 100644 --- a/packages/techdocs-cli/src/commands/index.ts +++ b/packages/techdocs-cli/src/commands/index.ts @@ -294,6 +294,21 @@ export function registerCommands(program: Command) { '-c, --mkdocs-config-file-name ', 'Mkdocs config file name', ) + .option( + '--mkdocs-parameter-clean', + 'Pass "--clean" parameter to mkdocs server running in containerized environment', + false, + ) + .option( + '--mkdocs-parameter-dirtyreload', + 'Pass "--dirtyreload" parameter to mkdocs server running in containerized environment', + false, + ) + .option( + '--mkdocs-parameter-strict', + 'Pass "--strict" parameter to mkdocs server running in containerized environment', + false, + ) .hook('preAction', command => { if ( command.opts().previewAppPort !== defaultPreviewAppPort && diff --git a/packages/techdocs-cli/src/commands/serve/serve.ts b/packages/techdocs-cli/src/commands/serve/serve.ts index 98c9982b21..de681c2de4 100644 --- a/packages/techdocs-cli/src/commands/serve/serve.ts +++ b/packages/techdocs-cli/src/commands/serve/serve.ts @@ -116,6 +116,9 @@ export default async function serve(opts: OptionValues) { stdoutLogFunc: mkdocsLogFunc, stderrLogFunc: mkdocsLogFunc, mkdocsConfigFileName: mkdocsYmlPath, + mkdocsParameterClean: opts.mkdocsParameterClean, + mkdocsParameterDirtyReload: opts.mkdocsParameterDirtyReload, + mkdocsParameterStrict: opts.mkdocsParameterStrict, }); // Wait until mkdocs server has started so that Backstage starts with docs loaded diff --git a/packages/techdocs-cli/src/lib/mkdocsServer.test.ts b/packages/techdocs-cli/src/lib/mkdocsServer.test.ts index 8484c4e242..83f9c9f382 100644 --- a/packages/techdocs-cli/src/lib/mkdocsServer.test.ts +++ b/packages/techdocs-cli/src/lib/mkdocsServer.test.ts @@ -97,6 +97,24 @@ describe('runMkdocsServer', () => { expect.objectContaining({}), ); }); + + it('should accept additinoal mkdocs CLI parameters', async () => { + await runMkdocsServer({ + mkdocsParameterClean: true, + mkdocsParameterStrict: true, + }); + expect(run).toHaveBeenCalledWith( + 'docker', + expect.arrayContaining([ + 'serve', + '--dev-addr', + '0.0.0.0:8000', + '--clean', + '--strict', + ]), + expect.objectContaining({}), + ); + }); }); describe('mkdocs', () => { diff --git a/packages/techdocs-cli/src/lib/mkdocsServer.ts b/packages/techdocs-cli/src/lib/mkdocsServer.ts index 42409acb46..a793643367 100644 --- a/packages/techdocs-cli/src/lib/mkdocsServer.ts +++ b/packages/techdocs-cli/src/lib/mkdocsServer.ts @@ -26,6 +26,9 @@ export const runMkdocsServer = async (options: { stdoutLogFunc?: LogFunc; stderrLogFunc?: LogFunc; mkdocsConfigFileName?: string; + mkdocsParameterClean?: boolean; + mkdocsParameterDirtyReload?: boolean; + mkdocsParameterStrict?: boolean; }): Promise => { const port = options.port ?? '8000'; const useDocker = options.useDocker ?? true; @@ -55,6 +58,9 @@ export const runMkdocsServer = async (options: { ...(options.mkdocsConfigFileName ? ['--config-file', options.mkdocsConfigFileName] : []), + ...(options.mkdocsParameterClean ? ['--clean'] : []), + ...(options.mkdocsParameterDirtyReload ? ['--dirtyreload'] : []), + ...(options.mkdocsParameterStrict ? ['--strict'] : []), ], { stdoutLogFunc: options.stdoutLogFunc, @@ -72,6 +78,9 @@ export const runMkdocsServer = async (options: { ...(options.mkdocsConfigFileName ? ['--config-file', options.mkdocsConfigFileName] : []), + ...(options.mkdocsParameterClean ? ['--clean'] : []), + ...(options.mkdocsParameterDirtyReload ? ['--dirtyreload'] : []), + ...(options.mkdocsParameterStrict ? ['--strict'] : []), ], { stdoutLogFunc: options.stdoutLogFunc, diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 5bde6c3354..101720cb30 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,102 @@ # @backstage/test-utils +## 1.5.0-next.0 + +### Minor Changes + +- bb40898: Added `components` option to `TestAppOptions`, which will be forwarded as the `components` option to `createApp`. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-react@0.4.19 + +## 1.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + +## 1.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.11 + +## 1.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.11 + +## 1.4.6 + +### Patch Changes + +- e8f2ace: Deprecated `mockBreakpoint`, as it is now available from `@backstage/core-components/testUtils` instead. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/core-app-api@1.11.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 1.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## 1.4.6-next.1 + +### Patch Changes + +- e8f2acef80: Deprecated `mockBreakpoint`, as it is now available from `@backstage/core-components/testUtils` instead. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + ## 1.4.6-next.0 ### Patch Changes diff --git a/packages/test-utils/alpha-api-report.md b/packages/test-utils/api-report-alpha.md similarity index 100% rename from packages/test-utils/alpha-api-report.md rename to packages/test-utils/api-report-alpha.md diff --git a/packages/test-utils/api-report.md b/packages/test-utils/api-report.md index 60bd352726..e31a820a1c 100644 --- a/packages/test-utils/api-report.md +++ b/packages/test-utils/api-report.md @@ -7,6 +7,7 @@ import { AnalyticsApi } from '@backstage/core-plugin-api'; import { AnalyticsEvent } from '@backstage/core-plugin-api'; import { ApiHolder } from '@backstage/core-plugin-api'; import { ApiRef } from '@backstage/core-plugin-api'; +import { AppComponents } from '@backstage/core-plugin-api'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { ComponentType } from 'react'; import { Config } from '@backstage/config'; @@ -74,7 +75,7 @@ export class MockAnalyticsApi implements AnalyticsApi { getEvents(): AnalyticsEvent[]; } -// @public +// @public @deprecated export function mockBreakpoint(options: { matches: boolean }): void; // @public @@ -243,6 +244,7 @@ export type TestAppOptions = { mountedRoutes?: { [path: string]: RouteRef | ExternalRouteRef; }; + components?: Partial; }; // @public diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 15b3a387ae..3e1d751677 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "1.4.6-next.0", + "version": "1.5.0-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx index 8bc69e184a..f2c1ff592e 100644 --- a/packages/test-utils/src/testUtils/appWrappers.tsx +++ b/packages/test-utils/src/testUtils/appWrappers.tsx @@ -25,6 +25,7 @@ import { themes, UnifiedThemeProvider } from '@backstage/theme'; import MockIcon from '@material-ui/icons/AcUnit'; import { createSpecializedApp } from '@backstage/core-app-api'; import { + AppComponents, attachComponentData, BootErrorPageProps, createRouteRef, @@ -101,6 +102,11 @@ export type TestAppOptions = { * const link = useRouteRef(myRouteRef) */ mountedRoutes?: { [path: string]: RouteRef | ExternalRouteRef }; + + /** + * Components to be forwarded to the `components` option of `createApp`. + */ + components?: Partial; }; function isExternalRouteRef( @@ -137,6 +143,7 @@ export function createTestAppWrapper( Router: ({ children }) => ( ), + ...options.components, }, icons: mockIcons, plugins: [], diff --git a/packages/test-utils/src/testUtils/mockBreakpoint.ts b/packages/test-utils/src/testUtils/mockBreakpoint.ts index 78d7c9497f..c674c98f06 100644 --- a/packages/test-utils/src/testUtils/mockBreakpoint.ts +++ b/packages/test-utils/src/testUtils/mockBreakpoint.ts @@ -25,6 +25,7 @@ * https://mui.com/material-ui/react-use-media-query/#testing * * @public + * @deprecated Import from `@backstage/core-components/testUtils` instead. */ export default function mockBreakpoint(options: { matches: boolean }) { Object.defineProperty(window, 'matchMedia', { diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index e1ee5fe11f..323fc8062d 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/theme +## 0.5.0 + +### Minor Changes + +- 4d9e3b3: Added a global `OverrideComponentNameToClassKeys` for other plugins and packages to populate using module augmentation. This will in turn will provide component style override types for `createUnifiedTheme`. + +### Patch Changes + +- cd0dd4c: Align Material UI v5 `Paper` component background color in dark mode to v4. + +## 0.5.0-next.1 + +### Patch Changes + +- cd0dd4c: Align Material UI v5 `Paper` component background color in dark mode to v4. + ## 0.5.0-next.0 ### Minor Changes diff --git a/packages/theme/package.json b/packages/theme/package.json index ad70198cf8..56179fae19 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/theme", "description": "material-ui theme for use with Backstage.", - "version": "0.5.0-next.0", + "version": "0.5.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/theme/src/v5/defaultComponentThemes.ts b/packages/theme/src/v5/defaultComponentThemes.ts index e484300818..60caa47e90 100644 --- a/packages/theme/src/v5/defaultComponentThemes.ts +++ b/packages/theme/src/v5/defaultComponentThemes.ts @@ -239,4 +239,7 @@ export const defaultComponentThemes: ThemeOptions['components'] = { underline: 'hover', }, }, + MuiPaper: { + styleOverrides: { root: { backgroundImage: 'unset' } }, + }, }; diff --git a/plugins/adr-backend/CHANGELOG.md b/plugins/adr-backend/CHANGELOG.md index 5ea30aee78..e4986f0ca2 100644 --- a/plugins/adr-backend/CHANGELOG.md +++ b/plugins/adr-backend/CHANGELOG.md @@ -1,5 +1,135 @@ # @backstage/plugin-adr-backend +## 0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-adr-common@0.2.19 + - @backstage/plugin-search-common@1.2.10 + +## 0.4.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-adr-common@0.2.19 + - @backstage/plugin-search-common@1.2.10 + +## 0.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## 0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-adr-common@0.2.18 + - @backstage/plugin-search-common@1.2.9 + +## 0.4.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-adr-common@0.2.18 + - @backstage/plugin-search-common@1.2.9 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-adr-common@0.2.18 + - @backstage/plugin-search-common@1.2.9 + +## 0.4.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-adr-common@0.2.18-next.1 + - @backstage/plugin-search-common@1.2.8 + +## 0.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-adr-common@0.2.18-next.1 + - @backstage/plugin-search-common@1.2.8 + +## 0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-adr-common@0.2.18-next.1 + - @backstage/plugin-search-common@1.2.8 + ## 0.4.5-next.0 ### Patch Changes diff --git a/plugins/adr-backend/package.json b/plugins/adr-backend/package.json index 8981a4905d..9f30c95581 100644 --- a/plugins/adr-backend/package.json +++ b/plugins/adr-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr-backend", - "version": "0.4.5-next.0", + "version": "0.4.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -50,7 +50,6 @@ "@backstage/cli": "workspace:^", "@types/marked": "^5.0.0", "@types/supertest": "^2.0.8", - "msw": "^1.0.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/adr-common/CHANGELOG.md b/plugins/adr-common/CHANGELOG.md index 2127f06437..84c285a1a8 100644 --- a/plugins/adr-common/CHANGELOG.md +++ b/plugins/adr-common/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-adr-common +## 0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-search-common@1.2.10 + +## 0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-search-common@1.2.9 + +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-search-common@1.2.8 + ## 0.2.18-next.0 ### Patch Changes diff --git a/plugins/adr-common/package.json b/plugins/adr-common/package.json index 43bc951328..d05b585c7d 100644 --- a/plugins/adr-common/package.json +++ b/plugins/adr-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-adr-common", "description": "Common functionalities for the adr plugin", - "version": "0.2.18-next.0", + "version": "0.2.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/adr/CHANGELOG.md b/plugins/adr/CHANGELOG.md index 0e69d1e735..4586b14522 100644 --- a/plugins/adr/CHANGELOG.md +++ b/plugins/adr/CHANGELOG.md @@ -1,5 +1,147 @@ # @backstage/plugin-adr +## 0.6.13-next.0 + +### Patch Changes + +- 0b03962: Updated README +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration-react@1.1.23 + - @backstage/plugin-adr-common@0.2.19 + - @backstage/plugin-search-common@1.2.10 + +## 0.6.12 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-search-react@1.7.5 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-adr-common@0.2.19 + - @backstage/plugin-search-common@1.2.10 + +## 0.6.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## 0.6.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-adr-common@0.2.18 + - @backstage/plugin-search-common@1.2.9 + +## 0.6.12-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-adr-common@0.2.18 + - @backstage/plugin-search-common@1.2.9 + +## 0.6.11 + +### Patch Changes + +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- fb8f3bd: Updated alpha translation message keys to use nested format and camel case. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-react@1.7.4 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-adr-common@0.2.18 + - @backstage/plugin-search-common@1.2.9 + +## 0.6.11-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-adr-common@0.2.18-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + +## 0.6.11-next.2 + +### Patch Changes + +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-adr-common@0.2.18-next.1 + - @backstage/plugin-search-common@1.2.8 + +## 0.6.11-next.1 + +### Patch Changes + +- fb8f3bdbc2: Updated alpha translation message keys to use nested format and camel case. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-adr-common@0.2.18-next.1 + - @backstage/plugin-search-common@1.2.8 + ## 0.6.11-next.0 ### Patch Changes diff --git a/plugins/adr/README.md b/plugins/adr/README.md index e6178f003b..3e8647a8fa 100644 --- a/plugins/adr/README.md +++ b/plugins/adr/README.md @@ -2,7 +2,7 @@ Welcome to the ADR plugin! -This plugin allows you to browse ADRs associated with your entities as well as a way to discover ADRs across others entities via Backstage Search. Use this to learn from the past experience of other projects to guide your own architecture decisions. +This plugin allows you to explore ADRs (Architecture Decision Records) associated with your entities, as well as discover ADRs across other entities using Backstage Search. Use this to inform your own architectural decisions based on the experiences of previous projects. ![ADR tab](./docs/adr-tab.png) diff --git a/plugins/adr/alpha-api-report.md b/plugins/adr/api-report-alpha.md similarity index 67% rename from plugins/adr/alpha-api-report.md rename to plugins/adr/api-report-alpha.md index 5740556904..bfa27a6d4f 100644 --- a/plugins/adr/alpha-api-report.md +++ b/plugins/adr/api-report-alpha.md @@ -4,11 +4,11 @@ ```ts import { BackstagePlugin } from '@backstage/frontend-plugin-api'; -import { Extension } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) -export const AdrSearchResultListItemExtension: Extension<{ +export const adrSearchResultListItemExtension: ExtensionDefinition<{ lineClamp: number; noTrack: boolean; }>; @@ -17,9 +17,9 @@ export const AdrSearchResultListItemExtension: Extension<{ export const adrTranslationRef: TranslationRef< 'adr', { - readonly content_header_title: 'Architecture Decision Records'; - readonly failed_to_fetch: 'Failed to fetch ADRs'; - readonly no_adrs: 'No ADRs found'; + readonly contentHeaderTitle: 'Architecture Decision Records'; + readonly failedToFetch: 'Failed to fetch ADRs'; + readonly notFound: 'No ADRs found'; } >; diff --git a/plugins/adr/package.json b/plugins/adr/package.json index 47468844ce..d2157dcae7 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr", - "version": "0.6.11-next.0", + "version": "0.6.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -51,10 +51,8 @@ "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-search-common": "workspace:^", "@backstage/plugin-search-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "lodash": "^4.17.21", "react-markdown": "^8.0.0", @@ -68,16 +66,9 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", - "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "@types/git-url-parse": "^9.0.0", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/adr/src/alpha.tsx b/plugins/adr/src/alpha.tsx index 1f6fa0711d..4abba11fcf 100644 --- a/plugins/adr/src/alpha.tsx +++ b/plugins/adr/src/alpha.tsx @@ -29,9 +29,8 @@ function isAdrDocument(result: any): result is AdrDocument { } /** @alpha */ -export const AdrSearchResultListItemExtension = +export const adrSearchResultListItemExtension = createSearchResultListItemExtension({ - id: 'adr', configSchema: createSchemaFromZod(z => z.object({ // TODO: Define how the icon can be configurable @@ -54,5 +53,5 @@ export const AdrSearchResultListItemExtension = /** @alpha */ export default createPlugin({ id: 'adr', - extensions: [AdrSearchResultListItemExtension], + extensions: [adrSearchResultListItemExtension], }); diff --git a/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx b/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx index 261dd994fb..5c8d244d26 100644 --- a/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx +++ b/plugins/adr/src/components/EntityAdrContent/EntityAdrContent.tsx @@ -219,7 +219,7 @@ export const EntityAdrContent = (props: { return ( - + {appSupportConfigured && } @@ -230,7 +230,7 @@ export const EntityAdrContent = (props: { {loading && } {entityHasAdrs && !loading && error && ( - + )} {entityHasAdrs && @@ -257,7 +257,7 @@ export const EntityAdrContent = (props: { ) : ( - {t('no_adrs')} + {t('notFound')} ))} ); diff --git a/plugins/adr/src/translations.ts b/plugins/adr/src/translations.ts index cc0995f597..038b0fd064 100644 --- a/plugins/adr/src/translations.ts +++ b/plugins/adr/src/translations.ts @@ -19,8 +19,8 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; export const adrTranslationRef = createTranslationRef({ id: 'adr', messages: { - content_header_title: 'Architecture Decision Records', - failed_to_fetch: 'Failed to fetch ADRs', - no_adrs: 'No ADRs found', + contentHeaderTitle: 'Architecture Decision Records', + failedToFetch: 'Failed to fetch ADRs', + notFound: 'No ADRs found', }, }); diff --git a/plugins/airbrake-backend/CHANGELOG.md b/plugins/airbrake-backend/CHANGELOG.md index 4ce8e4b3b5..85f5e412ed 100644 --- a/plugins/airbrake-backend/CHANGELOG.md +++ b/plugins/airbrake-backend/CHANGELOG.md @@ -1,5 +1,87 @@ # @backstage/plugin-airbrake-backend +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + +## 0.3.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + +## 0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.3.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + +## 0.3.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + +## 0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index a3ac0b4c27..d3424d130d 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake-backend", - "version": "0.3.5-next.0", + "version": "0.3.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -42,7 +42,6 @@ "@backstage/cli": "workspace:^", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", - "msw": "^1.0.0", "supertest": "^6.1.6" }, "files": [ diff --git a/plugins/airbrake/CHANGELOG.md b/plugins/airbrake/CHANGELOG.md index 19bc827a72..bf89516a38 100644 --- a/plugins/airbrake/CHANGELOG.md +++ b/plugins/airbrake/CHANGELOG.md @@ -1,5 +1,115 @@ # @backstage/plugin-airbrake +## 0.3.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/test-utils@1.5.0-next.0 + - @backstage/dev-utils@1.0.27-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.3.29 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/dev-utils@1.0.26 + - @backstage/catalog-model@1.4.3 + - @backstage/test-utils@1.4.7 + +## 0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/dev-utils@1.0.26-next.2 + +## 0.3.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/dev-utils@1.0.26-next.1 + - @backstage/test-utils@1.4.7-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.3.29-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/dev-utils@1.0.26-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/test-utils@1.4.7-next.0 + +## 0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/test-utils@1.4.6 + - @backstage/dev-utils@1.0.25 + - @backstage/catalog-model@1.4.3 + +## 0.3.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/dev-utils@1.0.25-next.3 + - @backstage/test-utils@1.4.6-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/dev-utils@1.0.25-next.2 + - @backstage/test-utils@1.4.6-next.2 + +## 0.3.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/test-utils@1.4.6-next.1 + - @backstage/dev-utils@1.0.25-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.3.28-next.0 ### Patch Changes diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 932aa55e91..904dbcef76 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake", - "version": "0.3.28-next.0", + "version": "0.3.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,10 +35,8 @@ "@backstage/dev-utils": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/test-utils": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "react-use": "^17.2.4" }, @@ -55,7 +53,6 @@ "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0" }, "files": [ diff --git a/plugins/allure/CHANGELOG.md b/plugins/allure/CHANGELOG.md index c72b6eedb6..4b99d120fb 100644 --- a/plugins/allure/CHANGELOG.md +++ b/plugins/allure/CHANGELOG.md @@ -1,5 +1,98 @@ # @backstage/plugin-allure +## 0.1.46-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.1.45 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.1.45-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.1.45-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.1.44 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## 0.1.44-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.44-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.1.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.1.44-next.0 ### Patch Changes diff --git a/plugins/allure/package.json b/plugins/allure/package.json index ee1952c819..9254b6c0c8 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-allure", "description": "A Backstage plugin that integrates with Allure", - "version": "0.1.44-next.0", + "version": "0.1.46-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,6 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", - "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "react-use": "^17.2.4" }, @@ -48,13 +44,11 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0" }, "files": [ diff --git a/plugins/analytics-module-ga/CHANGELOG.md b/plugins/analytics-module-ga/CHANGELOG.md index acf6f0eb98..5135b15ac1 100644 --- a/plugins/analytics-module-ga/CHANGELOG.md +++ b/plugins/analytics-module-ga/CHANGELOG.md @@ -1,5 +1,74 @@ # @backstage/plugin-analytics-module-ga +## 0.1.37 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + +## 0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/config@1.1.1 + +## 0.1.37-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + +## 0.1.36 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/config@1.1.1 + +## 0.1.36-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## 0.1.36-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + ## 0.1.36-next.0 ### Patch Changes diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index 9cace65a5d..3d367a5889 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-analytics-module-ga", - "version": "0.1.36-next.0", + "version": "0.1.37", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,12 +32,7 @@ "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/theme": "workspace:^", - "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", - "react-ga": "^3.3.0", - "react-use": "^17.2.4" + "react-ga": "^3.3.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", @@ -46,15 +41,11 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "@types/react": "^16.13.1 || ^17.0.0", - "msw": "^1.0.0" + "@types/react": "^16.13.1 || ^17.0.0" }, "files": [ "dist", diff --git a/plugins/analytics-module-ga4/CHANGELOG.md b/plugins/analytics-module-ga4/CHANGELOG.md index a929abaf61..6529fc3711 100644 --- a/plugins/analytics-module-ga4/CHANGELOG.md +++ b/plugins/analytics-module-ga4/CHANGELOG.md @@ -1,5 +1,75 @@ # @backstage/plugin-analytics-module-ga4 +## 0.1.8 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/config@1.1.1 + +## 0.1.8-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + +## 0.1.7 + +### Patch Changes + +- af6f227: Disabled `send_page_view` to get rid of events duplication +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/config@1.1.1 + +## 0.1.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## 0.1.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + ## 0.1.7-next.0 ### Patch Changes diff --git a/plugins/analytics-module-ga4/README.md b/plugins/analytics-module-ga4/README.md index 93138a8235..2d8c930e30 100644 --- a/plugins/analytics-module-ga4/README.md +++ b/plugins/analytics-module-ga4/README.md @@ -87,6 +87,7 @@ Additional dimensional data can be captured using custom dimensions, like this: attribute names will be prefixed by `a_`. 5. `allowedContexts` and `allowedAttributes` are optional, if not provided, no additional context and attributes will be sent. 6. if `allowedContexts` or `allowedAttributes` is set to '\*', all context and attributes will be sent. +7. `enableSendPageView` is used to send default events and is disabled by default. ```yaml app: diff --git a/plugins/analytics-module-ga4/config.d.ts b/plugins/analytics-module-ga4/config.d.ts index 4834d75f58..0a60ee5f9e 100644 --- a/plugins/analytics-module-ga4/config.d.ts +++ b/plugins/analytics-module-ga4/config.d.ts @@ -54,6 +54,14 @@ export interface Config { */ debug?: boolean; + /** + * Whether to send default send_page_view event. + * Defaults to false. + * + * @visibility frontend + */ + enableSendPageView?: boolean; + /** * Prevents events from actually being sent when set to true. Defaults * to false. diff --git a/plugins/analytics-module-ga4/package.json b/plugins/analytics-module-ga4/package.json index 90155e34a8..97b610c2bf 100644 --- a/plugins/analytics-module-ga4/package.json +++ b/plugins/analytics-module-ga4/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-analytics-module-ga4", - "version": "0.1.7-next.0", + "version": "0.1.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,7 @@ "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/theme": "workspace:^", - "react-ga4": "^2.0.0", - "react-use": "^17.2.4" + "react-ga4": "^2.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", @@ -43,16 +41,12 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "@types/jest": "^29.0.0", - "@types/react": "^16.13.1 || ^17.0.0", - "msw": "^1.0.0" + "@types/react": "^16.13.1 || ^17.0.0" }, "files": [ "dist", diff --git a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts index 399366b804..9abf4719c6 100644 --- a/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts +++ b/plugins/analytics-module-ga4/src/apis/implementations/AnalyticsApi/GoogleAnalytics4.ts @@ -47,6 +47,7 @@ export class GoogleAnalytics4 implements AnalyticsApi { measurementId: string; testMode: boolean; debug: boolean; + enableSendPageView: boolean; contentGroupBy?: string; allowedContexts?: string[]; allowedAttributes?: string[]; @@ -58,6 +59,7 @@ export class GoogleAnalytics4 implements AnalyticsApi { userIdTransform = 'sha-256', testMode, debug, + enableSendPageView, contentGroupBy, allowedContexts, allowedAttributes, @@ -70,6 +72,7 @@ export class GoogleAnalytics4 implements AnalyticsApi { }, gtagOptions: { debug_mode: debug, + send_page_view: enableSendPageView, }, }); @@ -112,6 +115,9 @@ export class GoogleAnalytics4 implements AnalyticsApi { const identity = config.getOptionalString('app.analytics.ga4.identity') || 'disabled'; const debug = config.getOptionalBoolean('app.analytics.ga4.debug') ?? false; + const enableSendPageView = + config.getOptionalBoolean('app.analytics.ga4.enableSendPageView') ?? + false; const testMode = config.getOptionalBoolean('app.analytics.ga4.testMode') ?? false; @@ -138,6 +144,7 @@ export class GoogleAnalytics4 implements AnalyticsApi { measurementId: measurementId, testMode, debug, + enableSendPageView, contentGroupBy, allowedContexts, allowedAttributes, diff --git a/plugins/analytics-module-newrelic-browser/CHANGELOG.md b/plugins/analytics-module-newrelic-browser/CHANGELOG.md index 86a86de836..0f9b210d9a 100644 --- a/plugins/analytics-module-newrelic-browser/CHANGELOG.md +++ b/plugins/analytics-module-newrelic-browser/CHANGELOG.md @@ -1,5 +1,70 @@ # @backstage/plugin-analytics-module-newrelic-browser +## 0.0.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + +## 0.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/config@1.1.1 + +## 0.0.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + +## 0.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/config@1.1.1 + +## 0.0.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + ## 0.0.5-next.0 ### Patch Changes diff --git a/plugins/analytics-module-newrelic-browser/package.json b/plugins/analytics-module-newrelic-browser/package.json index c9d876c315..0815b62b6c 100644 --- a/plugins/analytics-module-newrelic-browser/package.json +++ b/plugins/analytics-module-newrelic-browser/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-analytics-module-newrelic-browser", - "version": "0.0.5-next.0", + "version": "0.0.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,21 +26,16 @@ "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@newrelic/browser-agent": "^1.236.0", - "react-use": "^17.2.4" + "@newrelic/browser-agent": "^1.236.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist", diff --git a/plugins/apache-airflow/CHANGELOG.md b/plugins/apache-airflow/CHANGELOG.md index 959d447f55..9c2a434030 100644 --- a/plugins/apache-airflow/CHANGELOG.md +++ b/plugins/apache-airflow/CHANGELOG.md @@ -1,5 +1,63 @@ # @backstage/plugin-apache-airflow +## 0.2.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## 0.2.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## 0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + +## 0.2.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + ## 0.2.18-next.0 ### Patch Changes diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index f1d122f4f5..489fcde5e8 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apache-airflow", - "version": "0.2.18-next.0", + "version": "0.2.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -52,7 +52,6 @@ "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0" }, "files": [ diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index d88c90e769..4a293884f1 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,123 @@ # @backstage/plugin-api-docs +## 0.10.4-next.0 + +### Patch Changes + +- 170c023: Adding `supportedSubmitMethods` prop to `api-docs` to pass to the Swagger UI. This allows users to specify which HTTP methods they wish to allow end-users to make requests through the `Try It Out` button on the Swagger UI. +- c03f977: Updated dependency `graphiql` to `3.1.0`. +- 49b3b5e: Updated dependency `@asyncapi/react-component` to `1.2.13`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-catalog@1.17.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-react@0.4.19 + +## 0.10.3 + +### Patch Changes + +- 8a69cc9: Fix custom http resolvers for AsyncAPI widget. +- 062b8f2: Add permission check to Register Existing API button +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-catalog@1.16.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.10.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.16.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.10.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog@1.16.1-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.10.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-catalog@1.16.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.10.2 + +### Patch Changes + +- 816d331: Add dependency on `graphql-config` to compensate for `graphql-language-service` needing it but not shipping the dep properly +- 615159e: Updated dependency `graphiql` to `3.0.10`. +- e16e7ce: Updated dependency `@asyncapi/react-component` to `1.2.2`. +- 82fb18b: Updated dependency `@asyncapi/react-component` to `1.2.6`. +- 53e2c06: Updated dependency `@asyncapi/react-component` to `1.1.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-catalog@1.16.0 + - @backstage/catalog-model@1.4.3 + +## 0.10.2-next.4 + +### Patch Changes + +- 82fb18b: Updated dependency `@asyncapi/react-component` to `1.2.6`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog@1.16.0-next.4 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.10.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog@1.16.0-next.3 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.10.2-next.2 + +### Patch Changes + +- e16e7ce6a5: Updated dependency `@asyncapi/react-component` to `1.2.2`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-catalog@1.16.0-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.10.2-next.1 ### Patch Changes diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index 79c4581e79..46d9df4628 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -13,7 +13,7 @@ The plugin provides a standalone list of APIs, as well as an integration into th Right now, the following API formats are supported: - [OpenAPI](https://swagger.io/specification/) 2 & 3 -- [AsyncAPI](https://www.asyncapi.com/docs/specifications/latest/) +- [AsyncAPI](https://www.asyncapi.com/docs/reference/specification/latest) - [GraphQL](https://graphql.org/learn/schema/) Other formats are displayed as plain text, but this can easily be extended. @@ -263,3 +263,56 @@ createApiFactory({ ``` In the same way as the `requestInterceptor` you can override any property of Swagger UI + +### Provide Specific Supported Methods to Swagger UI + +This can be done through utilising the +[supportedSubmitMethods prop](https://github.com/swagger-api/swagger-ui/tree/master/flavors/swagger-ui-react#supportedsubmitmethods-proptypesarrayofproptypesoneofget-put-post-delete-options-head-patch-trace). +If you wish to limit the HTTP methods available for the `Try It Out` feature of an OpenAPI API +component, you will need to add the following to your `api.tsx`, listing the permitted methods for +your API in the `supportedSubmitMethods` parameter: + +```tsx +... +import { + OpenApiDefinitionWidget, + apiDocsConfigRef, + defaultDefinitionWidgets, +} from '@backstage/plugin-api-docs'; +import { ApiEntity } from '@backstage/catalog-model'; + +export const apis: AnyApiFactory[] = [ +... + createApiFactory({ + api: apiDocsConfigRef, + deps: {}, + factory: () => { + const supportedSubmitMethods = ['get', 'post', 'put', 'delete']; + const definitionWidgets = defaultDefinitionWidgets().map(obj => { + if (obj.type === 'openapi') { + return { + ...obj, + component: definition => ( + + ), + }; + } + return obj; + }); + + return { + getApiDefinitionWidget: (apiEntity: ApiEntity) => { + return definitionWidgets.find(d => d.type === apiEntity.spec.type); + } + }; + } + }) +] + +``` + +N.B. if you wish to disable the `Try It Out` feature for your API, you can provide an empty list to +the `supportedSubmitMethods` parameter. diff --git a/plugins/api-docs/api-report.md b/plugins/api-docs/api-report.md index a018dfcdb1..7523198298 100644 --- a/plugins/api-docs/api-report.md +++ b/plugins/api-docs/api-report.md @@ -166,6 +166,7 @@ export const OpenApiDefinitionWidget: ( export type OpenApiDefinitionWidgetProps = { definition: string; requestInterceptor?: (req: any) => any | Promise; + supportedSubmitMethods?: string[]; }; // @public (undocumented) diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index ad7eb8aac7..3147c080a8 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-api-docs", "description": "A Backstage plugin that helps represent API entities in the frontend", - "version": "0.10.2-next.1", + "version": "0.10.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,24 +33,24 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@asyncapi/react-component": "1.1.0", + "@asyncapi/react-component": "1.2.13", "@backstage/catalog-model": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog": "workspace:^", + "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", + "@backstage/plugin-permission-react": "workspace:^", "@graphiql/react": "^0.20.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", - "graphiql": "3.0.10", + "graphiql": "3.1.0", "graphql": "^16.0.0", "graphql-config": "^5.0.2", "graphql-ws": "^5.4.1", "isomorphic-form-data": "^2.0.0", - "react-use": "^17.2.4", "swagger-ui-react": "^5.0.0" }, "peerDependencies": { @@ -67,9 +67,7 @@ "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.0.0", - "@types/swagger-ui-react": "^4.18.0", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@types/swagger-ui-react": "^4.18.0" }, "files": [ "dist" diff --git a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx index 6d0b191d68..7938770c4c 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx @@ -32,6 +32,7 @@ import { starredEntitiesApiRef, } from '@backstage/plugin-catalog-react'; import { + MockPermissionApi, MockStorageApi, TestApiProvider, renderInTestApp, @@ -41,6 +42,7 @@ import { screen, waitFor } from '@testing-library/react'; import React from 'react'; import { apiDocsConfigRef } from '../../config'; import { DefaultApiExplorerPage } from './DefaultApiExplorerPage'; +import { permissionApiRef } from '@backstage/plugin-permission-react'; describe('DefaultApiExplorerPage', () => { const catalogApi: Partial = { @@ -98,6 +100,7 @@ describe('DefaultApiExplorerPage', () => { new DefaultStarredEntitiesApi({ storageApi }), ], [apiDocsConfigRef, apiDocsConfig], + [permissionApiRef, new MockPermissionApi()], ]} > {children} diff --git a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx index ad43205c5f..dc83c2528d 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.tsx @@ -38,6 +38,8 @@ import { } from '@backstage/plugin-catalog-react'; import React from 'react'; import { registerComponentRouteRef } from '../../routes'; +import { usePermission } from '@backstage/plugin-permission-react'; +import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; const defaultColumns: TableColumn[] = [ CatalogTable.columns.createTitleColumn({ hidden: true }), @@ -72,6 +74,9 @@ export const DefaultApiExplorerPage = (props: DefaultApiExplorerPageProps) => { configApi.getOptionalString('organization.name') ?? 'Backstage' } API Explorer`; const registerComponentLink = useRouteRef(registerComponentRouteRef); + const { allowed } = usePermission({ + permission: catalogEntityCreatePermission, + }); return ( { > - + {allowed && ( + + )} All your APIs diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx index 3e2fbd1078..461e466b4f 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx @@ -143,18 +143,33 @@ const useStyles = makeStyles(theme => ({ }, })); -const fetchResolver = { - order: 199, // Use 199 as the built-in http resolver is 200 - canRead: /^https?:\/\//, - async read(file: any) { - const response = await fetch(file.url); +const httpsFetchResolver = { + schema: 'https', + order: 1, + canRead: true, + async read(uri: any) { + const response = await fetch(uri.toString()); + return response.text(); + }, +}; + +const httpFetchResolver = { + schema: 'http', + order: 1, + canRead: true, + async read(uri: any) { + const response = await fetch(uri.toString()); return response.text(); }, }; const config = { parserOptions: { - resolve: { fetch: fetchResolver }, + __unstable: { + resolver: { + resolvers: [httpsFetchResolver, httpFetchResolver], + }, + }, }, }; diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.test.tsx b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.test.tsx index d150525b05..524c416d2d 100644 --- a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.test.tsx +++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.test.tsx @@ -97,11 +97,13 @@ paths: `; const requestInterceptor = (req: any) => req; + const supportedSubmitMethods = ['get', 'post', 'put', 'delete']; const { findByRole, getByRole, getByLabelText } = await renderInTestApp( , ); diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx index 152f1fd089..64ff324941 100644 --- a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx +++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx @@ -29,15 +29,22 @@ const LazyOpenApiDefinition = React.lazy(() => export type OpenApiDefinitionWidgetProps = { definition: string; requestInterceptor?: (req: any) => any | Promise; + supportedSubmitMethods?: string[]; }; /** @public */ export const OpenApiDefinitionWidget = ( props: OpenApiDefinitionWidgetProps, ) => { + const validSubmitMethods = props.supportedSubmitMethods?.map(method => + method.toLocaleLowerCase(), + ); return ( }> - + ); }; diff --git a/plugins/apollo-explorer/CHANGELOG.md b/plugins/apollo-explorer/CHANGELOG.md index 26cda3787a..1523721718 100644 --- a/plugins/apollo-explorer/CHANGELOG.md +++ b/plugins/apollo-explorer/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/plugin-apollo-explorer +## 0.1.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## 0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## 0.1.18 + +### Patch Changes + +- e296b94: Updated dependency `@apollo/explorer` to `^3.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## 0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## 0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + ## 0.1.18-next.0 ### Patch Changes diff --git a/plugins/apollo-explorer/package.json b/plugins/apollo-explorer/package.json index 0cbc7cd292..37895e3343 100644 --- a/plugins/apollo-explorer/package.json +++ b/plugins/apollo-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apollo-explorer", - "version": "0.1.18-next.0", + "version": "0.1.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,11 +32,8 @@ "@apollo/explorer": "^3.0.0", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", "@types/react": "^16.13.1 || ^17.0.0", - "react-use": "^17.2.4", "use-deep-compare-effect": "^1.8.1" }, "peerDependencies": { @@ -46,14 +43,10 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index d3c21b03fd..4a5361361d 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,114 @@ # @backstage/plugin-app-backend +## 0.3.58-next.0 + +### Patch Changes + +- 9dfd57d: Do not force caching of the Javascript asset that contains the injected config. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/config-loader@1.6.1 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.10-next.0 + +## 0.3.57 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config-loader@1.6.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.9 + +## 0.3.57-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-app-node@0.1.9-next.2 + - @backstage/config-loader@1.6.1-next.0 + +## 0.3.57-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.1-next.0 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.9-next.1 + +## 0.3.57-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.9-next.0 + +## 0.3.56 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config-loader@1.6.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.8 + +## 0.3.56-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.8-next.3 + +## 0.3.56-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.0-next.0 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.8-next.2 + +## 0.3.56-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.5.3 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.8-next.1 + ## 0.3.56-next.0 ### Patch Changes diff --git a/plugins/app-backend/alpha-api-report.md b/plugins/app-backend/api-report-alpha.md similarity index 100% rename from plugins/app-backend/alpha-api-report.md rename to plugins/app-backend/api-report-alpha.md diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 87f6b6827c..3722512874 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.56-next.0", + "version": "0.3.58-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -69,7 +69,6 @@ "@backstage/cli": "workspace:^", "@backstage/types": "workspace:^", "@types/supertest": "^2.0.8", - "msw": "^1.0.0", "node-fetch": "^2.6.7", "supertest": "^6.1.3" }, diff --git a/plugins/app-backend/src/lib/assets/StaticAssetsStore.test.ts b/plugins/app-backend/src/lib/assets/StaticAssetsStore.test.ts index f14349a55b..e18d136b9d 100644 --- a/plugins/app-backend/src/lib/assets/StaticAssetsStore.test.ts +++ b/plugins/app-backend/src/lib/assets/StaticAssetsStore.test.ts @@ -37,7 +37,7 @@ jest.setTimeout(60_000); describe('StaticAssetsStore', () => { const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], + ids: ['MYSQL_8', 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'], }); it.each(databases.eachSupportedId())( diff --git a/plugins/app-backend/src/lib/config.ts b/plugins/app-backend/src/lib/config.ts index 6959253336..05b304075e 100644 --- a/plugins/app-backend/src/lib/config.ts +++ b/plugins/app-backend/src/lib/config.ts @@ -31,7 +31,9 @@ type InjectOptions = { /** * Injects configs into the app bundle, replacing any existing injected config. */ -export async function injectConfig(options: InjectOptions) { +export async function injectConfig( + options: InjectOptions, +): Promise { const { staticDir, logger, appConfigs } = options; const files = await fs.readdir(staticDir); @@ -52,7 +54,7 @@ export async function injectConfig(options: InjectOptions) { injected, ); await fs.writeFile(path, newContent, 'utf8'); - return; + return path; } else if (content.includes('__APP_INJECTED_CONFIG_MARKER__')) { logger.info(`Replacing injected env config in ${jsFile}`); @@ -61,10 +63,11 @@ export async function injectConfig(options: InjectOptions) { injected, ); await fs.writeFile(path, newContent, 'utf8'); - return; + return path; } } logger.info('Env config not injected'); + return undefined; } type ReadOptions = { diff --git a/plugins/app-backend/src/lib/headers.ts b/plugins/app-backend/src/lib/headers.ts index 1bee2bbb37..2483fdeee2 100644 --- a/plugins/app-backend/src/lib/headers.ts +++ b/plugins/app-backend/src/lib/headers.ts @@ -16,3 +16,4 @@ export const CACHE_CONTROL_NO_CACHE = 'no-store, max-age=0'; export const CACHE_CONTROL_MAX_CACHE = 'public, max-age=1209600'; // 14 days +export const CACHE_CONTROL_REVALIDATE_CACHE = 'no-cache'; // require revalidating cached responses before reuse them. diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index 40e291c19d..5574892d37 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -35,6 +35,7 @@ import { import { CACHE_CONTROL_MAX_CACHE, CACHE_CONTROL_NO_CACHE, + CACHE_CONTROL_REVALIDATE_CACHE, } from '../lib/headers'; // express uses mime v1 while we only have types for mime v2 @@ -114,6 +115,7 @@ export async function createRouter( logger.info(`Serving static app content from ${appDistDir}`); + let injectedConfigPath: string | undefined; if (!disableConfigInjection) { const appConfigs = await readConfigs({ config, @@ -121,7 +123,7 @@ export async function createRouter( env: process.env, }); - await injectConfig({ appConfigs, logger, staticDir }); + injectedConfigPath = await injectConfig({ appConfigs, logger, staticDir }); } const router = Router(); @@ -132,8 +134,12 @@ export async function createRouter( const staticRouter = Router(); staticRouter.use( express.static(resolvePath(appDistDir, 'static'), { - setHeaders: res => { - res.setHeader('Cache-Control', CACHE_CONTROL_MAX_CACHE); + setHeaders: (res, path) => { + if (path === injectedConfigPath) { + res.setHeader('Cache-Control', CACHE_CONTROL_REVALIDATE_CACHE); + } else { + res.setHeader('Cache-Control', CACHE_CONTROL_MAX_CACHE); + } }, }), ); diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index f901f5b879..cef24b16ff 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/plugin-app-node +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + +## 0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + +## 0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + +## 0.1.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + ## 0.1.8-next.0 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index 042703f5ab..345b3fbdc0 100644 --- a/plugins/app-node/package.json +++ b/plugins/app-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-node", "description": "Node.js library for the app plugin", - "version": "0.1.8-next.0", + "version": "0.1.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-visualizer/.eslintrc.js b/plugins/app-visualizer/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/app-visualizer/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md new file mode 100644 index 0000000000..ee679a979b --- /dev/null +++ b/plugins/app-visualizer/CHANGELOG.md @@ -0,0 +1,23 @@ +# @backstage/plugin-app-visualizer + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 0.1.0 + +### Minor Changes + +- e57cc9f: Initial release of the app visualizer plugin. + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 diff --git a/plugins/app-visualizer/README.md b/plugins/app-visualizer/README.md new file mode 100644 index 0000000000..865cd66746 --- /dev/null +++ b/plugins/app-visualizer/README.md @@ -0,0 +1,10 @@ +# @backstage/plugin-app-visualizer + +A plugin to help explore the structure of your Backstage app. + +This plugin provides the following extensions: + +| ID | Type | Description | Default Config | +| ------------------------- | --------- | ------------------------------------ | ------------------------- | +| `page:app-visualizer` | `Page` | The app visualizer page | `{ path: '/visualizer' }` | +| `nav-item:app-visualizer` | `NavItem` | Nav item for the app visualizer page | | diff --git a/plugins/app-visualizer/api-report.md b/plugins/app-visualizer/api-report.md new file mode 100644 index 0000000000..764caa7190 --- /dev/null +++ b/plugins/app-visualizer/api-report.md @@ -0,0 +1,13 @@ +## API Report File for "@backstage/plugin-app-visualizer" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackstagePlugin } from '@backstage/frontend-plugin-api'; + +// @public (undocumented) +const visualizerPlugin: BackstagePlugin<{}, {}>; +export default visualizerPlugin; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/app-visualizer/catalog-info.yaml b/plugins/app-visualizer/catalog-info.yaml new file mode 100644 index 0000000000..cb444af524 --- /dev/null +++ b/plugins/app-visualizer/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-app-visualizer + title: '@backstage/plugin-app-visualizer' + description: Visualizes the Backstage app structure +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json new file mode 100644 index 0000000000..49ebffd3f0 --- /dev/null +++ b/plugins/app-visualizer/package.json @@ -0,0 +1,43 @@ +{ + "name": "@backstage/plugin-app-visualizer", + "description": "Visualizes the Backstage app structure", + "version": "0.1.1-next.0", + "publishConfig": { + "access": "public" + }, + "backstage": { + "role": "frontend-plugin" + }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "sideEffects": false, + "scripts": { + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" + }, + "dependencies": { + "@backstage/core-components": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-router-dom": "6.0.0-beta.0 || ^6.3.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@types/react": "^16.13.1 || ^17.0.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/AppVisualizerPage.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/AppVisualizerPage.tsx new file mode 100644 index 0000000000..b984f8ac59 --- /dev/null +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/AppVisualizerPage.tsx @@ -0,0 +1,100 @@ +/* + * 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 { Content, Header, HeaderTabs, Page } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; +import { appTreeApiRef } from '@backstage/frontend-plugin-api'; +import Box from '@material-ui/core/Box'; +import React, { useCallback, useEffect, useMemo } from 'react'; +import { DetailedVisualizer } from './DetailedVisualizer'; +import { TextVisualizer } from './TextVisualizer'; +import { TreeVisualizer } from './TreeVisualizer'; +import { + matchRoutes, + useLocation, + useNavigate, + useParams, + useRoutes, +} from 'react-router-dom'; + +export function AppVisualizerPage() { + const appTreeApi = useApi(appTreeApiRef); + const { tree } = appTreeApi.getTree(); + + const tabs = useMemo( + () => [ + { + id: 'tree', + path: 'tree', + label: 'Tree', + element: , + }, + { + id: 'detailed', + path: 'detailed', + label: 'Detailed', + element: , + }, + { + id: 'text', + path: 'text', + label: 'Text', + element: , + }, + ], + [tree], + ); + + const location = useLocation(); + const element = useRoutes(tabs, location); + + const currentPath = `/${useParams()['*']}`; + const [matchedRoute] = matchRoutes(tabs, currentPath) ?? []; + + const currentTabIndex = matchedRoute + ? tabs.findIndex(t => t.path === matchedRoute.route.path) + : 0; + + const navigate = useNavigate(); + const handleTabChange = useCallback( + (index: number) => { + navigate(tabs[index].id); + }, + [navigate, tabs], + ); + + useEffect(() => { + if (!element) { + navigate(tabs[0].path); + } + }, [element, navigate, tabs]); + + return ( + +
    + + + + {element} + + + + ); +} diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx new file mode 100644 index 0000000000..2356f38cfa --- /dev/null +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/DetailedVisualizer.tsx @@ -0,0 +1,377 @@ +/* + * 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 { + AppNode, + AppTree, + ExtensionDataRef, + RouteRef, + coreExtensionData, + createApiExtension, + createNavItemExtension, + createThemeExtension, + useRouteRef, +} from '@backstage/frontend-plugin-api'; +import Box from '@material-ui/core/Box'; +import Paper from '@material-ui/core/Paper'; +import Tooltip from '@material-ui/core/Tooltip'; +import Typography from '@material-ui/core/Typography'; +import * as colors from '@material-ui/core/colors'; +import { makeStyles } from '@material-ui/core/styles'; +import InputIcon from '@material-ui/icons/InputSharp'; +import DisabledIcon from '@material-ui/icons/NotInterestedSharp'; +import React from 'react'; +import { Link } from 'react-router-dom'; + +function createOutputColorGenerator( + colorMap: { [extDataId: string]: string }, + availableColors: string[], +) { + const map = new Map(); + let i = 0; + + return function getOutputColor(id: string) { + if (id in colorMap) { + return colorMap[id]; + } + let color = map.get(id); + if (color) { + return color; + } + color = availableColors[i]; + i += 1; + if (i >= availableColors.length) { + i = 0; + } + map.set(id, color); + return color; + }; +} + +const getOutputColor = createOutputColorGenerator( + { + [coreExtensionData.reactElement.id]: colors.green[500], + [coreExtensionData.routePath.id]: colors.yellow[500], + [coreExtensionData.routeRef.id]: colors.purple[500], + [createApiExtension.factoryDataRef.id]: colors.blue[500], + [createThemeExtension.themeDataRef.id]: colors.lime[500], + [createNavItemExtension.targetDataRef.id]: colors.orange[500], + }, + + [ + colors.blue[200], + colors.orange[200], + colors.green[200], + colors.red[200], + colors.yellow[200], + colors.purple[200], + colors.lime[200], + ], +); + +interface StyleProps { + enabled: boolean; + depth: number; +} + +const config = { + borderWidth: 0.75, +}; + +const useStyles = makeStyles(theme => ({ + extension: { + borderLeftWidth: theme.spacing(config.borderWidth), + borderLeftStyle: 'solid', + borderLeftColor: ({ depth }: StyleProps) => + colors.grey[(700 - (depth % 6) * 100) as keyof typeof colors.grey], + cursor: 'pointer', + + '&:hover $extensionHeader': { + color: ({ enabled }: StyleProps) => + enabled ? theme.palette.primary.main : theme.palette.text.secondary, + }, + }, + extensionHeader: { + display: 'flex', + alignItems: 'center', + width: 'fit-content', + + padding: theme.spacing(0.5, 1), + color: ({ enabled }: StyleProps) => + enabled ? theme.palette.text.primary : theme.palette.text.disabled, + background: theme.palette.background.paper, + + borderTopRightRadius: theme.shape.borderRadius, + borderBottomRightRadius: theme.shape.borderRadius, + }, + extensionHeaderId: { + userSelect: 'all', + }, + extensionHeaderOutputs: { + display: 'flex', + alignItems: 'center', + marginLeft: theme.spacing(1), + gap: theme.spacing(1), + }, + attachments: { + gap: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + }, + attachmentsInput: { + '&:first-child $attachmentsInputTitle': { + borderTop: 0, + }, + }, + attachmentsInputTitle: { + display: 'flex', + alignItems: 'center', + width: 'fit-content', + padding: theme.spacing(1), + + borderTopWidth: theme.spacing(config.borderWidth), + borderTopStyle: 'solid', + borderTopColor: ({ depth }: StyleProps) => + colors.grey[(700 - (depth % 6) * 100) as keyof typeof colors.grey], + }, + attachmentsInputName: { + marginLeft: theme.spacing(1), + }, + attachmentsInputChildren: { + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + gap: theme.spacing(0.5), + marginLeft: theme.spacing(1), + marginBottom: theme.spacing(1), + }, +})); + +const useOutputStyles = makeStyles(theme => ({ + output: ({ color }: { color: string }) => ({ + padding: `0 10px`, + height: 20, + borderRadius: 10, + color: theme.palette.getContrastText(color), + backgroundColor: color, + }), +})); + +function getFullPath(node?: AppNode): string { + if (!node) { + return ''; + } + const parent = node.edges.attachedTo?.node; + const part = node.instance?.getData(coreExtensionData.routePath); + if (!part) { + return getFullPath(parent); + } + return getFullPath(parent) + part; +} + +function OutputLink(props: { + dataRef: ExtensionDataRef; + node?: AppNode; + className: string; +}) { + const routeRef = props.node?.instance?.getData(coreExtensionData.routeRef); + + let link: string | undefined = undefined; + try { + // eslint-disable-next-line react-hooks/rules-of-hooks + link = useRouteRef(routeRef as RouteRef)(); + } catch { + /* ignore */ + } + + return ( + {props.dataRef.id}}> + + {link ? link : null} + + + ); +} + +function Output(props: { dataRef: ExtensionDataRef; node?: AppNode }) { + const { dataRef, node } = props; + const { id } = dataRef; + const instance = node?.instance; + + const classes = useOutputStyles({ color: getOutputColor(id) }); + + if (id === coreExtensionData.routePath.id) { + return ( + {getFullPath(node)}}> + + {String(instance?.getData(dataRef) ?? '')} + + + ); + } + + if (id === coreExtensionData.routeRef.id) { + return ; + } + + return ( + {id}}> + + + ); +} + +function Attachments(props: { + node: AppNode; + enabled: boolean; + depth: number; +}) { + const { node, enabled, depth } = props; + const { attachments } = node.edges; + + const classes = useStyles({ enabled, depth }); + + if (attachments.size === 0) { + return null; + } + + return ( + + {[...attachments.entries()] + .sort(([a], [b]) => a.localeCompare(b)) + .map(([key, children]) => { + return ( + + + + + {key} + + + + {children.map(childNode => ( + + ))} + + + ); + })} + + ); +} + +function ExtensionTooltip(props: { node: AppNode }) { + const parts = []; + let node = props.node; + parts.push(node.spec.id); + while (node.edges.attachedTo) { + const input = node.edges.attachedTo.input; + node = node.edges.attachedTo.node; + parts.push(`${node.spec.id} [${input}]`); + } + parts.reverse(); + + return ( + <> + {parts.map(part => ( + {part} + ))} + + ); +} + +function Extension(props: { node: AppNode; depth: number }) { + const { node, depth } = props; + + const enabled = Boolean(node.instance); + const classes = useStyles({ enabled, depth }); + + const dataRefs = node.instance && [...node.instance.getDataRefs()]; + + return ( + + + }> + + {node.spec.id} + + + + {dataRefs && + dataRefs.length > 0 && + dataRefs + .sort((a, b) => a.id.localeCompare(b.id)) + .map(ref => )} + {!enabled && } + + + + + ); +} + +const legendMap = { + 'React Element': coreExtensionData.reactElement, + 'Utility API': createApiExtension.factoryDataRef, + 'Route Path': coreExtensionData.routePath, + 'Route Ref': coreExtensionData.routeRef, + 'Nav Target': createNavItemExtension.targetDataRef, + Theme: createThemeExtension.themeDataRef, +}; + +function Legend() { + return ( + + {Object.entries(legendMap).map(([label, dataRef]) => ( + + + {label} + + ))} + + ); +} + +export function DetailedVisualizer({ tree }: { tree: AppTree }) { + return ( + + + + + + + + + + ); +} diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/TextVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/TextVisualizer.tsx new file mode 100644 index 0000000000..50b1f61484 --- /dev/null +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/TextVisualizer.tsx @@ -0,0 +1,116 @@ +/* + * 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 { AppNode, AppTree } from '@backstage/frontend-plugin-api'; +import Box from '@material-ui/core/Box'; +import Checkbox from '@material-ui/core/Checkbox'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Paper from '@material-ui/core/Paper'; +import React, { ReactNode, useState } from 'react'; + +function mkDiv( + children: ReactNode, + options?: { indent?: boolean; key?: string | number; color?: string }, +) { + return ( +
    + {children} +
    + ); +} + +function nodeToText( + node: AppNode, + options?: { showOutputs?: boolean; showDisabled?: boolean }, +): ReactNode { + const dataRefIds = + node.instance && [...node.instance.getDataRefs()].map(r => r.id); + const out = + options?.showOutputs && dataRefIds && dataRefIds.length > 0 + ? ` out="${[...dataRefIds].sort().join(', ')}"` + : ''; + const color = node.instance ? undefined : 'gray'; + + if (node.edges.attachments.size === 0) { + return mkDiv(`<${node.spec.id}${out}/>`, { color }); + } + + return mkDiv([ + mkDiv(`<${node.spec.id}${out}>`, { key: 'start', color }), + ...[...node.edges.attachments.entries()] + .sort(([a], [b]) => a.localeCompare(b)) + .map(([key, v]) => { + const children = v + .filter(e => options?.showDisabled || e.instance) + .sort((a, b) => a.spec.id.localeCompare(b.spec.id)); + if (children.length === 0) { + return mkDiv(`${key} []`, { indent: true }); + } + return mkDiv( + [ + mkDiv(`${key} [`), + ...children.map(e => + mkDiv(nodeToText(e, options), { indent: true }), + ), + mkDiv(']'), + ], + { key, indent: true }, + ); + }), + mkDiv(``, { key: 'end', color }), + ]); +} + +export function TextVisualizer({ tree }: { tree: AppTree }) { + const [showOutputs, setShowOutputs] = useState(false); + const [showDisabled, setShowDisabled] = useState(false); + + return ( + <> + +
    + {nodeToText(tree.root, { showOutputs, showDisabled })} +
    +
    + + setShowOutputs(value)} + /> + } + label="Show Outputs" + /> + setShowDisabled(value)} + /> + } + label="Show Disabled" + /> + + + ); +} diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/TreeVisualizer.tsx b/plugins/app-visualizer/src/components/AppVisualizerPage/TreeVisualizer.tsx new file mode 100644 index 0000000000..eac7c4e203 --- /dev/null +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/TreeVisualizer.tsx @@ -0,0 +1,172 @@ +/* + * 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 { + DependencyGraph, + DependencyGraphTypes, +} from '@backstage/core-components'; +import { AppNode, AppTree } from '@backstage/frontend-plugin-api'; +import Box from '@material-ui/core/Box'; +import { makeStyles } from '@material-ui/core/styles'; +import { useLayoutEffect, useMemo, useRef, useState } from 'react'; + +type NodeType = + | ({ type: 'node'; id: string } & AppNode) + | { type: 'input'; id: string; name: string }; + +function inputId({ node, input }: { node: AppNode; input: string }) { + return `${node.spec.id}$$${input}`; +} + +function trimNodeId(id: string) { + let newId = id; + if (newId.startsWith('apis.')) { + newId = newId.slice('apis.'.length); + } + if (newId.startsWith('plugin.')) { + newId = newId.slice('plugin.'.length); + } + if (newId.startsWith('catalog.filter.entity.')) { + newId = newId.slice('catalog.filter.entity.'.length); + } + if (newId.endsWith('.nav.index')) { + newId = newId.slice(0, -'.nav.index'.length); + } + return newId; +} + +function resolveGraphData(tree: AppTree): { + nodes: NodeType[]; + edges: { from: string; to: string }[]; +} { + const nodes = [...tree.nodes.values()] + .filter(node => node.instance) + .map(node => ({ ...node, id: node.spec.id, type: 'node' as const })); + + return { + nodes: [ + ...nodes, + ...nodes.flatMap(node => + [...node.edges.attachments.keys()].map(input => ({ + id: inputId({ node, input }), + type: 'input' as const, + name: input, + })), + ), + ], + edges: [ + ...nodes + .filter(node => node.edges.attachedTo) + .map(node => ({ + from: inputId(node.edges.attachedTo!), + to: node.spec.id, + })), + ...nodes.flatMap(node => + [...node.edges.attachments.keys()].map(input => ({ + from: node.spec.id, + to: inputId({ node, input }), + })), + ), + ], + }; +} + +const useStyles = makeStyles(theme => ({ + node: { + fill: (node: NodeType) => + node.type === 'node' + ? theme.palette.primary.light + : theme.palette.grey[500], + stroke: (node: NodeType) => + node.type === 'node' + ? theme.palette.primary.main + : theme.palette.grey[600], + }, + text: { + fill: theme.palette.primary.contrastText, + }, +})); + +/** @public */ +export function Node(props: { node: NodeType }) { + const { node } = props; + const classes = useStyles(node); + const [width, setWidth] = useState(0); + const [height, setHeight] = useState(0); + const idRef = useRef(null); + + useLayoutEffect(() => { + // set the width to the length of the ID + if (idRef.current) { + let { height: renderedHeight, width: renderedWidth } = + idRef.current.getBBox(); + renderedHeight = Math.round(renderedHeight); + renderedWidth = Math.round(renderedWidth); + + if (renderedHeight !== height || renderedWidth !== width) { + setWidth(renderedWidth); + setHeight(renderedHeight); + } + } + }, [width, height]); + + const padding = 10; + const paddedWidth = width + padding * 2; + const paddedHeight = height + padding * 2; + + return ( + + + + {node.type === 'node' ? trimNodeId(node.id) : node.name} + + + ); +} + +export function TreeVisualizer({ tree }: { tree: AppTree }) { + const graphData = useMemo(() => resolveGraphData(tree), [tree]); + + return ( + + + + ); +} diff --git a/plugins/app-visualizer/src/components/AppVisualizerPage/index.ts b/plugins/app-visualizer/src/components/AppVisualizerPage/index.ts new file mode 100644 index 0000000000..971bc6ccb2 --- /dev/null +++ b/plugins/app-visualizer/src/components/AppVisualizerPage/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { AppVisualizerPage } from './AppVisualizerPage'; diff --git a/plugins/app-visualizer/src/index.ts b/plugins/app-visualizer/src/index.ts new file mode 100644 index 0000000000..b91a06c569 --- /dev/null +++ b/plugins/app-visualizer/src/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { visualizerPlugin as default } from './plugin'; diff --git a/plugins/app-visualizer/src/plugin.tsx b/plugins/app-visualizer/src/plugin.tsx new file mode 100644 index 0000000000..9a726ecc99 --- /dev/null +++ b/plugins/app-visualizer/src/plugin.tsx @@ -0,0 +1,45 @@ +/* + * 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 { + createNavItemExtension, + createPageExtension, + createPlugin, + createRouteRef, +} from '@backstage/frontend-plugin-api'; +import VisualizerIcon from '@material-ui/icons/Visibility'; +import React from 'react'; + +const rootRouteRef = createRouteRef(); + +const appVisualizerPage = createPageExtension({ + defaultPath: '/visualizer', + routeRef: rootRouteRef, + loader: () => + import('./components/AppVisualizerPage').then(m => ), +}); + +export const appVisualizerNavItem = createNavItemExtension({ + title: 'Visualizer', + icon: VisualizerIcon, + routeRef: rootRouteRef, +}); + +/** @public */ +export const visualizerPlugin = createPlugin({ + id: 'app-visualizer', + extensions: [appVisualizerPage, appVisualizerNavItem], +}); diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index 7e02d921ea..580d58ae43 100644 --- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md @@ -1,5 +1,89 @@ # @backstage/plugin-auth-backend-module-atlassian-provider +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.1.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.0 + +### Minor Changes + +- 2a5891e: New module for `@backstage/plugin-auth-backend` that adds an atlassian auth provider + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## 0.1.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.0-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.1.0-next.0 ### Minor Changes diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index a75aae73ea..311122fe54 100644 --- a/plugins/auth-backend-module-atlassian-provider/package.json +++ b/plugins/auth-backend-module-atlassian-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-atlassian-provider", "description": "The atlassian-provider backend module for the auth plugin.", - "version": "0.1.0-next.0", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,11 +23,10 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "express": "^4.18.2", - "passport": "^0.6.0", + "passport": "^0.7.0", "passport-atlassian-oauth2": "^2.1.0" }, "devDependencies": { diff --git a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md index 60aa68020a..87ccf9ad8e 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md @@ -1,5 +1,97 @@ # @backstage/plugin-auth-backend-module-gcp-iap-provider +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.2.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.2.2-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-gcp-iap-provider/package.json b/plugins/auth-backend-module-gcp-iap-provider/package.json index 2dbb69c8cd..95fea863f3 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/package.json +++ b/plugins/auth-backend-module-gcp-iap-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-gcp-iap-provider", "description": "A GCP IAP auth provider module for the Backstage auth backend", - "version": "0.2.2-next.0", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -41,9 +41,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "express": "^4.18.2", - "msw": "^1.0.0", - "supertest": "^6.1.3" + "express": "^4.18.2" }, "files": [ "dist", diff --git a/plugins/auth-backend-module-gcp-iap-provider/src/module.ts b/plugins/auth-backend-module-gcp-iap-provider/src/module.ts index 90771eecb9..23c503c317 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/src/module.ts +++ b/plugins/auth-backend-module-gcp-iap-provider/src/module.ts @@ -26,7 +26,7 @@ import { gcpIapSignInResolvers } from './resolvers'; /** @public */ export const authModuleGcpIapProvider = createBackendModule({ pluginId: 'auth', - moduleId: 'gcpIapProvider', + moduleId: 'gcp-iap-provider', register(reg) { reg.registerInit({ deps: { diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index 3bf47f8a7a..11053b8487 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,83 @@ # @backstage/plugin-auth-backend-module-github-provider +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## 0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index 218ec94cac..8113a9e03f 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", "description": "The github-provider backend module for the auth plugin.", - "version": "0.1.5-next.0", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,7 +23,6 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "passport-github2": "^0.1.12" diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md index c610e93ec8..15688da6ee 100644 --- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md @@ -1,5 +1,85 @@ # @backstage/plugin-auth-backend-module-gitlab-provider +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.5 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## 0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.5-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index 9d931e628c..e25ccfd547 100644 --- a/plugins/auth-backend-module-gitlab-provider/package.json +++ b/plugins/auth-backend-module-gitlab-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-gitlab-provider", "description": "The gitlab-provider backend module for the auth plugin.", - "version": "0.1.5-next.0", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,11 +23,10 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "express": "^4.18.2", - "passport": "^0.6.0", + "passport": "^0.7.0", "passport-gitlab2": "^5.0.0" }, "devDependencies": { diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index a8edf35c7e..78a6e68eb4 100644 --- a/plugins/auth-backend-module-google-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md @@ -1,5 +1,81 @@ # @backstage/plugin-auth-backend-module-google-provider +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.5 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## 0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.5-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index 1cbb87c5b2..c985192df6 100644 --- a/plugins/auth-backend-module-google-provider/package.json +++ b/plugins/auth-backend-module-google-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-google-provider", "description": "A Google auth provider module for the Backstage auth backend", - "version": "0.1.5-next.0", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -42,7 +42,6 @@ "@backstage/cli": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", "@types/passport-google-oauth20": "^2.0.3", - "msw": "^1.0.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/auth-backend-module-google-provider/src/module.ts b/plugins/auth-backend-module-google-provider/src/module.ts index 0354f99880..d0f8020b92 100644 --- a/plugins/auth-backend-module-google-provider/src/module.ts +++ b/plugins/auth-backend-module-google-provider/src/module.ts @@ -26,7 +26,7 @@ import { googleSignInResolvers } from './resolvers'; /** @public */ export const authModuleGoogleProvider = createBackendModule({ pluginId: 'auth', - moduleId: 'googleProvider', + moduleId: 'google-provider', register(reg) { reg.registerInit({ deps: { diff --git a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md index 76bf7d60d7..e61ab20c6b 100644 --- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md @@ -1,5 +1,87 @@ # @backstage/plugin-auth-backend-module-microsoft-provider +## 0.1.5-next.0 + +### Patch Changes + +- 1ff2684: Added the possibility to use custom scopes for performing login with Microsoft EntraID. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.1.4 + +### Patch Changes + +- 928efbc: Deprecated the `authModuleMicrosoftProvider` export. A default export is now available and should be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-microsoft-provider'));` +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## 0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.1.4-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.3 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## 0.1.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.3-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.1.3-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-microsoft-provider/api-report.md b/plugins/auth-backend-module-microsoft-provider/api-report.md index 21bf77d545..33600c10d8 100644 --- a/plugins/auth-backend-module-microsoft-provider/api-report.md +++ b/plugins/auth-backend-module-microsoft-provider/api-report.md @@ -10,9 +10,13 @@ import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; import { PassportProfile } from '@backstage/plugin-auth-node'; import { SignInResolverFactory } from '@backstage/plugin-auth-node'; -// @public (undocumented) +// @public @deprecated (undocumented) export const authModuleMicrosoftProvider: () => BackendFeature; +// @public (undocumented) +const authModuleMicrosoftProvider_2: () => BackendFeature; +export default authModuleMicrosoftProvider_2; + // @public (undocumented) export const microsoftAuthenticator: OAuthAuthenticator< { diff --git a/plugins/auth-backend-module-microsoft-provider/config.d.ts b/plugins/auth-backend-module-microsoft-provider/config.d.ts index aa78f72271..df63e9ccf4 100644 --- a/plugins/auth-backend-module-microsoft-provider/config.d.ts +++ b/plugins/auth-backend-module-microsoft-provider/config.d.ts @@ -28,6 +28,7 @@ export interface Config { clientSecret: string; domainHint?: string; callbackUrl?: string; + additionalScopes?: string[]; }; }; }; diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index e67da9228c..e20f2d8a4c 100644 --- a/plugins/auth-backend-module-microsoft-provider/package.json +++ b/plugins/auth-backend-module-microsoft-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-microsoft-provider", "description": "The microsoft-provider backend module for the auth plugin.", - "version": "0.1.3-next.0", + "version": "0.1.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,13 +23,13 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "express": "^4.18.2", "jose": "^4.6.0", + "lodash": "^4.17.21", "node-fetch": "^2.6.7", - "passport": "^0.6.0", + "passport": "^0.7.0", "passport-microsoft": "^1.0.0" }, "devDependencies": { diff --git a/plugins/auth-backend-module-microsoft-provider/src/authenticator.test.ts b/plugins/auth-backend-module-microsoft-provider/src/authenticator.test.ts index 35d35c464c..21a60a229b 100644 --- a/plugins/auth-backend-module-microsoft-provider/src/authenticator.test.ts +++ b/plugins/auth-backend-module-microsoft-provider/src/authenticator.test.ts @@ -113,6 +113,7 @@ describe('microsoftAuthenticator', () => { tenantId: 'tenantId', clientId: 'clientId', clientSecret: 'clientSecret', + additionalScopes: ['User.Read.All'], }), }); }); diff --git a/plugins/auth-backend-module-microsoft-provider/src/authenticator.ts b/plugins/auth-backend-module-microsoft-provider/src/authenticator.ts index 52ef1ddd90..1b6eb87fae 100644 --- a/plugins/auth-backend-module-microsoft-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-microsoft-provider/src/authenticator.ts @@ -21,6 +21,7 @@ import { PassportProfile, } from '@backstage/plugin-auth-node'; import { ExtendedMicrosoftStrategy } from './strategy'; +import { union } from 'lodash'; /** @public */ export const microsoftAuthenticator = createOAuthAuthenticator({ @@ -31,6 +32,10 @@ export const microsoftAuthenticator = createOAuthAuthenticator({ const clientSecret = config.getString('clientSecret'); const tenantId = config.getString('tenantId'); const domainHint = config.getOptionalString('domainHint'); + const scope = union( + ['user.read'], + config.getOptionalStringArray('additionalScopes'), + ); const helper = PassportOAuthAuthenticatorHelper.from( new ExtendedMicrosoftStrategy( @@ -39,7 +44,7 @@ export const microsoftAuthenticator = createOAuthAuthenticator({ clientSecret: clientSecret, callbackURL: callbackUrl, tenant: tenantId, - scope: ['user.read'], + scope: scope, }, ( accessToken: string, diff --git a/plugins/auth-backend-module-microsoft-provider/src/deprecated.ts b/plugins/auth-backend-module-microsoft-provider/src/deprecated.ts new file mode 100644 index 0000000000..3c671b6c25 --- /dev/null +++ b/plugins/auth-backend-module-microsoft-provider/src/deprecated.ts @@ -0,0 +1,24 @@ +/* + * 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 { authModuleMicrosoftProvider as deprecatedAuthModuleMicrosoftProvider } from './module'; + +/** + * @public + * @deprecated Use default import instead + */ +export const authModuleMicrosoftProvider = + deprecatedAuthModuleMicrosoftProvider; diff --git a/plugins/auth-backend-module-microsoft-provider/src/index.ts b/plugins/auth-backend-module-microsoft-provider/src/index.ts index ae4fbf5926..45b7b719cd 100644 --- a/plugins/auth-backend-module-microsoft-provider/src/index.ts +++ b/plugins/auth-backend-module-microsoft-provider/src/index.ts @@ -21,5 +21,6 @@ */ export { microsoftAuthenticator } from './authenticator'; -export { authModuleMicrosoftProvider } from './module'; +export { authModuleMicrosoftProvider as default } from './module'; export { microsoftSignInResolvers } from './resolvers'; +export * from './deprecated'; diff --git a/plugins/auth-backend-module-microsoft-provider/src/module.test.ts b/plugins/auth-backend-module-microsoft-provider/src/module.test.ts index 21f9a7e845..2ef92c0b44 100644 --- a/plugins/auth-backend-module-microsoft-provider/src/module.test.ts +++ b/plugins/auth-backend-module-microsoft-provider/src/module.test.ts @@ -38,6 +38,7 @@ describe('authModuleMicrosoftProvider', () => { clientId: 'my-client-id', clientSecret: 'my-client-secret', tenantId: 'my-tenant-id', + additionalScopes: ['User.Read.All'], }, }, }, @@ -66,7 +67,7 @@ describe('authModuleMicrosoftProvider', () => { expect(startUrl.pathname).toBe('/my-tenant-id/oauth2/v2.0/authorize'); expect(Object.fromEntries(startUrl.searchParams)).toEqual({ response_type: 'code', - scope: 'user.read', + scope: 'user.read User.Read.All', client_id: 'my-client-id', redirect_uri: `http://localhost:${server.port()}/api/auth/microsoft/handler/frame`, state: expect.any(String), diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index bae3565d3b..0b70811413 100644 --- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md @@ -1,5 +1,85 @@ # @backstage/plugin-auth-backend-module-oauth2-provider +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.5 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## 0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.5-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index cc672f77b9..83c14c7821 100644 --- a/plugins/auth-backend-module-oauth2-provider/package.json +++ b/plugins/auth-backend-module-oauth2-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-provider", "description": "The oauth2-provider backend module for the auth plugin.", - "version": "0.1.5-next.0", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,11 +23,9 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "express": "^4.18.2", - "passport": "^0.6.0", + "passport": "^0.7.0", "passport-oauth2": "^1.6.1" }, "devDependencies": { diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/.eslintrc.js b/plugins/auth-backend-module-oauth2-proxy-provider/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/auth-backend-module-oauth2-proxy-provider/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md new file mode 100644 index 0000000000..809e58fd6c --- /dev/null +++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md @@ -0,0 +1,101 @@ +# @backstage/plugin-auth-backend-module-oauth2-proxy-provider + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/errors@1.2.3 + +## 0.1.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/errors@1.2.3 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + +## 0.1.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.0 + +### Minor Changes + +- 271aa12: Release of `oauth2-proxy-provider` plugin + +### Patch Changes + +- a6be465: Exported the provider as default so it gets discovered when using `featureDiscoveryServiceFactory()` +- 510dab4: Change provider id from `oauth2ProxyProvider` to `oauth2Proxy` +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/errors@1.2.3 + +## 0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.0-next.1 + +### Patch Changes + +- a6be465: Exported the provider as default so it gets discovered when using `featureDiscoveryServiceFactory()` +- 510dab4: Change provider id from `oauth2ProxyProvider` to `oauth2Proxy` +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/errors@1.2.3 + +## 0.1.0-next.0 + +### Minor Changes + +- 271aa12c7c: Release of `oauth2-proxy-provider` plugin + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/README.md b/plugins/auth-backend-module-oauth2-proxy-provider/README.md new file mode 100644 index 0000000000..edec8fb3c2 --- /dev/null +++ b/plugins/auth-backend-module-oauth2-proxy-provider/README.md @@ -0,0 +1,5 @@ +# @backstage/plugin-auth-backend-module-oauth2-proxy-provider + +The oauth2-proxy-provider backend module for the auth plugin. + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/api-report.md b/plugins/auth-backend-module-oauth2-proxy-provider/api-report.md new file mode 100644 index 0000000000..1a4c1ee6d6 --- /dev/null +++ b/plugins/auth-backend-module-oauth2-proxy-provider/api-report.md @@ -0,0 +1,32 @@ +## API Report File for "@backstage/plugin-auth-backend-module-oauth2-proxy-provider" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { IncomingHttpHeaders } from 'http'; +import { ProxyAuthenticator } from '@backstage/plugin-auth-node'; + +// @public (undocumented) +const authModuleOauth2ProxyProvider: () => BackendFeature; +export default authModuleOauth2ProxyProvider; + +// @public +export const OAUTH2_PROXY_JWT_HEADER = 'X-OAUTH2-PROXY-ID-TOKEN'; + +// @public (undocumented) +export const oauth2ProxyAuthenticator: ProxyAuthenticator< + unknown, + OAuth2ProxyResult +>; + +// @public +export type OAuth2ProxyResult = { + fullProfile: JWTPayload; + accessToken: string; + headers: IncomingHttpHeaders; + getHeader(name: string): string | undefined; +}; +``` diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml b/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml new file mode 100644 index 0000000000..c26202c97d --- /dev/null +++ b/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend-module-oauth2-proxy-provider + title: '@backstage/plugin-auth-backend-module-oauth2-proxy-provider' + description: The oauth2-proxy-provider backend module for the auth plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/package.json b/plugins/auth-backend-module-oauth2-proxy-provider/package.json new file mode 100644 index 0000000000..d0dabe9321 --- /dev/null +++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json @@ -0,0 +1,38 @@ +{ + "name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider", + "description": "The oauth2-proxy-provider backend module for the auth plugin.", + "version": "0.1.2-next.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": "backend-plugin-module" + }, + "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" + }, + "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/plugin-auth-node": "workspace:^", + "jose": "^4.6.0" + }, + "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/src/authenticator.ts b/plugins/auth-backend-module-oauth2-proxy-provider/src/authenticator.ts new file mode 100644 index 0000000000..a3da83791a --- /dev/null +++ b/plugins/auth-backend-module-oauth2-proxy-provider/src/authenticator.ts @@ -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. + */ + +import { AuthenticationError } from '@backstage/errors'; +import { + createProxyAuthenticator, + getBearerTokenFromAuthorizationHeader, +} from '@backstage/plugin-auth-node'; +import { decodeJwt } from 'jose'; +import { OAuth2ProxyResult } from './types'; + +/** + * NOTE: This may come in handy if you're doing work on this provider: + * plugins/auth-backend/examples/docker-compose.oauth2-proxy.yaml + * + * @public + */ +export const OAUTH2_PROXY_JWT_HEADER = 'X-OAUTH2-PROXY-ID-TOKEN'; + +/** @public */ +export const oauth2ProxyAuthenticator = createProxyAuthenticator< + unknown, + OAuth2ProxyResult +>({ + defaultProfileTransform: async (result: OAuth2ProxyResult) => { + return { + profile: { + email: result.getHeader('x-forwarded-email'), + displayName: + result.getHeader('x-forwarded-preferred-username') || + result.getHeader('x-forwarded-user'), + }, + }; + }, + async initialize() {}, + async authenticate({ req }) { + try { + const authHeader = req.header(OAUTH2_PROXY_JWT_HEADER); + const jwt = getBearerTokenFromAuthorizationHeader(authHeader); + const decodedJWT = jwt && decodeJwt(jwt); + + const result = { + fullProfile: decodedJWT || {}, + accessToken: jwt || '', + headers: req.headers, + getHeader(name: string) { + if (name.toLocaleLowerCase('en-US') === 'set-cookie') { + throw new Error('Access Set-Cookie via the headers object instead'); + } + return req.get(name); + }, + }; + return { result }; + } catch (e) { + throw new AuthenticationError('Authentication failed', e); + } + }, +}); diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/src/index.ts b/plugins/auth-backend-module-oauth2-proxy-provider/src/index.ts new file mode 100644 index 0000000000..b804f666c7 --- /dev/null +++ b/plugins/auth-backend-module-oauth2-proxy-provider/src/index.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +/** + * The oauth2-proxy-provider backend module for the auth plugin. + * + * @packageDocumentation + */ +export { authModuleOauth2ProxyProvider as default } from './module'; +export { + oauth2ProxyAuthenticator, + OAUTH2_PROXY_JWT_HEADER, +} from './authenticator'; +export type { OAuth2ProxyResult } from './types'; diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/src/module.ts b/plugins/auth-backend-module-oauth2-proxy-provider/src/module.ts new file mode 100644 index 0000000000..70ce506d73 --- /dev/null +++ b/plugins/auth-backend-module-oauth2-proxy-provider/src/module.ts @@ -0,0 +1,49 @@ +/* + * 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 { createBackendModule } from '@backstage/backend-plugin-api'; +import { + authProvidersExtensionPoint, + createProxyAuthProviderFactory, + commonSignInResolvers, +} from '@backstage/plugin-auth-node'; +import { oauth2ProxyAuthenticator } from './authenticator'; +import { oauth2ProxySignInResolvers } from './resolvers'; + +/** @public */ +export const authModuleOauth2ProxyProvider = createBackendModule({ + pluginId: 'auth', + moduleId: 'oauth2-proxy-provider', + register(reg) { + reg.registerInit({ + deps: { + providers: authProvidersExtensionPoint, + }, + async init({ providers }) { + providers.registerProvider({ + providerId: 'oauth2Proxy', + factory: createProxyAuthProviderFactory({ + authenticator: oauth2ProxyAuthenticator, + signInResolverFactories: { + ...commonSignInResolvers, + ...oauth2ProxySignInResolvers, + }, + }), + }); + }, + }); + }, +}); diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/src/resolvers.ts b/plugins/auth-backend-module-oauth2-proxy-provider/src/resolvers.ts new file mode 100644 index 0000000000..99ca8bc156 --- /dev/null +++ b/plugins/auth-backend-module-oauth2-proxy-provider/src/resolvers.ts @@ -0,0 +1,41 @@ +/* + * 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 { + createSignInResolverFactory, + SignInInfo, +} from '@backstage/plugin-auth-node'; +import { OAuth2ProxyResult } from './types'; + +/** + * @public + */ +export namespace oauth2ProxySignInResolvers { + export const forwardedUserMatchingUserEntityName = + createSignInResolverFactory({ + create() { + return async (info: SignInInfo, ctx) => { + const name = info.result.getHeader('x-forwarded-user'); + if (!name) { + throw new Error('Request did not contain a user'); + } + return ctx.signInWithCatalogUser({ + entityRef: { name }, + }); + }; + }, + }); +} diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/src/types.ts b/plugins/auth-backend-module-oauth2-proxy-provider/src/types.ts new file mode 100644 index 0000000000..5b9e97ab05 --- /dev/null +++ b/plugins/auth-backend-module-oauth2-proxy-provider/src/types.ts @@ -0,0 +1,60 @@ +/* + * 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 { IncomingHttpHeaders } from 'http'; + +/** + * JWT header extraction result, containing the raw value and the parsed JWT + * payload. + * + * @public + */ +export type OAuth2ProxyResult = { + /** + * The parsed payload of the `accessToken`. The token is only parsed, not verified. + * + * @deprecated Access through the `headers` instead. This will be removed in a future release. + */ + fullProfile: JWTPayload; + + /** + * The token received via the X-OAUTH2-PROXY-ID-TOKEN header. Will be an empty string + * if the header is not set. Note the this is typically an OpenID Connect token. + * + * @deprecated Access through the `headers` instead. This will be removed in a future release. + */ + accessToken: string; + + /** + * The headers of the incoming request from the OAuth2 proxy. This will include + * both the headers set by the client as well as the ones added by the OAuth2 proxy. + * You should only trust the headers that are injected by the OAuth2 proxy. + * + * Useful headers to use to complete the sign-in are for example `x-forwarded-user` + * and `x-forwarded-email`. See the OAuth2 proxy documentation for more information + * about the available headers and how to enable them. In particular it is possible + * to forward access and identity tokens, which can be user for additional verification + * and lookups. + */ + headers: IncomingHttpHeaders; + + /** + * Provides convenient access to the request headers. + * + * This call is simply forwarded to `req.get(name)`. + */ + getHeader(name: string): string | undefined; +}; diff --git a/plugins/auth-backend-module-oidc-provider/.eslintrc.js b/plugins/auth-backend-module-oidc-provider/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md new file mode 100644 index 0000000000..4581738ffb --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -0,0 +1,16 @@ +# @backstage/plugin-auth-backend-module-oidc-provider + +## 0.1.0-next.0 + +### Minor Changes + +- 5d2fcba: Created new `@backstage/plugin-auth-backend-module-oidc-provider` module package to house oidc auth provider migration. + +### Patch Changes + +- 8afb6f4: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/plugin-auth-backend@0.20.4-next.0 + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 diff --git a/plugins/auth-backend-module-oidc-provider/README.md b/plugins/auth-backend-module-oidc-provider/README.md new file mode 100644 index 0000000000..e586f32fd7 --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/README.md @@ -0,0 +1,8 @@ +# Auth Module: Oidc Provider + +This module provides an Oidc auth provider implementation for `@backstage/plugin-auth-backend`. + +## Links + +- [Repository](https://oidc.com/backstage/backstage/tree/master/plugins/auth-backend-module-oidc-provider) +- [Backstage Project Homepage](https://backstage.io) diff --git a/plugins/auth-backend-module-oidc-provider/api-report.md b/plugins/auth-backend-module-oidc-provider/api-report.md new file mode 100644 index 0000000000..d96d9952a0 --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/api-report.md @@ -0,0 +1,50 @@ +## API Report File for "@backstage/plugin-auth-backend-module-oidc-provider" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { BaseClient } from 'openid-client'; +import { OAuthAuthenticator } from '@backstage/plugin-auth-node'; +import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; +import { SignInResolverFactory } from '@backstage/plugin-auth-node'; +import { Strategy } from 'openid-client'; +import { TokenSet } from 'openid-client'; +import { UserinfoResponse } from 'openid-client'; + +// @public (undocumented) +const authModuleOidcProvider: () => BackendFeature; +export default authModuleOidcProvider; + +// @public (undocumented) +export const oidcAuthenticator: OAuthAuthenticator< + { + initializedScope: string | undefined; + initializedPrompt: string | undefined; + promise: Promise<{ + helper: PassportOAuthAuthenticatorHelper; + client: BaseClient; + strategy: Strategy; + }>; + }, + OidcAuthResult +>; + +// @public +export type OidcAuthResult = { + tokenset: TokenSet; + userinfo: UserinfoResponse; +}; + +// @public +export namespace oidcSignInResolvers { + const emailLocalPartMatchingUserEntityName: SignInResolverFactory< + unknown, + unknown + >; + const emailMatchingUserEntityProfileEmail: SignInResolverFactory< + unknown, + unknown + >; +} +``` diff --git a/plugins/auth-backend-module-oidc-provider/catalog-info.yaml b/plugins/auth-backend-module-oidc-provider/catalog-info.yaml new file mode 100644 index 0000000000..896738898b --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend-module-oidc-provider + title: '@backstage/plugin-auth-backend-module-oidc-provider' + description: The oidc-provider backend module for the auth plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/scaffolder/src/legacy/TaskPage/IconLink.test.tsx b/plugins/auth-backend-module-oidc-provider/config.d.ts similarity index 55% rename from plugins/scaffolder/src/legacy/TaskPage/IconLink.test.tsx rename to plugins/auth-backend-module-oidc-provider/config.d.ts index 0b6092c37d..16131c0761 100644 --- a/plugins/scaffolder/src/legacy/TaskPage/IconLink.test.tsx +++ b/plugins/auth-backend-module-oidc-provider/config.d.ts @@ -14,17 +14,25 @@ * limitations under the License. */ -import { renderInTestApp } from '@backstage/test-utils'; -import CloudIcon from '@material-ui/icons/Cloud'; -import React from 'react'; -import { IconLink } from './IconLink'; - -describe('IconLink', () => { - it('should render an icon link', async () => { - const rendered = await renderInTestApp( - , - ); - - expect(rendered.getByText('I am Link')).toBeInTheDocument(); - }); -}); +export interface Config { + auth?: { + providers?: { + /** @visibility frontend */ + oidc?: { + [authEnv: string]: { + clientId: string; + /** + * @visibility secret + */ + clientSecret: string; + metadataUrl: string; + callbackUrl?: string; + tokenEndpointAuthMethod?: string; + tokenSignedResponseAlg?: string; + scope?: string; + prompt?: string; + }; + }; + }; + }; +} diff --git a/plugins/auth-backend-module-oidc-provider/dev/index.ts b/plugins/auth-backend-module-oidc-provider/dev/index.ts new file mode 100644 index 0000000000..d3c18c1d48 --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/dev/index.ts @@ -0,0 +1,24 @@ +/* + * 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 { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('../src')); + +backend.start(); diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json new file mode 100644 index 0000000000..2b133f2f21 --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -0,0 +1,51 @@ +{ + "name": "@backstage/plugin-auth-backend-module-oidc-provider", + "description": "The oidc-provider backend module for the auth plugin.", + "version": "0.1.0-next.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": "backend-plugin-module" + }, + "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" + }, + "dependencies": { + "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/plugin-auth-backend": "workspace:^", + "@backstage/plugin-auth-node": "workspace:^", + "express": "^4.18.2", + "openid-client": "^5.5.0", + "passport": "^0.7.0" + }, + "devDependencies": { + "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@backstage/config": "workspace:^", + "cookie-parser": "^1.4.6", + "express-promise-router": "^4.1.1", + "express-session": "^1.17.3", + "jose": "^4.14.6", + "msw": "^1.3.1", + "supertest": "^6.3.3" + }, + "configSchema": "config.d.ts", + "files": [ + "dist", + "config.d.ts" + ] +} diff --git a/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts b/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts new file mode 100644 index 0000000000..717083562c --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/src/authenticator.test.ts @@ -0,0 +1,437 @@ +/* + * 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 { + OAuthAuthenticatorAuthenticateInput, + OAuthAuthenticatorRefreshInput, + OAuthAuthenticatorStartInput, + OAuthState, + decodeOAuthState, + encodeOAuthState, +} from '@backstage/plugin-auth-node'; +import { oidcAuthenticator } from './authenticator'; +import { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { JWK, SignJWT, exportJWK, generateKeyPair } from 'jose'; +import { rest } from 'msw'; +import express from 'express'; + +describe('oidcAuthenticator', () => { + let implementation: any; + let oauthState: OAuthState; + let idToken: string; + let publicKey: JWK; + + const mswServer = setupServer(); + setupRequestMockHandlers(mswServer); + + const issuerMetadata = { + issuer: 'https://oidc.test', + authorization_endpoint: 'https://oidc.test/oauth2/authorize', + token_endpoint: 'https://oidc.test/oauth2/token', + revocation_endpoint: 'https://oidc.test/oauth2/revoke_token', + userinfo_endpoint: 'https://oidc.test/idp/userinfo.openid', + introspection_endpoint: 'https://oidc.test/introspect.oauth2', + jwks_uri: 'https://oidc.test/jwks.json', + scopes_supported: [ + 'openid', + 'offline_access', + 'oidc:request-audience', + 'username', + 'groups', + ], + claims_supported: ['email', 'username', 'groups', 'additionalClaims'], + response_types_supported: ['code'], + id_token_signing_alg_values_supported: ['RS256', 'RS512', 'HS256'], + token_endpoint_auth_signing_alg_values_supported: [ + 'RS256', + 'RS512', + 'HS256', + ], + request_object_signing_alg_values_supported: ['RS256', 'RS512', 'HS256'], + }; + + beforeAll(async () => { + const keyPair = await generateKeyPair('RS256'); + const privateKey = await exportJWK(keyPair.privateKey); + publicKey = await exportJWK(keyPair.publicKey); + publicKey.alg = privateKey.alg = 'RS256'; + + idToken = await new SignJWT({ + sub: 'test', + iss: 'https://oidc.test', + iat: Date.now(), + aud: 'clientId', + exp: Date.now() + 10000, + }) + .setProtectedHeader({ alg: privateKey.alg, kid: privateKey.kid }) + .sign(keyPair.privateKey); + }); + + beforeEach(() => { + mswServer.use( + rest.get( + 'https://oidc.test/.well-known/openid-configuration', + (_req, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(issuerMetadata), + ), + ), + rest.get('https://oidc.test/jwks.json', async (_req, res, ctx) => + res(ctx.status(200), ctx.json({ keys: [{ ...publicKey }] })), + ), + rest.post('https://oidc.test/oauth2/token', async (req, res, ctx) => { + return res( + req.headers.get('Authorization') + ? ctx.json({ + access_token: 'accessToken', + id_token: idToken, + refresh_token: 'refreshToken', + scope: 'testScope', + expires_in: 3600, + }) + : ctx.status(401), + ); + }), + rest.get( + 'https://oidc.test/idp/userinfo.openid', + async (_req, res, ctx) => + res( + ctx.status(200), + ctx.json({ + sub: 'test', + name: 'Alice Adams', + given_name: 'Alice', + family_name: 'Adams', + email: 'alice@test.com', + picture: 'http://testPictureUrl/photo.jpg', + }), + ), + ), + ); + + implementation = oidcAuthenticator.initialize({ + callbackUrl: 'https://backstage.test/callback', + config: new ConfigReader({ + metadataUrl: 'https://oidc.test/.well-known/openid-configuration', + clientId: 'clientId', + clientSecret: 'clientSecret', + }), + }); + + oauthState = { + nonce: 'nonce', + env: 'env', + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('#start', () => { + let fakeSession: Record; + let startRequest: OAuthAuthenticatorStartInput; + + beforeEach(() => { + fakeSession = {}; + startRequest = { + state: encodeOAuthState(oauthState), + req: { + method: 'GET', + url: 'test', + session: fakeSession, + }, + } as unknown as OAuthAuthenticatorStartInput; + }); + + it('redirects to authorization endpoint returned from OIDC metadata endpoint', async () => { + const startResponse = await oidcAuthenticator.start( + startRequest, + implementation, + ); + const url = new URL(startResponse.url); + + expect(url.protocol).toBe('https:'); + expect(url.hostname).toBe('oidc.test'); + expect(url.pathname).toBe('/oauth2/authorize'); + }); + + it('initiates authorization code grant', async () => { + const startResponse = await oidcAuthenticator.start( + startRequest, + implementation, + ); + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('response_type')).toBe('code'); + }); + + it('passes client ID from config', async () => { + const startResponse = await oidcAuthenticator.start( + startRequest, + implementation, + ); + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('client_id')).toBe('clientId'); + }); + + it('passes callback URL from config', async () => { + const startResponse = await oidcAuthenticator.start( + startRequest, + implementation, + ); + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('redirect_uri')).toBe( + 'https://backstage.test/callback', + ); + }); + + it('generates PKCE challenge', async () => { + const startResponse = await oidcAuthenticator.start( + startRequest, + implementation, + ); + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('code_challenge_method')).toBe('S256'); + expect(searchParams.get('code_challenge')).not.toBeNull(); + }); + + it('stores PKCE verifier in session', async () => { + await oidcAuthenticator.start(startRequest, implementation); + expect(fakeSession['oidc:oidc.test'].code_verifier).toBeDefined(); + }); + + it('requests default scopes if none are provided in config', async () => { + const startResponse = await oidcAuthenticator.start( + startRequest, + implementation, + ); + const { searchParams } = new URL(startResponse.url); + const scopes = searchParams.get('scope')?.split(' ') ?? []; + + expect(scopes).toEqual( + expect.arrayContaining(['openid', 'profile', 'email']), + ); + }); + + it('encodes OAuth state in query param', async () => { + const startResponse = await oidcAuthenticator.start( + startRequest, + implementation, + ); + const { searchParams } = new URL(startResponse.url); + const stateParam = searchParams.get('state'); + const decodedState = decodeOAuthState(stateParam!); + + expect(decodedState).toMatchObject(oauthState); + }); + + it('fails when request has no session', async () => { + return expect( + oidcAuthenticator.start( + { + state: encodeOAuthState(oauthState), + req: { + method: 'GET', + url: 'test', + }, + } as unknown as OAuthAuthenticatorStartInput, + implementation, + ), + ).rejects.toThrow('authentication requires session support'); + }); + }); + + describe('#authenticate', () => { + let handlerRequest: OAuthAuthenticatorAuthenticateInput; + + beforeEach(() => { + handlerRequest = { + req: { + method: 'GET', + url: `https://test?code=authorization_code&state=${encodeOAuthState( + oauthState, + )}`, + session: { + 'oidc:oidc.test': { + state: encodeOAuthState(oauthState), + }, + }, + } as unknown as express.Request, + }; + }); + + it('exchanges authorization code for access token', async () => { + const authenticatorResult = await oidcAuthenticator.authenticate( + handlerRequest, + implementation, + ); + const accessToken = authenticatorResult.session.accessToken; + + expect(accessToken).toEqual('accessToken'); + }); + + it('exchanges authorization code for refresh token', async () => { + const authenticatorResult = await oidcAuthenticator.authenticate( + handlerRequest, + implementation, + ); + const refreshToken = authenticatorResult.session.refreshToken; + + expect(refreshToken).toEqual('refreshToken'); + }); + + it('returns granted scope', async () => { + const authenticatorResult = await oidcAuthenticator.authenticate( + handlerRequest, + implementation, + ); + const responseScope = authenticatorResult.session.scope; + + expect(responseScope).toEqual('testScope'); + }); + + it('returns a default session.tokentype field', async () => { + const authenticatorResult = await oidcAuthenticator.authenticate( + handlerRequest, + implementation, + ); + const tokenType = authenticatorResult.session.tokenType; + + expect(tokenType).toEqual('bearer'); + }); + + it('returns picture and email', async () => { + const authenticatorResult = await oidcAuthenticator.authenticate( + handlerRequest, + implementation, + ); + + expect(authenticatorResult).toMatchObject({ + fullProfile: { + userinfo: { + email: 'alice@test.com', + picture: 'http://testPictureUrl/photo.jpg', + name: 'Alice Adams', + }, + }, + }); + }); + + it('returns idToken', async () => { + const authenticatorResult = await oidcAuthenticator.authenticate( + handlerRequest, + implementation, + ); + + expect(authenticatorResult).toMatchObject({ + session: { + idToken, + }, + }); + expect( + Math.abs(authenticatorResult.session.expiresInSeconds! - 3600), + ).toBeLessThan(5); + }); + + it('fails without authorization code', async () => { + handlerRequest.req.url = 'https://test.com'; + return expect( + oidcAuthenticator.authenticate(handlerRequest, implementation), + ).rejects.toThrow('Unexpected redirect'); + }); + + it('fails without oauth state', async () => { + return expect( + oidcAuthenticator.authenticate( + { + req: { + method: 'GET', + url: `https://test?code=authorization_code}`, + session: { + ['oidc:pinniped.test']: { + state: { handle: 'sessionid', code_verifier: 'foo' }, + }, + }, + } as unknown as express.Request, + }, + implementation, + ), + ).rejects.toThrow( + 'Authentication failed, did not find expected authorization request details in session, req.session["oidc:oidc.test"] is undefined', + ); + }); + + it('fails when request has no session', async () => { + return expect( + oidcAuthenticator.authenticate( + { + req: { + method: 'GET', + url: 'https://test.com', + } as unknown as express.Request, + }, + implementation, + ), + ).rejects.toThrow('authentication requires session support'); + }); + }); + + describe('#refresh', () => { + let refreshRequest: OAuthAuthenticatorRefreshInput; + + beforeEach(() => { + refreshRequest = { + scope: '', + refreshToken: 'otherRefreshToken', + req: {} as express.Request, + }; + }); + + it('gets new refresh token', async () => { + const refreshResponse = await oidcAuthenticator.refresh( + refreshRequest, + implementation, + ); + + expect(refreshResponse.session.refreshToken).toBe('refreshToken'); + }); + + it('gets access token', async () => { + const refreshResponse = await oidcAuthenticator.refresh( + refreshRequest, + implementation, + ); + + expect(refreshResponse.session.accessToken).toBe('accessToken'); + }); + + it('gets id token', async () => { + const refreshResponse = await oidcAuthenticator.refresh( + refreshRequest, + implementation, + ); + + expect(refreshResponse.session.idToken).toBe(idToken); + }); + }); +}); diff --git a/plugins/auth-backend-module-oidc-provider/src/authenticator.ts b/plugins/auth-backend-module-oidc-provider/src/authenticator.ts new file mode 100644 index 0000000000..ba6bc9d142 --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/src/authenticator.ts @@ -0,0 +1,187 @@ +/* + * 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 { + Issuer, + ClientAuthMethod, + TokenSet, + UserinfoResponse, + Strategy as OidcStrategy, +} from 'openid-client'; +import { + createOAuthAuthenticator, + OAuthAuthenticatorResult, + PassportDoneCallback, + PassportHelpers, + PassportOAuthAuthenticatorHelper, + PassportOAuthPrivateInfo, +} from '@backstage/plugin-auth-node'; + +/** + * authentication result for the OIDC which includes the token set and user + * profile response + * @public + */ +export type OidcAuthResult = { + tokenset: TokenSet; + userinfo: UserinfoResponse; +}; + +/** @public */ +export const oidcAuthenticator = createOAuthAuthenticator({ + defaultProfileTransform: async ( + input: OAuthAuthenticatorResult, + ) => ({ + profile: { + email: input.fullProfile.userinfo.email, + picture: input.fullProfile.userinfo.picture, + displayName: input.fullProfile.userinfo.name, + }, + }), + initialize({ callbackUrl, config }) { + const clientId = config.getString('clientId'); + const clientSecret = config.getString('clientSecret'); + const metadataUrl = config.getString('metadataUrl'); + const customCallbackUrl = config.getOptionalString('callbackUrl'); + const tokenEndpointAuthMethod = config.getOptionalString( + 'tokenEndpointAuthMethod', + ) as ClientAuthMethod; + const tokenSignedResponseAlg = config.getOptionalString( + 'tokenSignedResponseAlg', + ); + const initializedScope = config.getOptionalString('scope'); + const initializedPrompt = config.getOptionalString('prompt'); + + const promise = Issuer.discover(metadataUrl).then(issuer => { + const client = new issuer.Client({ + access_type: 'offline', // this option must be passed to provider to receive a refresh token + client_id: clientId, + client_secret: clientSecret, + redirect_uris: [customCallbackUrl || callbackUrl], + response_types: ['code'], + token_endpoint_auth_method: + tokenEndpointAuthMethod || 'client_secret_basic', + id_token_signed_response_alg: tokenSignedResponseAlg || 'RS256', + scope: initializedScope || '', + }); + + const strategy = new OidcStrategy( + { + client, + passReqToCallback: false, + }, + ( + tokenset: TokenSet, + userinfo: UserinfoResponse, + done: PassportDoneCallback, + ) => { + if (typeof done !== 'function') { + throw new Error( + 'OIDC IdP must provide a userinfo_endpoint in the metadata response', + ); + } + + done( + undefined, + { tokenset, userinfo }, + { refreshToken: tokenset.refresh_token }, + ); + }, + ); + + const helper = PassportOAuthAuthenticatorHelper.from(strategy); + return { helper, client, strategy }; + }); + + return { initializedScope, initializedPrompt, promise }; + }, + + async start(input, ctx) { + const { initializedScope, initializedPrompt, promise } = ctx; + const { helper, strategy } = await promise; + const options: Record = { + scope: input.scope || initializedScope || 'openid profile email', + state: input.state, + }; + const prompt = initializedPrompt || 'none'; + if (prompt !== 'auto') { + options.prompt = prompt; + } + + return new Promise((resolve, reject) => { + strategy.error = reject; + + return helper + .start(input, { + ...options, + }) + .then(resolve); + }); + }, + + async authenticate( + input, + ctx, + ): Promise> { + const { strategy } = await ctx.promise; + const { result, privateInfo } = + await PassportHelpers.executeFrameHandlerStrategy< + OidcAuthResult, + PassportOAuthPrivateInfo + >(input.req, strategy); + + return { + fullProfile: result, + session: { + accessToken: result.tokenset.access_token!, + tokenType: result.tokenset.token_type ?? 'bearer', + scope: result.tokenset.scope!, + expiresInSeconds: result.tokenset.expires_in, + idToken: result.tokenset.id_token, + refreshToken: privateInfo.refreshToken, + }, + }; + }, + + async refresh(input, ctx) { + const { client } = await ctx.promise; + const tokenset = await client.refresh(input.refreshToken); + if (!tokenset.access_token) { + throw new Error('Refresh failed'); + } + if (!tokenset.scope) { + tokenset.scope = input.scope; + } + const userinfo = await client.userinfo(tokenset.access_token); + + return new Promise((resolve, reject) => { + if (!tokenset.access_token) { + reject(new Error('Refresh Failed')); + } + resolve({ + fullProfile: { userinfo, tokenset }, + session: { + accessToken: tokenset.access_token!, + tokenType: tokenset.token_type ?? 'bearer', + scope: tokenset.scope!, + expiresInSeconds: tokenset.expires_in, + idToken: tokenset.id_token, + refreshToken: tokenset.refresh_token, + }, + }); + }); + }, +}); diff --git a/plugins/auth-backend-module-oidc-provider/src/index.ts b/plugins/auth-backend-module-oidc-provider/src/index.ts new file mode 100644 index 0000000000..4e951382bb --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/src/index.ts @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/** + * The oidc-provider backend module for the auth plugin. + * + * @packageDocumentation + */ + +export { oidcAuthenticator } from './authenticator'; +export type { OidcAuthResult } from './authenticator'; +export { authModuleOidcProvider as default } from './module'; +export { oidcSignInResolvers } from './resolvers'; diff --git a/plugins/auth-backend-module-oidc-provider/src/module.test.ts b/plugins/auth-backend-module-oidc-provider/src/module.test.ts new file mode 100644 index 0000000000..216c101049 --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/src/module.test.ts @@ -0,0 +1,223 @@ +/* + * 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 request from 'supertest'; +import { decodeOAuthState } from '@backstage/plugin-auth-node'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { + mockServices, + setupRequestMockHandlers, + startTestBackend, +} from '@backstage/backend-test-utils'; +import { Server } from 'http'; +import { JWK, SignJWT, exportJWK, generateKeyPair } from 'jose'; +import { authModuleOidcProvider } from './module'; + +describe('authModuleOidcProvider', () => { + let backstageServer: Server; + let appUrl: string; + let idToken: string; + let publicKey: JWK; + + const mswServer = setupServer(); + setupRequestMockHandlers(mswServer); + + const issuerMetadata = { + issuer: 'https://oidc.test', + authorization_endpoint: 'https://oidc.test/oauth2/authorize', + token_endpoint: 'https://oidc.test/oauth2/token', + revocation_endpoint: 'https://oidc.test/oauth2/revoke_token', + userinfo_endpoint: 'https://oidc.test/idp/userinfo.openid', + introspection_endpoint: 'https://oidc.test/as/introspect.oauth2', + jwks_uri: 'https://oidc.test/jwks.json', + scopes_supported: ['openid'], + claims_supported: ['email'], + response_types_supported: ['code'], + id_token_signing_alg_values_supported: ['RS256', 'RS512', 'HS256'], + token_endpoint_auth_signing_alg_values_supported: [ + 'RS256', + 'RS512', + 'HS256', + ], + request_object_signing_alg_values_supported: ['RS256', 'RS512', 'HS256'], + }; + + beforeAll(async () => { + const keyPair = await generateKeyPair('RS256'); + const privateKey = await exportJWK(keyPair.privateKey); + publicKey = await exportJWK(keyPair.publicKey); + publicKey.alg = privateKey.alg = 'RS256'; + + idToken = await new SignJWT({ + sub: 'test', + iss: 'https://oidc.test', + iat: Date.now(), + aud: 'clientId', + exp: Date.now() + 10000, + }) + .setProtectedHeader({ alg: privateKey.alg, kid: privateKey.kid }) + .sign(keyPair.privateKey); + }); + + beforeEach(async () => { + jest.clearAllMocks(); + + mswServer.use( + rest.get( + 'https://oidc.test/.well-known/openid-configuration', + (_req, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(issuerMetadata), + ), + ), + rest.get('https://oidc.test/oauth2/authorize', async (req, res, ctx) => { + const callbackUrl = new URL(req.url.searchParams.get('redirect_uri')!); + callbackUrl.searchParams.set('code', 'authorization_code'); + callbackUrl.searchParams.set( + 'state', + req.url.searchParams.get('state')!, + ); + callbackUrl.searchParams.set('scope', 'test-scope'); + return res( + ctx.status(302), + ctx.set('Location', callbackUrl.toString()), + ); + }), + rest.get('https://oidc.test/jwks.json', async (_req, res, ctx) => + res(ctx.status(200), ctx.json({ keys: [{ ...publicKey }] })), + ), + rest.post('https://oidc.test/oauth2/token', async (req, res, ctx) => { + return res( + req.headers.get('Authorization') + ? ctx.json({ + access_token: 'accessToken', + id_token: idToken, + refresh_token: 'refreshToken', + scope: 'testScope', + token_type: '', + expires_in: 3600, + }) + : ctx.status(401), + ); + }), + rest.get( + 'https://oidc.test/idp/userinfo.openid', + async (_req, res, ctx) => + res( + ctx.status(200), + ctx.json({ + sub: 'test', + name: 'Alice Adams', + given_name: 'Alice', + family_name: 'Adams', + email: 'alice@test.com', + picture: 'http://testPictureUrl/photo.jpg', + }), + ), + ), + ); + + const backend = await startTestBackend({ + features: [ + authModuleOidcProvider, + import('@backstage/plugin-auth-backend'), + mockServices.rootConfig.factory({ + data: { + app: { baseUrl: 'http://localhost' }, + auth: { + session: { secret: 'test' }, + providers: { + oidc: { + development: { + metadataUrl: + 'https://oidc.test/.well-known/openid-configuration', + clientId: 'clientId', + clientSecret: 'clientSecret', + }, + }, + }, + }, + }, + }), + ], + }); + + backstageServer = backend.server; + const port = backend.server.port(); + appUrl = `http://localhost:${port}`; + mswServer.use(rest.all(`http://*:${port}/*`, req => req.passthrough())); + }); + + afterEach(() => { + backstageServer.close(); + }); + + it('should start', async () => { + const agent = request.agent(backstageServer); + + const startResponse = await agent.get( + `/api/auth/oidc/start?env=development`, + ); + expect(startResponse.status).toEqual(302); + + const nonceCookie = agent.jar.getCookie('oidc-nonce', { + domain: 'localhost', + path: '/api/auth/oidc/handler', + script: false, + secure: false, + }); + expect(nonceCookie).toBeDefined(); + + const startUrl = new URL(startResponse.get('location')); + expect(startUrl.origin).toBe('https://oidc.test'); + expect(startUrl.pathname).toBe('/oauth2/authorize'); + expect(Object.fromEntries(startUrl.searchParams)).toEqual({ + response_type: 'code', + scope: 'openid profile email', + client_id: 'clientId', + redirect_uri: `${appUrl}/api/auth/oidc/handler/frame`, + state: expect.any(String), + prompt: 'none', + code_challenge: expect.any(String), + code_challenge_method: `S256`, + }); + + expect(decodeOAuthState(startUrl.searchParams.get('state')!)).toEqual({ + env: 'development', + nonce: decodeURIComponent(nonceCookie.value), + }); + }); + + it('#authenticate exchanges authorization code for a access_token', async () => { + const agent = request.agent(''); + const startResponse = await agent.get( + `${appUrl}/api/auth/oidc/start?env=development`, + ); + const authorizationResponse = await agent.get( + startResponse.header.location, + ); + const handlerResponse = await agent.get( + authorizationResponse.header.location, + ); + + expect(handlerResponse.text).toContain( + encodeURIComponent(`"accessToken":"accessToken"`), + ); + }); +}); diff --git a/plugins/auth-backend-module-oidc-provider/src/module.ts b/plugins/auth-backend-module-oidc-provider/src/module.ts new file mode 100644 index 0000000000..5680cd8603 --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/src/module.ts @@ -0,0 +1,48 @@ +/* + * 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 { createBackendModule } from '@backstage/backend-plugin-api'; +import { + authProvidersExtensionPoint, + commonSignInResolvers, + createOAuthProviderFactory, +} from '@backstage/plugin-auth-node'; +import { oidcAuthenticator } from './authenticator'; +import { oidcSignInResolvers } from './resolvers'; + +/** @public */ +export const authModuleOidcProvider = createBackendModule({ + pluginId: 'auth', + moduleId: 'oidc-provider', + register(reg) { + reg.registerInit({ + deps: { + providers: authProvidersExtensionPoint, + }, + async init({ providers }) { + providers.registerProvider({ + providerId: 'oidc', + factory: createOAuthProviderFactory({ + authenticator: oidcAuthenticator, + signInResolverFactories: { + ...oidcSignInResolvers, + ...commonSignInResolvers, + }, + }), + }); + }, + }); + }, +}); diff --git a/plugins/auth-backend-module-oidc-provider/src/resolvers.ts b/plugins/auth-backend-module-oidc-provider/src/resolvers.ts new file mode 100644 index 0000000000..5367ab5216 --- /dev/null +++ b/plugins/auth-backend-module-oidc-provider/src/resolvers.ts @@ -0,0 +1,38 @@ +/* + * 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 { commonSignInResolvers } from '@backstage/plugin-auth-node'; + +/** + * Available sign-in resolvers for the Oidc auth provider. + * + * @public + */ +export namespace oidcSignInResolvers { + /** + * A oidc resolver that looks up the user using the local part of + * their email address as the entity name. + */ + export const emailLocalPartMatchingUserEntityName = + commonSignInResolvers.emailLocalPartMatchingUserEntityName; + + /** + * A oidc resolver that looks up the user using their email address + * as email of the entity. + */ + export const emailMatchingUserEntityProfileEmail = + commonSignInResolvers.emailMatchingUserEntityProfileEmail; +} diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index 98782fccb5..313259dd3d 100644 --- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md @@ -1,5 +1,87 @@ # @backstage/plugin-auth-backend-module-okta-provider +## 0.0.3-next.0 + +### Patch Changes + +- cd5114c: Added missing `additionalScopes` option to configuration schema. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.0.2 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + +## 0.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.0.2-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.0.1 + +### Patch Changes + +- e1c189b: Adds okta-provider backend module for the auth plugin +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + +## 0.0.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.0.1-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.0.1-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-okta-provider/config.d.ts b/plugins/auth-backend-module-okta-provider/config.d.ts index f047c96739..42688528ee 100644 --- a/plugins/auth-backend-module-okta-provider/config.d.ts +++ b/plugins/auth-backend-module-okta-provider/config.d.ts @@ -29,6 +29,7 @@ export interface Config { authServerId?: string; idp?: string; callbackUrl?: string; + additionalScopes?: string; }; }; }; diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index a9e8606335..2d2d3f857c 100644 --- a/plugins/auth-backend-module-okta-provider/package.json +++ b/plugins/auth-backend-module-okta-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-okta-provider", "description": "The okta-provider backend module for the auth plugin.", - "version": "0.0.1-next.0", + "version": "0.0.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,12 +23,11 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@davidzemon/passport-okta-oauth": "^0.0.5", "express": "^4.18.2", - "passport": "^0.6.0" + "passport": "^0.7.0" }, "devDependencies": { "@backstage/backend-defaults": "workspace:^", diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index 57d4e5611a..8b7c0a8214 100644 --- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md @@ -1,5 +1,94 @@ # @backstage/plugin-auth-backend-module-pinniped-provider +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + +## 0.1.3 + +### Patch Changes + +- 928efbc: Deprecated the `authModulePinnipedProvider` export. A default export is now available and should be used like this in your backend: `backend.add(import('@backstage/plugin-auth-backend-module-pinniped-provider'));` +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.1.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + +## 0.1.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.2-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.1.2-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-pinniped-provider/api-report.md b/plugins/auth-backend-module-pinniped-provider/api-report.md index 430099713e..182ea8ba21 100644 --- a/plugins/auth-backend-module-pinniped-provider/api-report.md +++ b/plugins/auth-backend-module-pinniped-provider/api-report.md @@ -10,9 +10,13 @@ import { OAuthAuthenticator } from '@backstage/plugin-auth-node'; import { Strategy } from 'openid-client'; import { TokenSet } from 'openid-client'; -// @public (undocumented) +// @public @deprecated (undocumented) export const authModulePinnipedProvider: () => BackendFeature; +// @public (undocumented) +const authModulePinnipedProvider_2: () => BackendFeature; +export default authModulePinnipedProvider_2; + // @public (undocumented) export const pinnipedAuthenticator: OAuthAuthenticator< PinnipedStrategyCache, diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index 2b572696fd..a414483000 100644 --- a/plugins/auth-backend-module-pinniped-provider/package.json +++ b/plugins/auth-backend-module-pinniped-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-pinniped-provider", "description": "The pinniped-provider backend module for the auth plugin.", - "version": "0.1.2-next.0", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,7 +23,6 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", @@ -37,11 +36,10 @@ "@backstage/plugin-auth-backend": "workspace:^", "cookie-parser": "^1.4.6", "express": "^4.18.2", - "express-promise-router": "^4.1.1", "express-session": "^1.17.3", "jose": "^4.14.6", "msw": "^1.3.0", - "passport": "^0.6.0", + "passport": "^0.7.0", "supertest": "^6.3.3" }, "files": [ diff --git a/plugins/auth-backend-module-pinniped-provider/src/deprecated.ts b/plugins/auth-backend-module-pinniped-provider/src/deprecated.ts new file mode 100644 index 0000000000..0dd2ed36bd --- /dev/null +++ b/plugins/auth-backend-module-pinniped-provider/src/deprecated.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. + */ + +import { authModulePinnipedProvider as deprecatedAuthModulePinnipedProvider } from './module'; + +/** + * @public + * @deprecated Use default import instead + */ +export const authModulePinnipedProvider = deprecatedAuthModulePinnipedProvider; diff --git a/plugins/auth-backend-module-pinniped-provider/src/index.ts b/plugins/auth-backend-module-pinniped-provider/src/index.ts index df4cd58603..f4b789644d 100644 --- a/plugins/auth-backend-module-pinniped-provider/src/index.ts +++ b/plugins/auth-backend-module-pinniped-provider/src/index.ts @@ -21,4 +21,5 @@ */ export { pinnipedAuthenticator, PinnipedStrategyCache } from './authenticator'; -export { authModulePinnipedProvider } from './module'; +export { authModulePinnipedProvider as default } from './module'; +export * from './deprecated'; diff --git a/plugins/auth-backend-module-vmware-cloud-provider/.eslintrc.js b/plugins/auth-backend-module-vmware-cloud-provider/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md new file mode 100644 index 0000000000..9613bb1499 --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -0,0 +1,95 @@ +# @backstage/plugin-auth-backend-module-vmware-cloud-provider + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + +## 0.1.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.1.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.1.0 + +### Minor Changes + +- ed02c69: Add VMware Cloud auth backend module provider + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + +## 0.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.1.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + +## 0.1.0-next.0 + +### Minor Changes + +- ed02c69a3c: Add VMware Cloud auth backend module provider + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-auth-node@0.4.2-next.1 diff --git a/plugins/auth-backend-module-vmware-cloud-provider/README.md b/plugins/auth-backend-module-vmware-cloud-provider/README.md new file mode 100644 index 0000000000..d06a0d84ab --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/README.md @@ -0,0 +1,7 @@ +# Auth Module: VMware Cloud Provider + +This module provides an VMware Cloud auth provider implementation for `@backstage/plugin-auth-backend`. + +## Links + +- [Backstage](https://backstage.io) diff --git a/plugins/auth-backend-module-vmware-cloud-provider/api-report.md b/plugins/auth-backend-module-vmware-cloud-provider/api-report.md new file mode 100644 index 0000000000..ff9e82dd45 --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/api-report.md @@ -0,0 +1,46 @@ +## API Report File for "@backstage/plugin-auth-backend-module-vmware-cloud-provider" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { OAuthAuthenticator } from '@backstage/plugin-auth-node'; +import { OAuthAuthenticatorResult } from '@backstage/plugin-auth-node'; +import { PassportOAuthAuthenticatorHelper } from '@backstage/plugin-auth-node'; +import { PassportProfile } from '@backstage/plugin-auth-node'; +import { SignInResolverFactory } from '@backstage/plugin-auth-node'; +import { Strategy } from 'passport-oauth2'; + +// @public +const authModuleVmwareCloudProvider: () => BackendFeature; +export default authModuleVmwareCloudProvider; + +// @public +export const vmwareCloudAuthenticator: OAuthAuthenticator< + VMwareCloudAuthenticatorContext, + VMwarePassportProfile +>; + +// @public (undocumented) +export interface VMwareCloudAuthenticatorContext { + // (undocumented) + helper: PassportOAuthAuthenticatorHelper; + // (undocumented) + organizationId?: string; + // (undocumented) + providerStrategy: Strategy; +} + +// @public +export namespace vmwareCloudSignInResolvers { + const profileEmailMatchingUserEntityEmail: SignInResolverFactory< + OAuthAuthenticatorResult, + unknown + >; +} + +// @public (undocumented) +export type VMwarePassportProfile = PassportProfile & { + organizationId?: string; +}; +``` diff --git a/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml b/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml new file mode 100644 index 0000000000..d40b1e4308 --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend-module-vmware-cloud-provider + title: '@backstage/plugin-auth-backend-module-vmware-cloud-provider' + description: The vmware-cloud-provider backend module for the auth plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/eks.ts b/plugins/auth-backend-module-vmware-cloud-provider/config.d.ts similarity index 66% rename from plugins/kubernetes-react/src/utils/clusterLinks/formatters/eks.ts rename to plugins/auth-backend-module-vmware-cloud-provider/config.d.ts index 975c39797c..10b3566899 100644 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/eks.ts +++ b/plugins/auth-backend-module-vmware-cloud-provider/config.d.ts @@ -13,8 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ClusterLinksFormatterOptions } from '../../../types/types'; -export function eksFormatter(_options: ClusterLinksFormatterOptions): URL { - throw new Error('EKS formatter is not yet implemented. Please, contribute!'); +export interface Config { + auth?: { + providers?: { + /** @visibility frontend */ + vmwareCloudServices?: { + [authEnv: string]: { + clientId: string; + organizationId: string; + scope?: string; + consoleEndpoint?: string; + }; + }; + }; + }; } diff --git a/plugins/auth-backend-module-vmware-cloud-provider/dev/index.ts b/plugins/auth-backend-module-vmware-cloud-provider/dev/index.ts new file mode 100644 index 0000000000..d3c18c1d48 --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/dev/index.ts @@ -0,0 +1,24 @@ +/* + * 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 { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.add(import('@backstage/plugin-auth-backend')); +backend.add(import('../src')); + +backend.start(); diff --git a/plugins/auth-backend-module-vmware-cloud-provider/package.json b/plugins/auth-backend-module-vmware-cloud-provider/package.json new file mode 100644 index 0000000000..3a1cbe4aa5 --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/package.json @@ -0,0 +1,46 @@ +{ + "name": "@backstage/plugin-auth-backend-module-vmware-cloud-provider", + "description": "The vmware-cloud-provider backend module for the auth plugin.", + "version": "0.1.2-next.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": "backend-plugin-module" + }, + "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" + }, + "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/catalog-model": "workspace:^", + "@backstage/plugin-auth-node": "workspace:^", + "@types/passport-oauth2": "^1.4.15", + "jose": "^4.6.0", + "passport-oauth2": "^1.6.1" + }, + "devDependencies": { + "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/plugin-auth-backend": "workspace:^", + "msw": "^2.0.8", + "supertest": "^6.3.3" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/auth-backend-module-vmware-cloud-provider/src/authenticator.test.ts b/plugins/auth-backend-module-vmware-cloud-provider/src/authenticator.test.ts new file mode 100644 index 0000000000..f38ceeb462 --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/src/authenticator.test.ts @@ -0,0 +1,477 @@ +/* + * 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 { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { + AuthResolverContext, + encodeOAuthState, + OAuthAuthenticatorAuthenticateInput, + OAuthAuthenticatorRefreshInput, + OAuthAuthenticatorStartInput, + OAuthState, +} from '@backstage/plugin-auth-node'; +import { SignJWT } from 'jose'; +import { http, HttpResponse } from 'msw'; +import { setupServer } from 'msw/node'; + +import { + vmwareCloudAuthenticator, + VMwareCloudAuthenticatorContext, +} from './authenticator'; + +jest.mock('uid2', () => jest.fn().mockReturnValue('sessionid')); + +describe('vmwareCloudAuthenticator', () => { + const server = setupServer(); + setupRequestMockHandlers(server); + + let oAuthState: OAuthState = { + nonce: 'nonce', + env: 'env', + }; + + const signInInfo: Record = { + given_name: 'Givenname', + family_name: 'Familyname', + context_name: 'orgId', + email: 'user@example.com', + }; + + let idToken: string; + + let authResponse: { + access_token: string; + refresh_token: string; + id_token: typeof idToken; + }; + + let fakeSession: Record; + let authenticatorCtx: VMwareCloudAuthenticatorContext; + + beforeAll(async () => { + idToken = await new SignJWT(signInInfo) + .setProtectedHeader({ alg: 'HS256' }) + .sign(Buffer.from('signing key')); + + authResponse = { + access_token: 'accessToken', + refresh_token: 'refreshToken', + id_token: idToken, + }; + }); + + beforeEach(() => { + server.use( + http.post( + 'https://console.cloud.vmware.com/csp/gateway/am/api/auth/token', + ({ request }) => + request.headers.get('Authorization') + ? HttpResponse.json(authResponse) + : HttpResponse.json(null, { status: 500 }), + ), + ); + + authenticatorCtx = vmwareCloudAuthenticator.initialize({ + callbackUrl: 'http://callbackUrl', + config: new ConfigReader({ + clientId: 'placeholderClientId', + organizationId: 'orgId', + }), + }); + }); + + describe('#initialize', () => { + it('fails when organizationId is not configured', () => { + return expect(() => + vmwareCloudAuthenticator.initialize({ + callbackUrl: 'http://callbackUrl', + config: new ConfigReader({ + clientId: 'placeholderClientId', + }), + }), + ).toThrow(`Missing required config value at 'organizationId'`); + }); + }); + + describe('#start', () => { + let startRequest: OAuthAuthenticatorStartInput; + + beforeEach(() => { + fakeSession = {}; + startRequest = { + state: encodeOAuthState(oAuthState), + req: { + query: {}, + session: fakeSession, + }, + } as OAuthAuthenticatorStartInput; + }); + + it('redirects to the Cloud Services Console consent page', async () => { + const startResponse = await vmwareCloudAuthenticator.start( + startRequest, + authenticatorCtx, + ); + const url = new URL(startResponse.url); + + expect(url.protocol).toBe('https:'); + expect(url.hostname).toBe('console.cloud.vmware.com'); + expect(url.pathname).toBe('/csp/gateway/discovery'); + }); + + it('passes client ID from config', async () => { + const startResponse = await vmwareCloudAuthenticator.start( + startRequest, + authenticatorCtx, + ); + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('client_id')).toBe('placeholderClientId'); + }); + + it('passes organizationId from config', async () => { + const startResponse = await vmwareCloudAuthenticator.start( + startRequest, + authenticatorCtx, + ); + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('orgId')).toBe('orgId'); + }); + + it('passes callback URL', async () => { + const startResponse = await vmwareCloudAuthenticator.start( + startRequest, + authenticatorCtx, + ); + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('redirect_uri')).toBe('http://callbackUrl'); + }); + + it('requests scopes for ID and refresh token', async () => { + const startResponse = await vmwareCloudAuthenticator.start( + startRequest, + authenticatorCtx, + ); + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('scope')).toBe('openid offline_access'); + }); + + it('generates PKCE challenge', async () => { + const startResponse = await vmwareCloudAuthenticator.start( + startRequest, + authenticatorCtx, + ); + const { searchParams } = new URL(startResponse.url); + + expect(searchParams.get('code_challenge_method')).toBe('S256'); + expect(searchParams.get('code_challenge')).not.toBeNull(); + }); + + it('stores PKCE verifier in session', async () => { + await vmwareCloudAuthenticator.start(startRequest, authenticatorCtx); + + expect( + fakeSession['oauth2:console.cloud.vmware.com'].state.code_verifier, + ).toBeDefined(); + }); + + it('fails when request has no session', () => { + return expect( + vmwareCloudAuthenticator.start( + { + state: encodeOAuthState(oAuthState), + req: { + query: {}, + }, + } as OAuthAuthenticatorStartInput, + authenticatorCtx, + ), + ).rejects.toThrow('requires session support'); + }); + + it('adds session ID handle to state param', async () => { + const startResponse = await vmwareCloudAuthenticator.start( + startRequest, + authenticatorCtx, + ); + const stateParam = new URL(startResponse.url).searchParams.get('state'); + + const state = Object.fromEntries( + new URLSearchParams(Buffer.from(stateParam!, 'hex').toString('utf-8')), + ); + + const { handle } = fakeSession['oauth2:console.cloud.vmware.com'].state; + expect(state.handle).toBe(handle); + }); + }); + + describe('#authenticate', () => { + let resolverContext: jest.Mocked; + let authenticateRequest: OAuthAuthenticatorAuthenticateInput; + + beforeEach(() => { + resolverContext = { + issueToken: jest.fn().mockResolvedValue({ + token: 'defaultBackstageToken', + }), + findCatalogUser: jest.fn(), + signInWithCatalogUser: jest.fn().mockResolvedValue({ + token: 'backstageToken', + }), + }; + + oAuthState = { + code_verifier: 'foo', + handle: 'sessionid', + nonce: 'nonce', + env: 'development', + } as OAuthState; + + fakeSession = { + ['oauth2:console.cloud.vmware.com']: { + state: oAuthState, + }, + }; + + authenticateRequest = { + req: { + query: { + code: 'foo', + state: encodeOAuthState(oAuthState), + } as unknown, + session: fakeSession, + }, + } as OAuthAuthenticatorAuthenticateInput; + }); + + it('stores refresh token in cookie', async () => { + const { + session: { refreshToken }, + } = await vmwareCloudAuthenticator.authenticate( + authenticateRequest, + authenticatorCtx, + ); + + expect(refreshToken).toBe('refreshToken'); + }); + + it('responds with ID token', async () => { + const { session } = await vmwareCloudAuthenticator.authenticate( + authenticateRequest, + authenticatorCtx, + ); + + expect(session.idToken).toBe(idToken); + }); + + it('default transform decodes ID token', async () => { + const result = await vmwareCloudAuthenticator.authenticate( + authenticateRequest, + authenticatorCtx, + ); + + const { profile } = + await vmwareCloudAuthenticator.defaultProfileTransform( + result, + resolverContext, + ); + + expect(profile).toStrictEqual({ + email: signInInfo.email, + displayName: `${signInInfo.given_name} ${signInInfo.family_name}`, + }); + }); + + it('default transform fails if claims are missing', async () => { + authenticatorCtx = vmwareCloudAuthenticator.initialize({ + callbackUrl: 'http://callbackUrl', + config: new ConfigReader({ + clientId: 'placeholderClientId', + organizationId: 'myOrgId', + }), + }); + + const result = await vmwareCloudAuthenticator.authenticate( + authenticateRequest, + authenticatorCtx, + ); + + return expect( + vmwareCloudAuthenticator.defaultProfileTransform( + result, + resolverContext, + ), + ).rejects.toThrow('ID token organizationId mismatch'); + }); + + it('default transform fails if organizationId mismatch', async () => { + const inadequateIdToken: string = await new SignJWT({ sub: 'unusual' }) + .setProtectedHeader({ alg: 'HS256' }) + .sign(Buffer.from('signing key')); + + server.use( + http.post( + 'https://console.cloud.vmware.com/csp/gateway/am/api/auth/token', + () => + HttpResponse.json({ + access_token: 'accessToken', + id_token: inadequateIdToken, + }), + ), + ); + + const result = await vmwareCloudAuthenticator.authenticate( + authenticateRequest, + authenticatorCtx, + ); + + return expect( + vmwareCloudAuthenticator.defaultProfileTransform( + result, + resolverContext, + ), + ).rejects.toThrow( + 'ID token missing required claims: email, given_name, family_name', + ); + }); + + it('fails when request has no session', () => { + return expect( + vmwareCloudAuthenticator.authenticate( + { + req: { + query: {}, + }, + } as OAuthAuthenticatorStartInput, + authenticatorCtx, + ), + ).rejects.toThrow('requires session support'); + }); + + it('fails when request has no authorization code', () => { + return expect( + vmwareCloudAuthenticator.authenticate( + { + req: { + query: {}, + session: fakeSession, + }, + } as OAuthAuthenticatorStartInput, + authenticatorCtx, + ), + ).rejects.toThrow('Unexpected redirect'); + }); + }); + + describe('integration between #start and #authenticate', () => { + beforeEach(() => { + fakeSession = { + ['oauth2:console.cloud.vmware.com']: { + state: oAuthState, + }, + }; + }); + + it('state param is compatible', async () => { + const startResponse = await vmwareCloudAuthenticator.start( + { + req: { + query: {}, + session: {}, + }, + state: encodeOAuthState(oAuthState), + } as OAuthAuthenticatorStartInput, + authenticatorCtx, + ); + const { searchParams } = new URL(startResponse.url); + const { session } = await vmwareCloudAuthenticator.authenticate( + { + req: { + query: { + code: 'authorization_code', + state: searchParams.get('state'), + } as unknown, + session: fakeSession, + }, + } as OAuthAuthenticatorAuthenticateInput, + authenticatorCtx, + ); + + expect(session).toBeDefined(); + expect(session.idToken).toBe(idToken); + }); + }); + + describe('#refresh', () => { + let refreshRequest: OAuthAuthenticatorRefreshInput; + let resolverContext: jest.Mocked; + + beforeEach(() => { + resolverContext = { + issueToken: jest.fn().mockResolvedValue({ + token: 'defaultBackstageToken', + }), + findCatalogUser: jest.fn(), + signInWithCatalogUser: jest.fn().mockResolvedValue({ + token: 'backstageToken', + }), + }; + + refreshRequest = { + req: { + query: { + code: 'foo', + state: 'sessionid', + } as unknown, + session: fakeSession, + }, + } as OAuthAuthenticatorRefreshInput; + }); + + it('gets new refresh token', async () => { + const { + session: { refreshToken }, + } = await vmwareCloudAuthenticator.refresh( + refreshRequest, + authenticatorCtx, + ); + + expect(refreshToken).toBe('refreshToken'); + }); + + it('default transform decodes ID token', async () => { + const result = await vmwareCloudAuthenticator.refresh( + refreshRequest, + authenticatorCtx, + ); + + const { profile } = + await vmwareCloudAuthenticator.defaultProfileTransform( + result, + resolverContext, + ); + + expect(profile).toStrictEqual({ + email: signInInfo.email, + displayName: `${signInInfo.given_name} ${signInInfo.family_name}`, + }); + }); + }); +}); diff --git a/plugins/auth-backend-module-vmware-cloud-provider/src/authenticator.ts b/plugins/auth-backend-module-vmware-cloud-provider/src/authenticator.ts new file mode 100644 index 0000000000..4db4836f13 --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/src/authenticator.ts @@ -0,0 +1,238 @@ +/* + * 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 { + createOAuthAuthenticator, + decodeOAuthState, + encodeOAuthState, + OAuthState, + PassportOAuthAuthenticatorHelper, + PassportOAuthDoneCallback, + PassportProfile, +} from '@backstage/plugin-auth-node'; +import { decodeJwt } from 'jose'; +import { + Metadata, + StateStoreStoreCallback, + StateStoreVerifyCallback, + Strategy as OAuth2Strategy, +} from 'passport-oauth2'; + +/** @public */ +export interface VMwareCloudAuthenticatorContext { + organizationId?: string; + providerStrategy: OAuth2Strategy; + helper: PassportOAuthAuthenticatorHelper; +} + +/** @public */ +export type VMwarePassportProfile = PassportProfile & { + organizationId?: string; +}; + +/** + * VMware Cloud Authenticator to be used by `createOAuthProviderFactory` + * + * @public + */ +export const vmwareCloudAuthenticator = createOAuthAuthenticator< + VMwareCloudAuthenticatorContext, + VMwarePassportProfile +>({ + defaultProfileTransform: async input => { + if (!input.session.idToken) { + throw new Error( + `Failed to parse id token and get profile info, missing token from session`, + ); + } + + const vmwareClaims = ['email', 'given_name', 'family_name', 'context_name']; + + const identity = decodeJwt(input.session.idToken); + const missingClaims = vmwareClaims.filter(key => !(key in identity)); + + if (missingClaims.length > 0) { + throw new Error( + `ID token missing required claims: ${missingClaims.join(', ')}`, + ); + } + + const typeMismatchClaims = vmwareClaims.filter( + key => typeof identity[key] !== 'string', + ); + + if (typeMismatchClaims.length > 0) { + throw new Error( + `ID token claims type mismatch: ${typeMismatchClaims.join(', ')}`, + ); + } + + // These claims were checked for presence & type + const { email, given_name, family_name, context_name } = identity as Record< + string, + string + >; + + if (context_name !== input.fullProfile.organizationId) { + throw new Error(`ID token organizationId mismatch`); + } + + return { + profile: { + displayName: `${given_name} ${family_name}`, + email, + }, + }; + }, + initialize({ callbackUrl, config }) { + const consoleEndpoint = + config.getOptionalString('consoleEndpoint') ?? + 'https://console.cloud.vmware.com'; + const organizationId = config.getString('organizationId'); + + const clientId = config.getString('clientId'); + const clientSecret = ''; + const authorizationUrl = `${consoleEndpoint}/csp/gateway/discovery`; + const tokenUrl = `${consoleEndpoint}/csp/gateway/am/api/auth/token`; + const scope = config.getOptionalString('scope') ?? 'openid offline_access'; + + const providerStrategy = new OAuth2Strategy( + { + clientID: clientId, + clientSecret: clientSecret, + callbackURL: callbackUrl, + authorizationURL: authorizationUrl, + tokenURL: tokenUrl, + passReqToCallback: false, + pkce: true, + state: true, + scope: scope, + customHeaders: { + Authorization: `Basic ${encodeClientCredentials( + clientId, + clientSecret, + )}`, + }, + }, + ( + accessToken: any, + refreshToken: any, + params: any, + fullProfile: PassportProfile, + done: PassportOAuthDoneCallback, + ) => { + done(undefined, { fullProfile, params, accessToken }, { refreshToken }); + }, + ); + + // Both VMware & OAuth2Strategy fight over control of the state when PKCE is on, thus this hack + const pkceSessionStore = Object.create( + (providerStrategy as any)._stateStore, + ); + (providerStrategy as any)._stateStore = { + verify(req: Request, state: string, callback: StateStoreVerifyCallback) { + pkceSessionStore.verify( + req, + (decodeOAuthState(state) as any).handle, + callback, + ); + }, + store( + req: Request & { + scope: string; + state: OAuthState; + }, + verifier: string, + state: any, + meta: Metadata, + callback: StateStoreStoreCallback, + ) { + pkceSessionStore.store( + req, + verifier, + state, + meta, + (err: Error, handle: string) => { + callback( + err, + encodeOAuthState({ + handle, + ...state, + ...req.state, + } as OAuthState), + ); + }, + ); + }, + }; + + return { + organizationId, + providerStrategy, + helper: PassportOAuthAuthenticatorHelper.from(providerStrategy), + }; + }, + + async start(input, ctx) { + return new Promise((resolve, reject) => { + const strategy: OAuth2Strategy = Object.create(ctx.providerStrategy); + + strategy.redirect = (url: string, status?: number) => { + const parsed = new URL(url); + if (ctx.organizationId) { + parsed.searchParams.set('orgId', ctx.organizationId); + } + resolve({ url: parsed.toString(), status: status ?? undefined }); + }; + strategy.error = (error: Error) => { + reject(error); + }; + strategy.authenticate(input.req, { + scope: input.scope, + state: decodeOAuthState(input.state), + accessType: 'offline', + prompt: 'consent', + }); + }); + }, + + async authenticate(input, ctx) { + return ctx.helper.authenticate(input).then(result => ({ + ...result, + fullProfile: { + ...result.fullProfile, + organizationId: ctx.organizationId, + } as VMwarePassportProfile, + })); + }, + + async refresh(input, ctx) { + return ctx.helper.refresh(input).then(result => ({ + ...result, + fullProfile: { + ...result.fullProfile, + organizationId: ctx.organizationId, + } as VMwarePassportProfile, + })); + }, +}); + +/** @private */ +function encodeClientCredentials( + clientID: string, + clientSecret: string, +): string { + return Buffer.from(`${clientID}:${clientSecret}`).toString('base64'); +} diff --git a/plugins/auth-backend-module-vmware-cloud-provider/src/index.ts b/plugins/auth-backend-module-vmware-cloud-provider/src/index.ts new file mode 100644 index 0000000000..1d7bb14b63 --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/src/index.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +/** + * The vmware-cloud-provider backend module for the auth plugin. + * + * @packageDocumentation + */ + +export { + vmwareCloudAuthenticator, + type VMwareCloudAuthenticatorContext, + type VMwarePassportProfile, +} from './authenticator'; +export { authModuleVmwareCloudProvider as default } from './module'; +export { vmwareCloudSignInResolvers } from './resolvers'; diff --git a/plugins/auth-backend-module-vmware-cloud-provider/src/module.test.ts b/plugins/auth-backend-module-vmware-cloud-provider/src/module.test.ts new file mode 100644 index 0000000000..9bed931da1 --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/src/module.test.ts @@ -0,0 +1,90 @@ +/* + * 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 { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { decodeOAuthState } from '@backstage/plugin-auth-node'; +import request from 'supertest'; + +import { Config } from '../config'; +import { authModuleVmwareCloudProvider } from './module'; + +describe('authModuleVmwareCloudProvider', () => { + it('should start', async () => { + const backend = await startTestBackend({ + features: [ + import('@backstage/plugin-auth-backend'), + authModuleVmwareCloudProvider, + mockServices.rootConfig.factory({ + data: { + app: { + baseUrl: 'http://localhost:3000', + }, + auth: { + session: { secret: 'test' }, + providers: { + vmwareCloudServices: { + development: { + clientId: 'placeholderClientId', + organizationId: 'orgId', + }, + }, + }, + } as Config['auth'], + }, + }), + ], + }); + + const { server } = backend; + + const agent = request.agent(server); + + const res = await agent.get( + '/api/auth/vmwareCloudServices/start?env=development', + ); + + expect(res.status).toEqual(302); + + const nonceCookie = agent.jar.getCookie('vmwareCloudServices-nonce', { + domain: 'localhost', + path: '/api/auth/vmwareCloudServices/handler', + script: false, + secure: false, + }); + expect(nonceCookie).toBeDefined(); + + const startUrl = new URL(res.get('location')); + expect(startUrl.origin).toBe('https://console.cloud.vmware.com'); + expect(startUrl.pathname).toBe('/csp/gateway/discovery'); + expect(Object.fromEntries(startUrl.searchParams)).toEqual({ + response_type: 'code', + client_id: 'placeholderClientId', + redirect_uri: `http://localhost:${server.port()}/api/auth/vmwareCloudServices/handler/frame`, + code_challenge: expect.any(String), + state: expect.any(String), + scope: 'openid offline_access', + orgId: 'orgId', + code_challenge_method: 'S256', + }); + + expect(decodeOAuthState(startUrl.searchParams.get('state')!)).toEqual({ + env: 'development', + handle: expect.any(String), + nonce: decodeURIComponent(nonceCookie.value), + }); + + backend.stop(); + }); +}); diff --git a/plugins/auth-backend-module-vmware-cloud-provider/src/module.ts b/plugins/auth-backend-module-vmware-cloud-provider/src/module.ts new file mode 100644 index 0000000000..5ef79654a3 --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/src/module.ts @@ -0,0 +1,51 @@ +/* + * 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 { createBackendModule } from '@backstage/backend-plugin-api'; +import { + authProvidersExtensionPoint, + commonSignInResolvers, + createOAuthProviderFactory, +} from '@backstage/plugin-auth-node'; + +import { vmwareCloudAuthenticator } from './authenticator'; +import { vmwareCloudSignInResolvers } from './resolvers'; + +/** + * VMware Cloud Provider backend module for the auth plugin + * + * @public + */ +export const authModuleVmwareCloudProvider = createBackendModule({ + pluginId: 'auth', + moduleId: 'vmware-cloud-provider', + register(reg) { + reg.registerInit({ + deps: { providers: authProvidersExtensionPoint }, + async init({ providers }) { + providers.registerProvider({ + providerId: 'vmwareCloudServices', + factory: createOAuthProviderFactory({ + authenticator: vmwareCloudAuthenticator, + signInResolverFactories: { + ...vmwareCloudSignInResolvers, + ...commonSignInResolvers, + }, + }), + }); + }, + }); + }, +}); diff --git a/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.test.ts b/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.test.ts new file mode 100644 index 0000000000..250fd430c7 --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.test.ts @@ -0,0 +1,90 @@ +/* + * 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 { NotFoundError } from '@backstage/errors'; +import { + AuthResolverContext, + OAuthAuthenticatorResult, + PassportProfile, + SignInInfo, + SignInResolver, +} from '@backstage/plugin-auth-node'; + +import { vmwareCloudSignInResolvers } from './resolvers'; + +describe('vmwareCloudResolver', () => { + let resolverContext: jest.Mocked; + let signInInfo: SignInInfo>; + let signInResolver: SignInResolver>; + + beforeEach(() => { + resolverContext = { + issueToken: jest.fn().mockResolvedValue({ + token: 'defaultBackstageToken', + }), + findCatalogUser: jest.fn(), + signInWithCatalogUser: jest.fn().mockResolvedValue({ + token: 'backstageToken', + }), + }; + + signInInfo = { + result: {} as any, // Resolver doesn't care about the result object + profile: { + displayName: 'TestName', + email: 'user@example.com', + }, + }; + + signInResolver = + vmwareCloudSignInResolvers.profileEmailMatchingUserEntityEmail(); + }); + + it('looks up backstage identity by email', async () => { + const backstageIdentity = await signInResolver(signInInfo, resolverContext); + + expect(backstageIdentity.token).toBe('backstageToken'); + expect(resolverContext.signInWithCatalogUser).toHaveBeenCalledWith({ + filter: { + 'spec.profile.email': 'user@example.com', + }, + }); + }); + + it('returns "fake" backstage identity when no entity matches', async () => { + resolverContext.signInWithCatalogUser.mockRejectedValue( + new NotFoundError('User not found'), + ); + + const backstageIdentity = await signInResolver(signInInfo, resolverContext); + + expect(backstageIdentity.token).toBe('defaultBackstageToken'); + expect(resolverContext.issueToken).toHaveBeenCalledWith({ + claims: { + sub: 'user:default/user@example.com', + ent: ['user:default/user@example.com'], + }, + }); + }); + + it('fails when resolver context throws other error', () => { + const error = new Error('bizarre'); + resolverContext.signInWithCatalogUser.mockRejectedValue(error); + + return expect(signInResolver(signInInfo, resolverContext)).rejects.toThrow( + error, + ); + }); +}); diff --git a/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.ts b/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.ts new file mode 100644 index 0000000000..2b3a78842b --- /dev/null +++ b/plugins/auth-backend-module-vmware-cloud-provider/src/resolvers.ts @@ -0,0 +1,75 @@ +/* + * 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 { stringifyEntityRef } from '@backstage/catalog-model'; +import { + createSignInResolverFactory, + OAuthAuthenticatorResult, + PassportProfile, + SignInInfo, +} from '@backstage/plugin-auth-node'; + +/** + * Available sign-in resolvers for the VMware Cloud auth provider. + * + * @public + */ +export namespace vmwareCloudSignInResolvers { + /** + * Looks up the user by matching their profile email to the entity's profile email. + * If that fails, sign in the user without associating with a catalog user. + */ + export const profileEmailMatchingUserEntityEmail = + createSignInResolverFactory({ + create() { + return async ( + info: SignInInfo>, + ctx, + ) => { + const email = info.profile.email; + + if (!email) { + throw new Error( + 'VMware login failed, user profile does not contain an email', + ); + } + + const userEntityRef = stringifyEntityRef({ + kind: 'User', + name: email, + }); + + try { + // we await here so that signInWithCatalogUser throws in the current `try` + return await ctx.signInWithCatalogUser({ + filter: { + 'spec.profile.email': email, + }, + }); + } catch (e) { + if (e.name !== 'NotFoundError') { + throw e; + } + return ctx.issueToken({ + claims: { + sub: userEntityRef, + ent: [userEntityRef], + }, + }); + } + }; + }, + }); +} diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 2ab0a71b77..27b912aca1 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,221 @@ # @backstage/plugin-auth-backend +## 0.20.4-next.0 + +### Patch Changes + +- a3f1fa3: Use the externalized `auth-backend-module-microsoft-provider` again. +- 5d2fcba: Migrated oidc auth provider to new `@backstage/plugin-auth-backend-module-oidc-provider` module package. +- Updated dependencies + - @backstage/plugin-auth-backend-module-okta-provider@0.0.3-next.0 + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.0-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.5-next.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.2-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.7-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.7-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.7-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.7-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.4-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.2-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.20.3 + +### Patch Changes + +- 004499c: Fixed an issue where some Okta's resolvers were missing +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.3 + - @backstage/plugin-auth-backend-module-github-provider@0.1.6 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.6 + - @backstage/plugin-auth-backend-module-google-provider@0.1.6 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.6 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.2 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.20.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.1-next.2 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.3-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.1.6-next.2 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.6-next.2 + - @backstage/plugin-auth-backend-module-google-provider@0.1.6-next.2 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.6-next.2 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.1-next.2 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.2-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + +## 0.20.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.1-next.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.3-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.6-next.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.6-next.1 + - @backstage/plugin-auth-backend-module-google-provider@0.1.6-next.1 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.6-next.1 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.1-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.2-next.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.20.3-next.0 + +### Patch Changes + +- 004499c: Fixed an issue where some Okta's resolvers were missing +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.1-next.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.1-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.3-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.6-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.6-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.6-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.6-next.0 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.20.1 + +### Patch Changes + +- 7ac2575: `oauth2-proxy` auth implementation has been moved to `@backstage/plugin-auth-backend-module-oauth2-proxy-provider` +- 2a5891e: Migrate the atlassian auth provider to be implemented using the new `@backstage/plugin-auth-backend-module-atlassian-provider` module +- 783797a: fix static token issuer not being able to initialize +- e1c189b: The Okta provider implementation is moved to the new module +- a62764b: Updated dependency `passport` to `^0.7.0`. +- bcbbf8e: Updated dependency `@google-cloud/firestore` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.0 + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.5 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.5 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.2 + - @backstage/plugin-auth-backend-module-google-provider@0.1.5 + - @backstage/plugin-auth-backend-module-github-provider@0.1.5 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.20.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.0-next.3 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.2-next.3 + - @backstage/plugin-auth-backend-module-github-provider@0.1.5-next.3 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.5-next.3 + - @backstage/plugin-auth-backend-module-google-provider@0.1.5-next.3 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.5-next.3 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.0-next.2 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.1-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.20.1-next.2 + +### Patch Changes + +- 783797a: fix static token issuer not being able to initialize +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.0-next.1 + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.0-next.2 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.5-next.2 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.5-next.2 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.1-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.2-next.2 + - @backstage/plugin-auth-backend-module-google-provider@0.1.5-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.5-next.2 + +## 0.20.1-next.1 + +### Patch Changes + +- 7ac25759a5: `oauth2-proxy` auth implementation has been moved to `@backstage/plugin-auth-backend-module-oauth2-proxy-provider` +- bcbbf8e042: Updated dependency `@google-cloud/firestore` to `^7.0.0`. +- Updated dependencies + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.0-next.0 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.5-next.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.5-next.1 + - @backstage/plugin-auth-backend-module-google-provider@0.1.5-next.1 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.5-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.1-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.2-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.20.1-next.0 ### Patch Changes diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index c20ddbe5a8..ef7b7909ba 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -3,8 +3,6 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -/// - import { AuthProviderConfig as AuthProviderConfig_2 } from '@backstage/plugin-auth-node'; import { AuthProviderFactory as AuthProviderFactory_2 } from '@backstage/plugin-auth-node'; import { AuthProviderRouteHandlers as AuthProviderRouteHandlers_2 } from '@backstage/plugin-auth-node'; @@ -23,10 +21,11 @@ import { Entity } from '@backstage/catalog-model'; import express from 'express'; import { GcpIapResult as GcpIapResult_2 } from '@backstage/plugin-auth-backend-module-gcp-iap-provider'; import { GcpIapTokenInfo as GcpIapTokenInfo_2 } from '@backstage/plugin-auth-backend-module-gcp-iap-provider'; -import { IncomingHttpHeaders } from 'http'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { OAuth2ProxyResult as OAuth2ProxyResult_2 } from '@backstage/plugin-auth-backend-module-oauth2-proxy-provider'; import { OAuthEnvironmentHandler as OAuthEnvironmentHandler_2 } from '@backstage/plugin-auth-node'; import { OAuthState as OAuthState_2 } from '@backstage/plugin-auth-node'; +import { OidcAuthResult as OidcAuthResult_2 } from '@backstage/plugin-auth-backend-module-oidc-provider'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { prepareBackstageIdentityResponse as prepareBackstageIdentityResponse_2 } from '@backstage/plugin-auth-node'; @@ -36,9 +35,7 @@ import { SignInInfo as SignInInfo_2 } from '@backstage/plugin-auth-node'; import { SignInResolver as SignInResolver_2 } from '@backstage/plugin-auth-node'; import { TokenManager } from '@backstage/backend-common'; import { TokenParams as TokenParams_2 } from '@backstage/plugin-auth-node'; -import { TokenSet } from 'openid-client'; import { UserEntity } from '@backstage/catalog-model'; -import { UserinfoResponse } from 'openid-client'; import { WebMessageResponse as WebMessageResponse_2 } from '@backstage/plugin-auth-node'; // @public @deprecated @@ -228,13 +225,8 @@ export type GithubOAuthResult = { refreshToken?: string; }; -// @public -export type OAuth2ProxyResult = { - fullProfile: JWTPayload; - accessToken: string; - headers: IncomingHttpHeaders; - getHeader(name: string): string | undefined; -}; +// @public @deprecated (undocumented) +export type OAuth2ProxyResult = OAuth2ProxyResult_2; // @public @deprecated (undocumented) export class OAuthAdapter implements AuthProviderRouteHandlers { @@ -347,11 +339,8 @@ export type OAuthStartResponse = { // @public @deprecated (undocumented) export type OAuthState = OAuthState_2; -// @public -export type OidcAuthResult = { - tokenset: TokenSet; - userinfo: UserinfoResponse; -}; +// @public @deprecated (undocumented) +export type OidcAuthResult = OidcAuthResult_2; // @public @deprecated (undocumented) export const postMessageResponse: ( @@ -538,9 +527,9 @@ export const providers: Readonly<{ | undefined, ) => AuthProviderFactory_2; resolvers: Readonly<{ - emailLocalPartMatchingUserEntityName: () => SignInResolver; - emailMatchingUserEntityProfileEmail: () => SignInResolver; - emailMatchingUserEntityAnnotation(): SignInResolver; + emailMatchingUserEntityProfileEmail: () => SignInResolver_2; + emailLocalPartMatchingUserEntityName: () => SignInResolver_2; + emailMatchingUserEntityAnnotation: () => SignInResolver_2; }>; }>; oauth2: Readonly<{ @@ -560,9 +549,9 @@ export const providers: Readonly<{ }>; oauth2Proxy: Readonly<{ create: (options: { - authHandler?: AuthHandler> | undefined; + authHandler?: AuthHandler | undefined; signIn: { - resolver: SignInResolver>; + resolver: SignInResolver; }; }) => AuthProviderFactory_2; resolvers: never; @@ -571,10 +560,10 @@ export const providers: Readonly<{ create: ( options?: | { - authHandler?: AuthHandler | undefined; + authHandler?: AuthHandler | undefined; signIn?: | { - resolver: SignInResolver; + resolver: SignInResolver; } | undefined; } @@ -599,6 +588,8 @@ export const providers: Readonly<{ | undefined, ) => AuthProviderFactory_2; resolvers: Readonly<{ + emailLocalPartMatchingUserEntityName: () => SignInResolver; + emailMatchingUserEntityProfileEmail: () => SignInResolver; emailMatchingUserEntityAnnotation(): SignInResolver; }>; }>; diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts index 34139593d3..9dbb89fada 100644 --- a/plugins/auth-backend/config.d.ts +++ b/plugins/auth-backend/config.d.ts @@ -149,22 +149,6 @@ export interface Config { }; }; /** @visibility frontend */ - oidc?: { - [authEnv: string]: { - clientId: string; - /** - * @visibility secret - */ - clientSecret: string; - callbackUrl?: string; - metadataUrl: string; - tokenEndpointAuthMethod?: string; - tokenSignedResponseAlg?: string; - scope?: string; - prompt?: string; - }; - }; - /** @visibility frontend */ auth0?: { [authEnv: string]: { clientId: string; @@ -180,18 +164,6 @@ export interface Config { }; }; /** @visibility frontend */ - microsoft?: { - [authEnv: string]: { - clientId: string; - /** - * @visibility secret - */ - clientSecret: string; - tenantId: string; - callbackUrl?: string; - }; - }; - /** @visibility frontend */ onelogin?: { [authEnv: string]: { clientId: string; diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index aa8a5b3151..16445713dc 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend", "description": "A Backstage backend plugin that handles authentication", - "version": "0.20.1-next.0", + "version": "0.20.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -43,12 +43,14 @@ "@backstage/plugin-auth-backend-module-github-provider": "workspace:^", "@backstage/plugin-auth-backend-module-gitlab-provider": "workspace:^", "@backstage/plugin-auth-backend-module-google-provider": "workspace:^", + "@backstage/plugin-auth-backend-module-microsoft-provider": "workspace:^", "@backstage/plugin-auth-backend-module-oauth2-provider": "workspace:^", + "@backstage/plugin-auth-backend-module-oauth2-proxy-provider": "workspace:^", + "@backstage/plugin-auth-backend-module-oidc-provider": "workspace:^", "@backstage/plugin-auth-backend-module-okta-provider": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", - "@backstage/types": "workspace:^", - "@google-cloud/firestore": "^6.0.0", + "@google-cloud/firestore": "^7.0.0", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", "compression": "^1.7.4", @@ -70,11 +72,10 @@ "node-cache": "^5.1.2", "node-fetch": "^2.6.7", "openid-client": "^5.2.1", - "passport": "^0.6.0", + "passport": "^0.7.0", "passport-auth0": "^1.4.3", "passport-bitbucket-oauth2": "^0.1.2", "passport-github2": "^0.1.12", - "passport-gitlab2": "^5.0.0", "passport-google-oauth20": "^2.0.0", "passport-microsoft": "^1.0.0", "passport-oauth2": "^1.6.1", diff --git a/plugins/auth-backend/src/identity/DatabaseKeyStore.test.ts b/plugins/auth-backend/src/identity/DatabaseKeyStore.test.ts index cf415c7e13..f86c4a400b 100644 --- a/plugins/auth-backend/src/identity/DatabaseKeyStore.test.ts +++ b/plugins/auth-backend/src/identity/DatabaseKeyStore.test.ts @@ -28,9 +28,7 @@ const keyBase = { jest.setTimeout(60_000); describe('DatabaseKeyStore', () => { - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); it.each(databases.eachSupportedId())( 'should store a key, %p', diff --git a/plugins/auth-backend/src/identity/KeyStores.ts b/plugins/auth-backend/src/identity/KeyStores.ts index 7242530c57..16f25817a9 100644 --- a/plugins/auth-backend/src/identity/KeyStores.ts +++ b/plugins/auth-backend/src/identity/KeyStores.ts @@ -76,7 +76,7 @@ export class KeyStores { } if (provider === 'static') { - await StaticKeyStore.fromConfig(config); + return await StaticKeyStore.fromConfig(config); } throw new Error(`Unknown KeyStore provider: ${provider}`); diff --git a/plugins/auth-backend/src/migrations.test.ts b/plugins/auth-backend/src/migrations.test.ts index 194bbbf44c..f80b729975 100644 --- a/plugins/auth-backend/src/migrations.test.ts +++ b/plugins/auth-backend/src/migrations.test.ts @@ -42,9 +42,7 @@ async function migrateUntilBefore(knex: Knex, target: string): Promise { jest.setTimeout(60_000); describe('migrations', () => { - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); it.each(databases.eachSupportedId())( '20230428155633_sessions.js, %p', diff --git a/plugins/auth-backend/src/providers/microsoft/__testUtils__/fake.test.ts b/plugins/auth-backend/src/providers/microsoft/__testUtils__/fake.test.ts deleted file mode 100644 index dee37f0f6b..0000000000 --- a/plugins/auth-backend/src/providers/microsoft/__testUtils__/fake.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 { FakeMicrosoftAPI } from './fake'; - -describe('FakeMicrosoftAPI', () => { - const api = new FakeMicrosoftAPI(); - - describe('#token', () => { - it('exchanges auth codes', () => { - const { access_token } = api.token( - new URLSearchParams({ - grant_type: 'authorization_code', - code: api.generateAuthCode('User.Read'), - }), - ); - - expect(api.tokenHasScope(access_token, 'User.Read')).toBe(true); - }); - - it('supports scopes for the first requested audience only', () => { - const { access_token } = api.token( - new URLSearchParams({ - grant_type: 'authorization_code', - code: api.generateAuthCode('someaudience/somescope User.Read'), - }), - ); - - expect(api.tokenHasScope(access_token, 'User.Read')).toBe(false); - }); - - it('special openid scopes do not count towards the 1-audience limit', () => { - const { access_token } = api.token( - new URLSearchParams({ - grant_type: 'authorization_code', - code: api.generateAuthCode('openid offline_access User.Read'), - }), - ); - - expect(api.tokenHasScope(access_token, 'User.Read')).toBe(true); - }); - - it('refreshes tokens', () => { - const { access_token } = api.token( - new URLSearchParams({ - grant_type: 'refresh_token', - refresh_token: api.generateRefreshToken( - 'email openid profile User.Read', - ), - }), - ); - - expect( - api.tokenHasScope(access_token, 'email openid profile User.Read'), - ).toBe(true); - }); - it('requires `openid` scope for ID token', () => { - const { id_token } = api.token( - new URLSearchParams({ - grant_type: 'authorization_code', - code: api.generateAuthCode('User.Read'), - }), - ); - - expect(id_token).toBeUndefined(); - }); - it('requires `offline_access` scope for refresh token', () => { - const { refresh_token } = api.token( - new URLSearchParams({ - grant_type: 'authorization_code', - code: api.generateAuthCode('User.Read'), - }), - ); - - expect(refresh_token).toBeUndefined(); - }); - }); -}); diff --git a/plugins/auth-backend/src/providers/microsoft/__testUtils__/fake.ts b/plugins/auth-backend/src/providers/microsoft/__testUtils__/fake.ts deleted file mode 100644 index 9b3ca09c57..0000000000 --- a/plugins/auth-backend/src/providers/microsoft/__testUtils__/fake.ts +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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 { decodeJwt } from 'jose'; - -type Claims = { aud: string; scp: string }; - -export class FakeMicrosoftAPI { - generateAccessToken(scope: string): string { - return this.tokenWithClaims(this.allClaimsForScope(scope)).access_token; - } - generateAuthCode(scope: string): string { - return this.encodeClaims(this.allClaimsForScope(scope)); - } - generateRefreshToken(scope: string): string { - return this.encodeClaims(this.allClaimsForScope(scope)); - } - token(formData: URLSearchParams): { - access_token: string; - scope: string; - refresh_token?: string; - id_token?: string; - } { - const scopeParameter = formData.get('scope'); - const claims = - (scopeParameter && this.allClaimsForScope(scopeParameter)) ?? - formData.get('grant_type') === 'refresh_token' - ? this.decodeClaims(formData.get('refresh_token')!) - : this.decodeClaims(formData.get('code')!); - return { - ...this.tokenWithClaims(claims), - ...(this.hasScope(claims, 'offline_access') && { - refresh_token: this.encodeClaims(claims), - }), - ...(this.hasScope(claims, 'openid') && { - id_token: 'header.e30K.microsoft', - }), - }; - } - tokenHasScope(token: string, scope: string): boolean { - const { aud, scp } = decodeJwt(token); - return this.hasScope({ aud: aud as string, scp: scp as string }, scope); - } - private tokenWithClaims(claims: Claims): { - access_token: string; - scope: string; - } { - const filteredClaims = { - ...claims, - scp: claims.scp - .split(' ') - .filter(s => s !== 'offline_access') - .join(' '), - }; - return { - access_token: `header.${Buffer.from( - JSON.stringify(filteredClaims), - ).toString('base64')}.signature`, - scope: this.scopeFromClaims(filteredClaims), - }; - } - private allClaimsForScope(scope: string): Claims { - const scopes = scope.split(' ').map(this.parseScope); - const firstAudience = scopes - .map(({ aud }) => aud) - .find(aud => aud !== 'openid'); - return { - aud: firstAudience ?? '00000003-0000-0000-c000-000000000000', - scp: scopes - .filter(({ aud }) => aud === 'openid' || aud === firstAudience) - .map(({ scp }) => scp) - .join(' '), - }; - } - // auth codes and refresh tokens in this fake system are base64-encoded JSON - // strings of claims - private encodeClaims(claims: Claims): string { - return Buffer.from(JSON.stringify(claims)).toString('base64'); - } - private decodeClaims(encoded: string): Claims { - return JSON.parse(Buffer.from(encoded, 'base64').toString()); - } - private hasScope(claims: Claims, scope: string): boolean { - return this.scopeFromClaims(claims).includes(scope); - } - private parseScope(s: string): Claims { - if (s.includes('/')) { - const [aud, scp] = s.split('/'); - return { aud, scp }; - } - switch (s) { - case 'email': - case 'openid': - case 'offline_access': - case 'profile': { - return { aud: 'openid', scp: s }; - } - default: - return { aud: '00000003-0000-0000-c000-000000000000', scp: s }; - } - } - private scopeFromClaims(claims: Claims): string { - return claims.scp - .split(' ') - .map(this.parseScope) - .map(({ aud, scp }) => - aud === 'openid' || - claims.aud === '00000003-0000-0000-c000-000000000000' - ? scp - : `${claims.aud}/${scp}`, - ) - .join(' '); - } -} diff --git a/plugins/auth-backend/src/providers/microsoft/provider.test.ts b/plugins/auth-backend/src/providers/microsoft/provider.test.ts deleted file mode 100644 index 7bbfd085d6..0000000000 --- a/plugins/auth-backend/src/providers/microsoft/provider.test.ts +++ /dev/null @@ -1,450 +0,0 @@ -/* - * 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 { microsoft } from './provider'; -import { getVoidLogger } from '@backstage/backend-common'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; -import { ConfigReader } from '@backstage/config'; -import { rest } from 'msw'; -import { setupServer } from 'msw/node'; -import { AuthProviderRouteHandlers, AuthResolverContext } from '../types'; -import express from 'express'; -import crypto from 'crypto'; -import { FakeMicrosoftAPI } from './__testUtils__/fake'; - -describe('MicrosoftAuthProvider', () => { - const nonce = 'AAAAAAAAAAAAAAAAAAAAAA=='; // 16 bytes of zeros in base64 - const state = Buffer.from( - `nonce=${encodeURIComponent(nonce)}&env=development`, - ).toString('hex'); - const mockBackstageToken = `header.${Buffer.from( - JSON.stringify({ sub: 'user:default/mock' }), - 'utf8', - ).toString('base64')}.backstage`; - - const server = setupServer(); - const microsoftApi = new FakeMicrosoftAPI(); - let provider: AuthProviderRouteHandlers; - let response: jest.Mocked; - - setupRequestMockHandlers(server); - - beforeEach(() => { - provider = microsoft.create({ - signIn: { - resolver: microsoft.resolvers.emailMatchingUserEntityAnnotation(), - }, - })({ - providerId: 'microsoft', - baseUrl: 'http://backstage.test/api/auth', - appUrl: 'http://backstage.test', - isOriginAllowed: _ => true, - globalConfig: { - baseUrl: 'http://backstage.test/api/auth', - appUrl: 'http://backstage.test', - isOriginAllowed: _ => true, - }, - config: new ConfigReader({ - development: { - tenantId: 'tenantId', - clientId: 'clientId', - clientSecret: 'clientSecret', - }, - }), - logger: getVoidLogger(), - resolverContext: { - issueToken: jest.fn(), - findCatalogUser: jest.fn(), - signInWithCatalogUser: async _ => ({ - token: mockBackstageToken, - }), - } as AuthResolverContext, - }) as AuthProviderRouteHandlers; - - server.use( - rest.post( - 'https://login.microsoftonline.com/tenantId/oauth2/v2.0/token', - async (req, res, ctx) => { - return res( - ctx.json({ - ...microsoftApi.token(new URLSearchParams(await req.text())), - token_type: 'Bearer', - expires_in: 123, - ext_expires_in: 123, - }), - ); - }, - ), - rest.get('https://graph.microsoft.com/v1.0/me/', (req, res, ctx) => { - if ( - !microsoftApi.tokenHasScope( - req.headers.get('authorization')!.replace(/^Bearer /, ''), - 'User.Read', - ) - ) { - return res(ctx.status(403)); - } - return res( - ctx.json({ - id: 'conrad', - displayName: 'Conrad', - surname: 'Ribas', - givenName: 'Francisco', - mail: 'conrad@example.com', - }), - ); - }), - rest.get( - 'https://graph.microsoft.com/v1.0/me/photos/*', - async (req, res, ctx) => { - if ( - !microsoftApi.tokenHasScope( - req.headers.get('authorization')!.replace(/^Bearer /, ''), - 'User.Read', - ) - ) { - return res(ctx.status(403)); - } - const imageBuffer = new Uint8Array([104, 111, 119, 100, 121]).buffer; - return res( - ctx.set('Content-Length', imageBuffer.byteLength.toString()), - ctx.set('Content-Type', 'image/jpeg'), - ctx.body(imageBuffer), - ); - }, - ), - ); - response = { - cookie: jest.fn(), - end: jest.fn(), - json: jest.fn(), - setHeader: jest.fn(), - status: jest.fn(), - } as unknown as jest.Mocked; - response.status.mockReturnValue(response); - }); - - describe('#start', () => { - const randomBytes = jest.spyOn( - crypto, - 'randomBytes', - ) as unknown as jest.MockedFunction<(size: number) => Buffer>; - - afterEach(() => { - randomBytes.mockRestore(); - }); - - it('redirects to authorize URL', async () => { - randomBytes.mockReturnValue(Buffer.from(nonce, 'base64')); - - await provider.start( - { - query: { - env: 'development', - scope: 'email openid profile User.Read', - }, - } as unknown as express.Request, - response, - ); - - expect(response.setHeader).toHaveBeenCalledWith( - 'Location', - 'https://login.microsoftonline.com/tenantId/oauth2/v2.0/authorize' + - '?response_type=code' + - `&redirect_uri=${encodeURIComponent( - 'http://backstage.test/api/auth/microsoft/handler/frame', - )}` + - `&scope=${encodeURIComponent('email openid profile User.Read')}` + - `&state=${state}` + - '&client_id=clientId', - ); - }); - }); - - describe('#handle', () => { - it('returns provider info and profile with photo data', async () => { - await provider.frameHandler( - { - query: { - env: 'development', - code: microsoftApi.generateAuthCode( - 'email openid profile User.Read', - ), - state, - }, - cookies: { - 'microsoft-nonce': nonce, - }, - } as unknown as express.Request, - response, - ); - - expect(response.end).toHaveBeenCalledWith( - expect.stringContaining( - encodeURIComponent( - JSON.stringify({ - type: 'authorization_response', - response: { - providerInfo: { - accessToken: microsoftApi.generateAccessToken( - 'email openid profile User.Read', - ), - scope: 'email openid profile User.Read', - expiresInSeconds: 123, - idToken: 'header.e30K.microsoft', - }, - profile: { - email: 'conrad@example.com', - picture: 'data:image/jpeg;base64,aG93ZHk=', - displayName: 'Conrad', - }, - backstageIdentity: { - token: mockBackstageToken, - identity: { - type: 'user', - userEntityRef: 'user:default/mock', - ownershipEntityRefs: [], - }, - }, - }, - }), - ), - ), - ); - }); - - it('returns access token for non-microsoft graph scope', async () => { - await provider.frameHandler( - { - query: { - env: 'development', - code: microsoftApi.generateAuthCode('aks-audience/user.read'), - state, - }, - cookies: { - 'microsoft-nonce': nonce, - }, - } as unknown as express.Request, - response, - ); - - expect(response.end).toHaveBeenCalledWith( - expect.stringContaining( - encodeURIComponent( - JSON.stringify({ - type: 'authorization_response', - response: { - providerInfo: { - accessToken: microsoftApi.generateAccessToken( - 'aks-audience/user.read', - ), - scope: 'aks-audience/user.read', - expiresInSeconds: 123, - }, - profile: {}, - }, - }), - ), - ), - ); - }); - - it('sets refresh token', async () => { - await provider.frameHandler( - { - query: { - env: 'development', - code: microsoftApi.generateAuthCode( - 'email offline_access openid profile User.Read', - ), - state, - }, - cookies: { - 'microsoft-nonce': nonce, - }, - } as unknown as express.Request, - response, - ); - - expect(response.cookie).toHaveBeenCalledWith( - 'microsoft-refresh-token', - microsoftApi.generateRefreshToken( - 'email offline_access openid profile User.Read', - ), - { - domain: 'backstage.test', - httpOnly: true, - maxAge: 86400000000, - path: '/api/auth/microsoft', - sameSite: 'lax', - secure: false, - }, - ); - }); - - it('omits photo data when fetching it fails', async () => { - server.use( - rest.get('https://graph.microsoft.com/v1.0/me/photos/*', (_, res) => - res.networkError('remote hung up'), - ), - ); - - await provider.frameHandler( - { - query: { - env: 'development', - code: microsoftApi.generateAuthCode( - 'email openid profile User.Read', - ), - state, - }, - cookies: { - 'microsoft-nonce': nonce, - }, - } as unknown as express.Request, - response, - ); - - expect(response.end).toHaveBeenCalledWith( - expect.stringContaining( - encodeURIComponent( - JSON.stringify({ - type: 'authorization_response', - response: { - providerInfo: { - accessToken: microsoftApi.generateAccessToken( - 'email openid profile User.Read', - ), - scope: 'email openid profile User.Read', - expiresInSeconds: 123, - idToken: 'header.e30K.microsoft', - }, - profile: { - email: 'conrad@example.com', - displayName: 'Conrad', - }, - backstageIdentity: { - token: mockBackstageToken, - identity: { - type: 'user', - userEntityRef: 'user:default/mock', - ownershipEntityRefs: [], - }, - }, - }, - }), - ), - ), - ); - }); - }); - - describe('#refresh', () => { - it('returns provider info and profile with photo data', async () => { - await provider.refresh!( - { - query: { - env: 'development', - scope: 'email openid profile User.Read', - }, - header: jest.fn(_ => 'XMLHttpRequest'), - cookies: { - 'microsoft-refresh-token': microsoftApi.generateRefreshToken( - 'email openid profile User.Read', - ), - }, - get: jest.fn(), - } as unknown as express.Request, - response, - ); - - expect(response.json).toHaveBeenCalledWith( - expect.objectContaining({ - providerInfo: { - accessToken: microsoftApi.generateAccessToken( - 'email openid profile User.Read', - ), - scope: 'email openid profile User.Read', - expiresInSeconds: 123, - idToken: 'header.e30K.microsoft', - }, - profile: { - email: 'conrad@example.com', - picture: 'data:image/jpeg;base64,aG93ZHk=', - displayName: 'Conrad', - }, - }), - ); - }); - - it('returns access token for non-microsoft graph scope', async () => { - await provider.refresh!( - { - query: { - env: 'development', - scope: 'aks-audience/user.read', - }, - header: jest.fn(_ => 'XMLHttpRequest'), - cookies: { - 'microsoft-refresh-token': microsoftApi.generateRefreshToken( - 'aks-audience/user.read', - ), - }, - get: jest.fn(), - } as unknown as express.Request, - response, - ); - - expect(response.json).toHaveBeenCalledWith({ - providerInfo: { - accessToken: microsoftApi.generateAccessToken( - 'aks-audience/user.read', - ), - expiresInSeconds: 123, - scope: 'aks-audience/user.read', - }, - profile: {}, - }); - }); - - it('returns backstage identity', async () => { - await provider.refresh!( - { - query: { - env: 'development', - scope: 'email openid profile User.Read', - }, - header: jest.fn(_ => 'XMLHttpRequest'), - cookies: { - 'microsoft-refresh-token': microsoftApi.generateRefreshToken( - 'email openid profile User.Read', - ), - }, - get: jest.fn(), - } as unknown as express.Request, - response, - ); - - expect(response.json).toHaveBeenCalledWith( - expect.objectContaining({ - backstageIdentity: expect.objectContaining({ - token: mockBackstageToken, - }), - }), - ); - }); - }); -}); diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index 8fc8459f10..b004cb9651 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -14,215 +14,22 @@ * limitations under the License. */ -import express from 'express'; -import passport from 'passport'; -import { Strategy as MicrosoftStrategy } from 'passport-microsoft'; -import { - encodeState, - OAuthAdapter, - OAuthEnvironmentHandler, - OAuthHandlers, - OAuthProviderOptions, - OAuthRefreshRequest, - OAuthResponse, - OAuthResult, - OAuthStartRequest, -} from '../../lib/oauth'; -import { - executeFetchUserProfileStrategy, - executeFrameHandlerStrategy, - executeRedirectStrategy, - executeRefreshTokenStrategy, - makeProfileInfo, - PassportDoneCallback, -} from '../../lib/passport'; -import { - AuthHandler, - OAuthStartResponse, - SignInResolver, - AuthResolverContext, -} from '../types'; +import { SignInResolver, AuthHandler } from '../types'; +import { OAuthResult } from '../../lib/oauth'; import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; import { - commonByEmailLocalPartResolver, - commonByEmailResolver, -} from '../resolvers'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import fetch from 'node-fetch'; -import { decodeJwt } from 'jose'; -import { Profile as PassportProfile } from 'passport'; -import { BACKSTAGE_SESSION_EXPIRATION } from '../../lib/session'; - -type PrivateInfo = { - refreshToken: string; -}; - -type Options = OAuthProviderOptions & { - signInResolver?: SignInResolver; - authHandler: AuthHandler; - logger: LoggerService; - resolverContext: AuthResolverContext; - authorizationUrl?: string; - tokenUrl?: string; -}; - -export class MicrosoftAuthProvider implements OAuthHandlers { - private readonly _strategy: MicrosoftStrategy; - private readonly signInResolver?: SignInResolver; - private readonly authHandler: AuthHandler; - private readonly logger: LoggerService; - private readonly resolverContext: AuthResolverContext; - - constructor(options: Options) { - this.signInResolver = options.signInResolver; - this.authHandler = options.authHandler; - this.logger = options.logger; - this.resolverContext = options.resolverContext; - - this._strategy = new MicrosoftStrategy( - { - clientID: options.clientId, - clientSecret: options.clientSecret, - callbackURL: options.callbackUrl, - authorizationURL: options.authorizationUrl, - tokenURL: options.tokenUrl, - passReqToCallback: false, - skipUserProfile: ( - accessToken: string, - done: (err: unknown, skip: boolean) => void, - ) => { - done(null, this.skipUserProfile(accessToken)); - }, - }, - ( - accessToken: any, - refreshToken: any, - params: any, - fullProfile: passport.Profile, - done: PassportDoneCallback, - ) => { - done(undefined, { fullProfile, accessToken, params }, { refreshToken }); - }, - ); - } - - private skipUserProfile = (accessToken: string): boolean => { - const { aud, scp } = decodeJwt(accessToken); - const hasGraphReadScope = - aud === '00000003-0000-0000-c000-000000000000' && - (scp as string) - .split(' ') - .map(s => s.toLowerCase()) - .includes('user.read'); - return !hasGraphReadScope; - }; - - async start(req: OAuthStartRequest): Promise { - return await executeRedirectStrategy(req, this._strategy, { - scope: req.scope, - state: encodeState(req.state), - }); - } - - async handler(req: express.Request) { - const { result, privateInfo } = await executeFrameHandlerStrategy< - OAuthResult, - PrivateInfo - >(req, this._strategy); - - return { - response: await this.handleResult(result), - refreshToken: privateInfo.refreshToken, - }; - } - - async refresh(req: OAuthRefreshRequest) { - const { accessToken, refreshToken, params } = - await executeRefreshTokenStrategy( - this._strategy, - req.refreshToken, - req.scope, - ); - - return { - response: await this.handleResult({ - params, - accessToken, - ...(!this.skipUserProfile(accessToken) && { - fullProfile: await executeFetchUserProfileStrategy( - this._strategy, - accessToken, - ), - }), - }), - refreshToken, - }; - } - - private async handleResult(result: { - fullProfile?: PassportProfile; - params: { - id_token?: string; - scope: string; - expires_in: number; - }; - accessToken: string; - refreshToken?: string; - }): Promise { - let profile = {}; - if (result.fullProfile) { - const photo = await this.getUserPhoto(result.accessToken); - result.fullProfile.photos = photo ? [{ value: photo }] : undefined; - ({ profile } = await this.authHandler( - result as OAuthResult, - this.resolverContext, - )); - } - - const expiresInSeconds = - result.params.expires_in === undefined - ? BACKSTAGE_SESSION_EXPIRATION - : Math.min(result.params.expires_in, BACKSTAGE_SESSION_EXPIRATION); - - return { - providerInfo: { - accessToken: result.accessToken, - scope: result.params.scope, - expiresInSeconds, - ...{ idToken: result.params.id_token }, - }, - profile, - ...(result.fullProfile && - this.signInResolver && { - backstageIdentity: await this.signInResolver( - { result: result as OAuthResult, profile }, - this.resolverContext, - ), - }), - }; - } - - private async getUserPhoto(accessToken: string): Promise { - try { - const res = await fetch( - 'https://graph.microsoft.com/v1.0/me/photos/48x48/$value', - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }, - ); - const data = await res.buffer(); - - return `data:image/jpeg;base64,${data.toString('base64')}`; - } catch (error) { - this.logger.warn( - `Could not retrieve user profile photo from Microsoft Graph API: ${error}`, - ); - return undefined; - } - } -} + commonSignInResolvers, + createOAuthProviderFactory, +} from '@backstage/plugin-auth-node'; +import { + adaptLegacyOAuthHandler, + adaptLegacyOAuthSignInResolver, + adaptOAuthSignInResolverToLegacy, +} from '../../lib/legacy'; +import { + microsoftAuthenticator, + microsoftSignInResolvers, +} from '@backstage/plugin-auth-backend-module-microsoft-provider'; /** * Auth provider integration for Microsoft auth @@ -241,75 +48,21 @@ export const microsoft = createAuthProviderIntegration({ * Configure sign-in for this provider, without it the provider can not be used to sign users in. */ signIn?: { - /** - * Maps an auth result to a Backstage identity for the user. - */ resolver: SignInResolver; }; }) { - return ({ providerId, globalConfig, config, logger, resolverContext }) => - OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const clientId = envConfig.getString('clientId'); - const clientSecret = envConfig.getString('clientSecret'); - const tenantId = envConfig.getString('tenantId'); - - const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); - const callbackUrl = - customCallbackUrl || - `${globalConfig.baseUrl}/${providerId}/handler/frame`; - const authorizationUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`; - const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; - - const authHandler: AuthHandler = options?.authHandler - ? options.authHandler - : async ({ fullProfile, params }) => ({ - profile: makeProfileInfo(fullProfile ?? {}, params.id_token), - }); - - const provider = new MicrosoftAuthProvider({ - clientId, - clientSecret, - callbackUrl, - authorizationUrl, - tokenUrl, - authHandler, - signInResolver: options?.signIn?.resolver, - logger, - resolverContext, - }); - - return OAuthAdapter.fromConfig(globalConfig, provider, { - providerId, - callbackUrl, - }); - }); - }, - resolvers: { - /** - * Looks up the user by matching their email local part to the entity name. - */ - emailLocalPartMatchingUserEntityName: () => commonByEmailLocalPartResolver, - /** - * Looks up the user by matching their email to the entity email. - */ - emailMatchingUserEntityProfileEmail: () => commonByEmailResolver, - /** - * Looks up the user by matching their email to the `microsoft.com/email` annotation. - */ - emailMatchingUserEntityAnnotation(): SignInResolver { - return async (info, ctx) => { - const { profile } = info; - - if (!profile.email) { - throw new Error('Microsoft profile contained no email'); - } - - return ctx.signInWithCatalogUser({ - annotations: { - 'microsoft.com/email': profile.email, - }, - }); - }; - }, + return createOAuthProviderFactory({ + authenticator: microsoftAuthenticator, + profileTransform: adaptLegacyOAuthHandler(options?.authHandler), + signInResolver: adaptLegacyOAuthSignInResolver(options?.signIn?.resolver), + }); }, + resolvers: adaptOAuthSignInResolverToLegacy({ + emailLocalPartMatchingUserEntityName: + commonSignInResolvers.emailLocalPartMatchingUserEntityName(), + emailMatchingUserEntityProfileEmail: + commonSignInResolvers.emailMatchingUserEntityProfileEmail(), + emailMatchingUserEntityAnnotation: + microsoftSignInResolvers.emailMatchingUserEntityAnnotation(), + }), }); diff --git a/plugins/auth-backend/src/providers/oauth2-proxy/index.ts b/plugins/auth-backend/src/providers/oauth2-proxy/index.ts index d3004f31e4..2e4e7d016f 100644 --- a/plugins/auth-backend/src/providers/oauth2-proxy/index.ts +++ b/plugins/auth-backend/src/providers/oauth2-proxy/index.ts @@ -15,4 +15,10 @@ */ export { oauth2Proxy } from './provider'; -export type { OAuth2ProxyResult } from './provider'; +import { OAuth2ProxyResult as _OAuth2ProxyResult } from '@backstage/plugin-auth-backend-module-oauth2-proxy-provider'; + +/** + * @public + * @deprecated import from `@backstage/plugin-auth-backend-module-oauth2-proxy-provider` instead + */ +export type OAuth2ProxyResult = _OAuth2ProxyResult; diff --git a/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts b/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts deleted file mode 100644 index f585d4831f..0000000000 --- a/plugins/auth-backend/src/providers/oauth2-proxy/provider.test.ts +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -jest.mock('jose', () => ({ - decodeJwt: jest.fn(), -})); -jest.mock('@backstage/catalog-client'); - -import { AuthenticationError } from '@backstage/errors'; -import express from 'express'; -import * as jose from 'jose'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { AuthHandler, AuthResolverContext, SignInResolver } from '../types'; -import { - oauth2Proxy, - Oauth2ProxyAuthProvider, - OAuth2ProxyResult, - OAUTH2_PROXY_JWT_HEADER, -} from './provider'; - -describe('Oauth2ProxyAuthProvider', () => { - const mockToken = - 'eyblob.eyJzdWIiOiJ1c2VyOmRlZmF1bHQvamltbXltYXJrdW0iLCJlbnQiOlsidXNlcjpkZWZhdWx0L2ppbW15bWFya3VtIl19.eyblob'; - - let provider: Oauth2ProxyAuthProvider; - let logger: jest.Mocked; - let signInResolver: jest.MockedFunction< - SignInResolver> - >; - let authHandler: jest.MockedFunction>>; - let mockResponse: jest.Mocked; - let mockRequest: jest.Mocked; - let mockJwtDecode: jest.MockedFunction; - - beforeEach(() => { - jest.resetAllMocks(); - - mockJwtDecode = jose.decodeJwt as jest.MockedFunction< - typeof jose.decodeJwt - >; - authHandler = jest.fn(); - signInResolver = jest.fn(); - logger = { error: jest.fn() } as unknown as jest.Mocked; - - mockResponse = { - status: jest.fn(), - end: jest.fn(), - json: jest.fn(), - } as unknown as jest.Mocked; - - mockRequest = { - body: {}, - header: jest.fn(), - headers: { - 'x-mock': 'mock', - }, - } as unknown as jest.Mocked; - - provider = new Oauth2ProxyAuthProvider({ - authHandler, - signInResolver, - resolverContext: { - _: 'resolver-context', - } as unknown as AuthResolverContext, - }); - }); - - describe('frameHandler()', () => { - it('should do nothing and return undefined', async () => { - const result = await provider.frameHandler(); - - expect(result).toBeUndefined(); - }); - }); - - describe('start()', () => { - it('should do nothing and return undefined', async () => { - const result = await provider.start(); - - expect(result).toBeUndefined(); - }); - }); - - describe('refresh()', () => { - it('should throw an error when auth header is missing', async () => { - mockRequest.header.mockReturnValue(undefined); - - await expect(provider.refresh(mockRequest, mockResponse)).rejects.toThrow( - AuthenticationError, - ); - }); - - it('should throw an error if the bearer token is invalid', async () => { - mockRequest.header.mockReturnValue('Basic asdf='); - - await expect(provider.refresh(mockRequest, mockResponse)).rejects.toThrow( - AuthenticationError, - ); - }); - - it('should return if auth header is set and valid', async () => { - mockRequest.header.mockReturnValue(`Bearer token`); - authHandler.mockResolvedValue({ - profile: {}, - }); - signInResolver.mockResolvedValue({ - token: mockToken, - }); - - await provider.refresh(mockRequest, mockResponse); - - expect(mockRequest.header).toHaveBeenCalledWith(OAUTH2_PROXY_JWT_HEADER); - expect(mockJwtDecode).toHaveBeenCalledWith('token'); - expect(mockResponse.json).toHaveBeenCalled(); - }); - - it('should load profile from authHandler and backstage identity from signInResolver', async () => { - const decodedToken = { - oid: 'oid', - name: 'name', - upn: 'john.doe@example.com', - }; - const profile = { displayName: 'some value' }; - mockRequest.header.mockReturnValue(`Bearer token`); - signInResolver.mockResolvedValue({ - token: mockToken, - }); - authHandler.mockResolvedValue({ profile: profile }); - mockJwtDecode.mockReturnValue(decodedToken as any); - - await provider.refresh(mockRequest, mockResponse); - - expect(signInResolver).toHaveBeenCalledWith( - { - profile: profile, - result: { - accessToken: 'token', - fullProfile: decodedToken, - getHeader: expect.any(Function), - headers: { - 'x-mock': 'mock', - }, - }, - }, - { _: 'resolver-context' }, - ); - expect(mockResponse.json).toHaveBeenCalledWith({ - backstageIdentity: { - identity: { - type: 'user', - userEntityRef: 'user:default/jimmymarkum', - ownershipEntityRefs: ['user:default/jimmymarkum'], - }, - token: mockToken, - }, - profile: { displayName: 'some value' }, - providerInfo: { - accessToken: 'token', - }, - }); - }); - }); - - describe('oauth2Proxy.create()', () => { - beforeEach(() => { - mockRequest.header.mockReturnValue(`Bearer token`); - authHandler.mockResolvedValue({ - profile: {}, - }); - signInResolver.mockResolvedValue({ - token: mockToken, - }); - }); - - it('should create a valid provider', async () => { - const factory = oauth2Proxy.create({ - authHandler, - signIn: { resolver: signInResolver }, - }); - const handler = factory({ - logger, - catalogApi: {}, - tokenIssuer: {}, - } as any); - await handler.refresh!(mockRequest, mockResponse); - - expect(mockRequest.header).toHaveBeenCalledWith(OAUTH2_PROXY_JWT_HEADER); - expect(mockJwtDecode).toHaveBeenCalledWith('token'); - expect(mockResponse.json).toHaveBeenCalled(); - }); - }); -}); diff --git a/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts b/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts index 3ac7d09000..5d75167e84 100644 --- a/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2-proxy/provider.ts @@ -14,164 +14,13 @@ * limitations under the License. */ -import express from 'express'; -import { AuthenticationError } from '@backstage/errors'; -import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; -import { - AuthHandler, - SignInResolver, - AuthProviderRouteHandlers, - AuthResponse, - AuthResolverContext, - AuthHandlerResult, -} from '../types'; -import { decodeJwt } from 'jose'; -import { prepareBackstageIdentityResponse } from '../prepareBackstageIdentityResponse'; +import { createProxyAuthProviderFactory } from '@backstage/plugin-auth-node'; +import { AuthHandler, SignInResolver } from '../types'; import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; -import { IncomingHttpHeaders } from 'http'; - -// NOTE: This may come in handy if you're doing work on this provider: -// -// plugins/auth-backend/examples/docker-compose.oauth2-proxy.yaml -// - -export const OAUTH2_PROXY_JWT_HEADER = 'X-OAUTH2-PROXY-ID-TOKEN'; - -/** - * JWT header extraction result, containing the raw value and the parsed JWT - * payload. - * - * @public - */ -export type OAuth2ProxyResult = { - /** - * The parsed payload of the `accessToken`. The token is only parsed, not verified. - * - * @deprecated Access through the `headers` instead. This will be removed in a future release. - */ - fullProfile: JWTPayload; - - /** - * The token received via the X-OAUTH2-PROXY-ID-TOKEN header. Will be an empty string - * if the header is not set. Note the this is typically an OpenID Connect token. - * - * @deprecated Access through the `headers` instead. This will be removed in a future release. - */ - accessToken: string; - - /** - * The headers of the incoming request from the OAuth2 proxy. This will include - * both the headers set by the client as well as the ones added by the OAuth2 proxy. - * You should only trust the headers that are injected by the OAuth2 proxy. - * - * Useful headers to use to complete the sign-in are for example `x-forwarded-user` - * and `x-forwarded-email`. See the OAuth2 proxy documentation for more information - * about the available headers and how to enable them. In particular it is possible - * to forward access and identity tokens, which can be user for additional verification - * and lookups. - */ - headers: IncomingHttpHeaders; - - /** - * Provides convenient access to the request headers. - * - * This call is simply forwarded to `req.get(name)`. - */ - getHeader(name: string): string | undefined; -}; - -interface Options { - resolverContext: AuthResolverContext; - signInResolver: SignInResolver>; - authHandler: AuthHandler>; -} - -export class Oauth2ProxyAuthProvider - implements AuthProviderRouteHandlers -{ - private readonly resolverContext: AuthResolverContext; - private readonly signInResolver: SignInResolver< - OAuth2ProxyResult - >; - private readonly authHandler: AuthHandler>; - - constructor(options: Options) { - this.resolverContext = options.resolverContext; - this.signInResolver = options.signInResolver; - this.authHandler = options.authHandler; - } - - frameHandler(): Promise { - return Promise.resolve(undefined); - } - - async refresh(req: express.Request, res: express.Response): Promise { - try { - // TODO(Rugvip): This parsing was deprecated in 1.2 and should be removed in a future release. - const authHeader = req.header(OAUTH2_PROXY_JWT_HEADER); - const jwt = getBearerTokenFromAuthorizationHeader(authHeader); - const decodedJWT = jwt && (decodeJwt(jwt) as unknown as JWTPayload); - - const result = { - fullProfile: decodedJWT || ({} as JWTPayload), - accessToken: jwt || '', - headers: req.headers, - getHeader(name: string) { - if (name.toLocaleLowerCase('en-US') === 'set-cookie') { - throw new Error('Access Set-Cookie via the headers object instead'); - } - return req.get(name); - }, - }; - - const response = await this.handleResult(result); - res.json(response); - } catch (e) { - throw new AuthenticationError('Refresh failed', e); - } - } - - start(): Promise { - return Promise.resolve(undefined); - } - - private async handleResult( - result: OAuth2ProxyResult, - ): Promise> { - const { profile } = await this.authHandler(result, this.resolverContext); - - const backstageSignInResult = await this.signInResolver( - { - result, - profile, - }, - this.resolverContext, - ); - - return { - providerInfo: { - accessToken: result.accessToken, - }, - backstageIdentity: prepareBackstageIdentityResponse( - backstageSignInResult, - ), - profile, - }; - } -} - -async function defaultAuthHandler( - result: OAuth2ProxyResult, -): Promise { - return { - profile: { - email: result.getHeader('x-forwarded-email'), - displayName: - result.getHeader('x-forwarded-preferred-username') || - result.getHeader('x-forwarded-user'), - }, - }; -} +import { + type OAuth2ProxyResult, + oauth2ProxyAuthenticator, +} from '@backstage/plugin-auth-backend-module-oauth2-proxy-provider'; /** * Auth provider integration for oauth2-proxy auth @@ -179,7 +28,7 @@ async function defaultAuthHandler( * @public */ export const oauth2Proxy = createAuthProviderIntegration({ - create(options: { + create(options: { /** * Configure an auth handler to generate a profile for the user. * @@ -187,7 +36,7 @@ export const oauth2Proxy = createAuthProviderIntegration({ * header as the display name, falling back to `X-Forwarded-User`, and the value of * the `X-Forwarded-Email` header as the email address. */ - authHandler?: AuthHandler>; + authHandler?: AuthHandler; /** * Configure sign-in for this provider, without it the provider can not be used to sign users in. @@ -196,17 +45,13 @@ export const oauth2Proxy = createAuthProviderIntegration({ /** * Maps an auth result to a Backstage identity for the user. */ - resolver: SignInResolver>; + resolver: SignInResolver; }; }) { - return ({ resolverContext }) => { - const signInResolver = options.signIn.resolver; - const authHandler = options.authHandler; - return new Oauth2ProxyAuthProvider({ - resolverContext, - signInResolver, - authHandler: authHandler ?? defaultAuthHandler, - }); - }; + return createProxyAuthProviderFactory({ + authenticator: oauth2ProxyAuthenticator, + profileTransform: options?.authHandler, + signInResolver: options?.signIn?.resolver, + }); }, }); diff --git a/plugins/auth-backend/src/providers/oidc/index.ts b/plugins/auth-backend/src/providers/oidc/index.ts index 6b2282411c..501f223fb3 100644 --- a/plugins/auth-backend/src/providers/oidc/index.ts +++ b/plugins/auth-backend/src/providers/oidc/index.ts @@ -15,4 +15,11 @@ */ export { oidc } from './provider'; -export type { OidcAuthResult } from './provider'; + +import { OidcAuthResult as OidcAuthResult_ } from '@backstage/plugin-auth-backend-module-oidc-provider'; + +/** + * @public + * @deprecated Use OidcAuthResult from `@backstage/plugin-auth-backend-module-oidc-provider` instead + */ +export type OidcAuthResult = OidcAuthResult_; diff --git a/plugins/auth-backend/src/providers/oidc/provider.test.ts b/plugins/auth-backend/src/providers/oidc/provider.test.ts index 7161ef1d6b..f2172a796d 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.test.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2024 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. @@ -13,177 +13,157 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -import { Config, ConfigReader } from '@backstage/config'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { getVoidLogger } from '@backstage/backend-common'; +import { LoggerService } from '@backstage/backend-plugin-api'; +import { Config, ConfigReader } from '@backstage/config'; +import { + AuthProviderConfig, + AuthResolverContext, + CookieConfigurer, +} from '@backstage/plugin-auth-node'; import express from 'express'; -import { Session } from 'express-session'; -import { UnsecuredJWT } from 'jose'; +import { JWK, SignJWT, exportJWK, generateKeyPair } from 'jose'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { ClientMetadata, IssuerMetadata } from 'openid-client'; -import { OAuthAdapter } from '../../lib/oauth'; -import { oidc, OidcAuthProvider, Options } from './provider'; -import { AuthResolverContext } from '../types'; +import { oidc } from './provider'; -const issuerMetadata = { - issuer: 'https://oidc.test', - authorization_endpoint: 'https://oidc.test/as/authorization.oauth2', - token_endpoint: 'https://oidc.test/as/token.oauth2', - revocation_endpoint: 'https://oidc.test/as/revoke_token.oauth2', - userinfo_endpoint: 'https://oidc.test/idp/userinfo.openid', - introspection_endpoint: 'https://oidc.test/as/introspect.oauth2', - jwks_uri: 'https://oidc.test/pf/JWKS', - scopes_supported: ['openid'], - claims_supported: ['email'], - response_types_supported: ['code'], - id_token_signing_alg_values_supported: ['RS256', 'RS512', 'HS256'], - token_endpoint_auth_signing_alg_values_supported: ['RS256', 'RS512', 'HS256'], - request_object_signing_alg_values_supported: ['RS256', 'RS512', 'HS256'], -}; +describe('oidc.create', () => { + const userinfo = { + sub: 'test', + iss: 'https://oidc.test', + aud: 'clientId', + nonce: 'foo', + }; + const server = setupServer(); + setupRequestMockHandlers(server); -const clientMetadata: Options = { - authHandler: async input => ({ - profile: { - displayName: input.userinfo.email, - }, - }), - resolverContext: {} as AuthResolverContext, - callbackUrl: 'https://oidc.test/callback', - clientId: 'testclientid', - clientSecret: 'testclientsecret', - metadataUrl: 'https://oidc.test/.well-known/openid-configuration', - tokenEndpointAuthMethod: 'none', - tokenSignedResponseAlg: 'none', -}; + let publicKey: JWK; + let tokenset: object; + let providerFactoryOptions: { + providerId: string; + globalConfig: AuthProviderConfig; + config: Config; + logger: LoggerService; + resolverContext: AuthResolverContext; + baseUrl: string; + appUrl: string; + isOriginAllowed: (origin: string) => boolean; + cookieConfigurer?: CookieConfigurer; + }; -describe('OidcAuthProvider', () => { - const worker = setupServer(); - setupRequestMockHandlers(worker); + beforeAll(async () => { + const keyPair = await generateKeyPair('RS256'); + const privateKey = await exportJWK(keyPair.privateKey); + publicKey = await exportJWK(keyPair.publicKey); + publicKey.alg = privateKey.alg = 'RS256'; + + tokenset = { + id_token: await new SignJWT({ + iat: Date.now(), + exp: Date.now() + 10000, + ...userinfo, + }) + .setProtectedHeader({ alg: privateKey.alg, kid: privateKey.kid }) + .sign(keyPair.privateKey), + access_token: 'accessToken', + }; + }); beforeEach(() => { - jest.clearAllMocks(); - }); - - it('hit the metadata url', async () => { - const handler = jest.fn((_req, res, ctx) => { - return res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(issuerMetadata), - ); - }); - worker.use( - rest.get('https://oidc.test/.well-known/openid-configuration', handler), + server.use( + rest.get( + 'https://oidc.test/.well-known/openid-configuration', + (_req, res, ctx) => + res( + ctx.json({ + issuer: 'https://oidc.test', + token_endpoint: 'https://oidc.test/oauth2/token', + userinfo_endpoint: 'https://oidc.test/idp/userinfo.openid', + jwks_uri: 'https://oidc.test/jwks.json', + }), + ), + ), + rest.post('https://oidc.test/oauth2/token', (_req, res, ctx) => + res(ctx.json(tokenset)), + ), + rest.get('https://oidc.test/jwks.json', async (_req, res, ctx) => + res(ctx.json({ keys: [{ ...publicKey }] })), + ), + rest.get( + 'https://oidc.test/idp/userinfo.openid', + async (_req, res, ctx) => res(ctx.json(userinfo)), + ), ); - const provider = new OidcAuthProvider(clientMetadata); - const { strategy } = (await (provider as any).implementation) as any as { - strategy: { - _client: ClientMetadata; - _issuer: IssuerMetadata; - }; - }; - // Assert that the expected request to the metadaurl was made. - expect(handler).toHaveBeenCalledTimes(1); - const { _client, _issuer } = strategy; - expect(_client.client_id).toBe(clientMetadata.clientId); - expect(_issuer.token_endpoint).toBe(issuerMetadata.token_endpoint); - }); - - it('OidcAuthProvider#handler successfully invokes the oidc endpoints', async () => { - const sub = 'alice'; - const iss = 'https://oidc.test'; - const iat = Date.now(); - const aud = clientMetadata.clientId; - const exp = Date.now() + 10000; - const jwt = await new UnsecuredJWT({ iss, sub, aud, iat, exp }) - .setIssuer(iss) - .setAudience(aud) - .setSubject(sub) - .setIssuedAt(iat) - .setExpirationTime(exp) - .encode(); - const requestSequence: Array = []; - - // The array of expected requests executed by the provider handler - const requests: Array<{ - method: 'get' | 'post'; - url: string; - payload: object; - }> = [ - { - method: 'get', - url: 'https://oidc.test/.well-known/openid-configuration', - payload: issuerMetadata, - }, - { - method: 'post', - url: 'https://oidc.test/as/token.oauth2', - payload: { - id_token: jwt, - access_token: 'test', - authorization_signed_response_alg: 'HS256', - }, - }, - { - method: 'get', - url: 'https://oidc.test/idp/userinfo.openid', - payload: { - sub: 'alice', - email: 'alice@oidc.test', - }, - }, - ]; - worker.use( - ...requests.map(r => { - return rest[r.method](r.url, (_req, res, ctx) => { - requestSequence.push(r.url); - return res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(r.payload), - ); - }); - }), - ); - const provider = new OidcAuthProvider(clientMetadata); - const req = { - method: 'GET', - url: 'https://oidc.test/?code=test2', - session: { 'oidc:oidc.test': 'test' } as any as Session, - } as express.Request; - await provider.handler(req); - expect(requestSequence).toEqual([0, 1, 2].map(i => requests[i].url)); - }); - - it('oidc.create', async () => { - const handler = jest.fn((_req, res, ctx) => { - return res( - ctx.status(200), - ctx.set('Content-Type', 'application/json'), - ctx.json(issuerMetadata), - ); - }); - worker.use( - rest.get('https://oidc.test/.well-known/openid-configuration', handler), - ); - const config: Config = new ConfigReader({ - testEnv: { - ...clientMetadata, - metadataUrl: 'https://oidc.test/.well-known/openid-configuration', - }, - } as any); - const provider = oidc.create()({ + providerFactoryOptions = { + providerId: 'myoidc', + baseUrl: 'http://backstage.test/api/auth', + appUrl: 'http://backstage.test', + isOriginAllowed: _ => true, globalConfig: { - appUrl: 'https://oidc.test', - baseUrl: 'https://oidc.test', + baseUrl: 'http://backstage.test/api/auth', + appUrl: 'http://backstage.test', + isOriginAllowed: _ => true, }, - config, - } as any) as OAuthAdapter; - expect(provider.start).toBeDefined(); - // Cast provider as any here to be able to inspect private members - await (provider as any).handlers.get('testEnv').handlers.implementation; - // Assert that the expected request to the metadaurl was made. - expect(handler).toHaveBeenCalledTimes(1); + config: new ConfigReader({ + development: { + metadataUrl: 'https://oidc.test/.well-known/openid-configuration', + clientId: 'clientId', + clientSecret: 'clientSecret', + }, + }), + logger: getVoidLogger(), + resolverContext: { + issueToken: jest.fn(), + findCatalogUser: jest.fn(), + signInWithCatalogUser: jest.fn(), + }, + }; + }); + + it('invokes authHandler with tokenset and userinfo response', async () => { + const authHandler = jest.fn(); + const provider = oidc.create({ authHandler })(providerFactoryOptions); + const state = Buffer.from('nonce=foo&env=development').toString('hex'); + + await provider.frameHandler( + { + method: 'GET', + url: `http://backstage.test/api/auth/myoidc/handler/frame?code=blahblah&state=${state}`, + query: { state }, + cookies: { 'myoidc-nonce': 'foo' }, + session: { 'oidc:oidc.test': { state, nonce: 'foo' } }, + } as unknown as express.Request, + { setHeader: jest.fn(), end: jest.fn() } as unknown as express.Response, + ); + + expect(authHandler).toHaveBeenCalledWith( + { tokenset, userinfo }, + providerFactoryOptions.resolverContext, + ); + }); + + it('invokes sign-in resolver with tokenset and userinfo response', async () => { + const resolver = jest.fn(); + const provider = oidc.create({ signIn: { resolver } })( + providerFactoryOptions, + ); + const state = Buffer.from('nonce=foo&env=development').toString('hex'); + + await provider.frameHandler( + { + method: 'GET', + url: `http://backstage.test/api/auth/myoidc/handler/frame?code=blahblah&state=${state}`, + query: { state }, + cookies: { 'myoidc-nonce': 'foo' }, + session: { 'oidc:oidc.test': { state, nonce: 'foo' } }, + } as unknown as express.Request, + { setHeader: jest.fn(), end: jest.fn() } as unknown as express.Response, + ); + + expect(resolver).toHaveBeenCalledWith( + expect.objectContaining({ result: { tokenset, userinfo } }), + providerFactoryOptions.resolverContext, + ); }); }); diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts index b6608aebbd..9bc78c48d2 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -14,208 +14,23 @@ * limitations under the License. */ -import express from 'express'; -import { - Client, - ClientAuthMethod, - Issuer, - Strategy as OidcStrategy, - TokenSet, - UserinfoResponse, -} from 'openid-client'; -import { - encodeState, - OAuthAdapter, - OAuthEnvironmentHandler, - OAuthHandlers, - OAuthProviderOptions, - OAuthRefreshRequest, - OAuthResponse, - OAuthStartRequest, -} from '../../lib/oauth'; -import { - executeFrameHandlerStrategy, - executeRedirectStrategy, - PassportDoneCallback, -} from '../../lib/passport'; -import { - AuthHandler, - AuthResolverContext, - OAuthStartResponse, - SignInResolver, -} from '../types'; +import { AuthHandler, SignInResolver } from '../types'; import { createAuthProviderIntegration } from '../createAuthProviderIntegration'; +import { + createOAuthProviderFactory, + AuthResolverContext, + BackstageSignInResult, + OAuthAuthenticatorResult, + SignInInfo, +} from '@backstage/plugin-auth-node'; +import { + oidcAuthenticator, + OidcAuthResult, +} from '@backstage/plugin-auth-backend-module-oidc-provider'; import { commonByEmailLocalPartResolver, commonByEmailResolver, } from '../resolvers'; -import { BACKSTAGE_SESSION_EXPIRATION } from '../../lib/session'; - -type PrivateInfo = { - refreshToken?: string; -}; - -type OidcImpl = { - strategy: OidcStrategy; - client: Client; -}; - -/** - * authentication result for the OIDC which includes the token set and user information (a profile response sent by OIDC server) - * @public - */ -export type OidcAuthResult = { - tokenset: TokenSet; - userinfo: UserinfoResponse; -}; - -export type Options = OAuthProviderOptions & { - metadataUrl: string; - scope?: string; - prompt?: string; - tokenEndpointAuthMethod?: ClientAuthMethod; - tokenSignedResponseAlg?: string; - signInResolver?: SignInResolver; - authHandler: AuthHandler; - resolverContext: AuthResolverContext; -}; - -export class OidcAuthProvider implements OAuthHandlers { - private readonly implementation: Promise; - private readonly scope?: string; - private readonly prompt?: string; - - private readonly signInResolver?: SignInResolver; - private readonly authHandler: AuthHandler; - private readonly resolverContext: AuthResolverContext; - - constructor(options: Options) { - this.implementation = this.setupStrategy(options); - this.scope = options.scope; - this.prompt = options.prompt; - this.signInResolver = options.signInResolver; - this.authHandler = options.authHandler; - this.resolverContext = options.resolverContext; - } - - async start(req: OAuthStartRequest): Promise { - const { strategy } = await this.implementation; - const options: Record = { - scope: req.scope || this.scope || 'openid profile email', - state: encodeState(req.state), - }; - const prompt = this.prompt || 'none'; - if (prompt !== 'auto') { - options.prompt = prompt; - } - return await executeRedirectStrategy(req, strategy, options); - } - - async handler(req: express.Request) { - const { strategy } = await this.implementation; - const { result, privateInfo } = await executeFrameHandlerStrategy< - OidcAuthResult, - PrivateInfo - >(req, strategy); - - return { - response: await this.handleResult(result), - refreshToken: privateInfo.refreshToken, - }; - } - - async refresh(req: OAuthRefreshRequest) { - const { client } = await this.implementation; - const tokenset = await client.refresh(req.refreshToken); - if (!tokenset.access_token) { - throw new Error('Refresh failed'); - } - if (!tokenset.scope) { - tokenset.scope = req.scope; - } - const userinfo = await client.userinfo(tokenset.access_token); - - return { - response: await this.handleResult({ tokenset, userinfo }), - refreshToken: tokenset.refresh_token, - }; - } - - private async setupStrategy(options: Options): Promise { - const issuer = await Issuer.discover(options.metadataUrl); - const client = new issuer.Client({ - access_type: 'offline', // this option must be passed to provider to receive a refresh token - client_id: options.clientId, - client_secret: options.clientSecret, - redirect_uris: [options.callbackUrl], - response_types: ['code'], - token_endpoint_auth_method: - options.tokenEndpointAuthMethod || 'client_secret_basic', - id_token_signed_response_alg: options.tokenSignedResponseAlg || 'RS256', - scope: options.scope || '', - }); - - const strategy = new OidcStrategy( - { - client, - passReqToCallback: false, - }, - ( - tokenset: TokenSet, - userinfo: UserinfoResponse, - done: PassportDoneCallback, - ) => { - if (typeof done !== 'function') { - throw new Error( - 'OIDC IdP must provide a userinfo_endpoint in the metadata response', - ); - } - done( - undefined, - { tokenset, userinfo }, - { - refreshToken: tokenset.refresh_token, - }, - ); - }, - ); - strategy.error = console.error; - return { strategy, client }; - } - - // Use this function to grab the user profile info from the token - // Then populate the profile with it - private async handleResult(result: OidcAuthResult): Promise { - const { profile } = await this.authHandler(result, this.resolverContext); - - const expiresInSeconds = - result.tokenset.expires_in === undefined - ? BACKSTAGE_SESSION_EXPIRATION - : Math.min(result.tokenset.expires_in, BACKSTAGE_SESSION_EXPIRATION); - - let backstageIdentity = undefined; - if (this.signInResolver) { - backstageIdentity = await this.signInResolver( - { - result, - profile, - }, - this.resolverContext, - ); - } - - return { - backstageIdentity, - providerInfo: { - idToken: result.tokenset.id_token, - accessToken: result.tokenset.access_token!, - scope: result.tokenset.scope!, - expiresInSeconds, - }, - profile, - }; - } -} /** * Auth provider integration for generic OpenID Connect auth @@ -224,59 +39,44 @@ export class OidcAuthProvider implements OAuthHandlers { */ export const oidc = createAuthProviderIntegration({ create(options?: { + /** + * The profile transformation function used to verify and convert the auth response + * into the profile that will be presented to the user. + */ authHandler?: AuthHandler; + /** + * Configure sign-in for this provider; convert user profile respones into + * Backstage identities. + */ signIn?: { resolver: SignInResolver; }; }) { - return ({ providerId, globalConfig, config, resolverContext }) => - OAuthEnvironmentHandler.mapConfig(config, envConfig => { - const clientId = envConfig.getString('clientId'); - const clientSecret = envConfig.getString('clientSecret'); - const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); - const callbackUrl = - customCallbackUrl || - `${globalConfig.baseUrl}/${providerId}/handler/frame`; - const metadataUrl = envConfig.getString('metadataUrl'); - const tokenEndpointAuthMethod = envConfig.getOptionalString( - 'tokenEndpointAuthMethod', - ) as ClientAuthMethod; - const tokenSignedResponseAlg = envConfig.getOptionalString( - 'tokenSignedResponseAlg', - ); - const scope = envConfig.getOptionalString('scope'); - const prompt = envConfig.getOptionalString('prompt'); - - const authHandler: AuthHandler = options?.authHandler - ? options.authHandler - : async ({ userinfo }) => ({ - profile: { - displayName: userinfo.name, - email: userinfo.email, - picture: userinfo.picture, - }, - }); - - const provider = new OidcAuthProvider({ - clientId, - clientSecret, - callbackUrl, - tokenEndpointAuthMethod, - tokenSignedResponseAlg, - metadataUrl, - scope, - prompt, - signInResolver: options?.signIn?.resolver, - authHandler, - resolverContext, - }); - - return OAuthAdapter.fromConfig(globalConfig, provider, { - providerId, - callbackUrl, - }); - }); + const authHandler = options?.authHandler; + const signInResolver = options?.signIn?.resolver; + return createOAuthProviderFactory({ + authenticator: oidcAuthenticator, + profileTransform: + authHandler && + (( + result: OAuthAuthenticatorResult, + context: AuthResolverContext, + ) => authHandler(result.fullProfile, context)), + signInResolver: + signInResolver && + (( + info: SignInInfo>, + context: AuthResolverContext, + ): Promise => + signInResolver( + { + result: info.result.fullProfile, + profile: info.profile, + }, + context, + )), + }); }, resolvers: { /** diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 4c8d5c37d7..669914e7fc 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -24,6 +24,10 @@ import { adaptLegacyOAuthSignInResolver, } from '../../lib/legacy'; import { oktaAuthenticator } from '@backstage/plugin-auth-backend-module-okta-provider'; +import { + commonByEmailLocalPartResolver, + commonByEmailResolver, +} from '../resolvers'; /** * Auth provider integration for Okta auth @@ -51,6 +55,17 @@ export const okta = createAuthProviderIntegration({ }); }, resolvers: { + /** + * Looks up the user by matching their email local part to the entity name. + */ + emailLocalPartMatchingUserEntityName: () => commonByEmailLocalPartResolver, + /** + * Looks up the user by matching their email to the entity email. + */ + emailMatchingUserEntityProfileEmail: () => commonByEmailResolver, + /** + * Looks up the user by matching their email to the `okta.com/email` annotation. + */ emailMatchingUserEntityAnnotation(): SignInResolver { return async (info, ctx) => { const { profile } = info; diff --git a/plugins/auth-backend/src/setupTests.ts b/plugins/auth-backend/src/setupTests.ts index d3232290a7..f7c56ef27d 100644 --- a/plugins/auth-backend/src/setupTests.ts +++ b/plugins/auth-backend/src/setupTests.ts @@ -14,4 +14,10 @@ * limitations under the License. */ +import { TestDatabases } from '@backstage/backend-test-utils'; + export {}; + +TestDatabases.setDefaults({ + ids: ['MYSQL_8', 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'], +}); diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index d585f8d1d4..97b69b0618 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,120 @@ # @backstage/plugin-auth-node +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.2-next.2 + +### Patch Changes + +- a62764b: Updated dependency `passport` to `^0.7.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + ## 0.4.2-next.0 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 5a18593ab6..0af59ecc53 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.4.2-next.0", + "version": "0.4.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -41,9 +41,9 @@ "jose": "^4.6.0", "lodash": "^4.17.21", "node-fetch": "^2.6.7", - "passport": "^0.6.0", + "passport": "^0.7.0", "winston": "^3.2.1", - "zod": "^3.21.4", + "zod": "^3.22.4", "zod-to-json-schema": "^3.21.4" }, "devDependencies": { diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md index 30b573b43f..736fa6276b 100644 --- a/plugins/azure-devops-backend/CHANGELOG.md +++ b/plugins/azure-devops-backend/CHANGELOG.md @@ -1,5 +1,149 @@ # @backstage/plugin-azure-devops-backend +## 0.5.2-next.0 + +### Patch Changes + +- 353244d: Added a note about Service Principles +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-azure-devops-common@0.3.2 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.5.1 + +### Patch Changes + +- d076ee4: Updated dependency `azure-devops-node-api` to `^12.0.0`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-azure-devops-common@0.3.2 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.5.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + +## 0.5.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-azure-devops-common@0.3.2 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.5.1-next.0 + +### Patch Changes + +- d076ee4: Updated dependency `azure-devops-node-api` to `^12.0.0`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-azure-devops-common@0.3.2 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.5.0 + +### Minor Changes + +- 844969c: **BREAKING** New `fromConfig` static method must be used now when creating an instance of the `AzureDevOpsApi` + + Added support for using the `AzureDevOpsCredentialsProvider` + +### Patch Changes + +- c70e4f5: Added multi-org support +- 646db72: Updated encoding of Org to use `encodeURIComponent` when building URL used to get credentials from credential provider +- 043b724: Introduced new `AzureDevOpsAnnotatorProcessor` that adds the needed annotations automatically. Also, moved constants to common package so they can be shared more easily +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-azure-devops-common@0.3.2 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.5.0-next.3 + +### Patch Changes + +- c70e4f5: Added multi-org support +- 646db72: Updated encoding of Org to use `encodeURIComponent` when building URL used to get credentials from credential provider +- Updated dependencies + - @backstage/plugin-azure-devops-common@0.3.2-next.1 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-azure-devops-common@0.3.2-next.0 + - @backstage/plugin-catalog-common@1.0.18 + +## 0.5.0-next.1 + +### Minor Changes + +- 844969cd97: **BREAKING** New `fromConfig` static method must be used now when creating an instance of the `AzureDevOpsApi` + + Added support for using the `AzureDevOpsCredentialsProvider` + +### Patch Changes + +- 043b724c56: Introduced new `AzureDevOpsAnnotatorProcessor` that adds the needed annotations automatically. Also, moved constants to common package so they can be shared more easily +- Updated dependencies + - @backstage/plugin-azure-devops-common@0.3.2-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.4.5-next.0 ### Patch Changes diff --git a/plugins/azure-devops-backend/README.md b/plugins/azure-devops-backend/README.md index e892b84dd1..0066e43856 100644 --- a/plugins/azure-devops-backend/README.md +++ b/plugins/azure-devops-backend/README.md @@ -8,7 +8,7 @@ The following sections will help you get the Azure DevOps Backend plugin setup a ### Configuration -The Azure DevOps plugin requires the following YAML to be added to your app-config.yaml: +The Azure DevOps plugin requires the following YAML to be added to your `app-config.yaml`: ```yaml azureDevOps: @@ -23,6 +23,12 @@ Configuration Details: - `AZURE_TOKEN` environment variable must be set to a [Personal Access Token](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page) with read access to both Code and Build - `organization` is your Azure DevOps Services (cloud) Organization name or for Azure DevOps Server (on-premise) this will be your Collection name +#### Multi Organization & Service Principals + +To support cases where you have multiple Azure DevOps organizations and/or you want to use a Service Principal you will want to make sure to configure them in the `integrations.azure` section of your `app-config.yaml` as detailed in the [Azure DevOps Locations](https://backstage.io/docs/integrations/azure/locations) documentation. + +**Note:** You will still need to define the [configuration above](#configuration). + ### Up and Running Here's how to get the backend up and running: @@ -89,6 +95,62 @@ In your `packages/backend/src/index.ts` make the following changes: backend.start(); ``` +## Processor + +The Azure DevOps backend plugin includes the `AzureDevOpsAnnotatorProcessor` which will automatically add the needed annotations for you. Here's how to install it: + +```diff + import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; + import { ScaffolderEntitiesProcessor } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; ++ import { AzureDevOpsAnnotatorProcessor } from '@backstage/plugin-azure-devops-backend'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const builder = await CatalogBuilder.create(env); + builder.addProcessor(new ScaffolderEntitiesProcessor()); ++ builder.addProcessor(AzureDevOpsAnnotatorProcessor.fromConfig(env.config)); + const { processingEngine, router } = await builder.build(); + await processingEngine.start(); + return router; + } +``` + +To use this with the New Backend System you'll want to create a [backend module extension for the Catalog](https://backstage.io/docs/backend-system/building-backends/migrating#other-catalog-extensions) if you haven't already. Here's a basic example of this assuming you are only adding the `AzureDevOpsAnnotatorProcessor`, this would go in your `packages/backend/index.ts`: + +```diff + import { createBackend } from '@backstage/backend-defaults'; ++ import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; ++ import { coreServices, createBackendModule } from '@backstage/backend-plugin-api'; ++ import { AzureDevOpsAnnotatorProcessor } from '@backstage/plugin-azure-devops-backend'; + ++ const catalogModuleCustomExtensions = createBackendModule({ ++ pluginId: 'catalog', // name of the plugin that the module is targeting ++ moduleId: 'custom-extensions', ++ register(env) { ++ env.registerInit({ ++ deps: { ++ catalog: catalogProcessingExtensionPoint, ++ config: coreServices.rootConfig, ++ }, ++ async init({ catalog, config }) { ++ catalog.addProcessor(AzureDevOpsAnnotatorProcessor.fromConfig(config)); ++ }, ++ }); ++ }, ++ }); + + const backend = createBackend(); + + // ... other feature additions + ++ backend.add(catalogModuleCustomExtensions()); + + backend.start(); +``` + ## Links - [Frontend part of the plugin](https://github.com/backstage/backstage/tree/master/plugins/azure-devops) diff --git a/plugins/azure-devops-backend/api-report.md b/plugins/azure-devops-backend/api-report.md index 8c916a1495..6dead7f11b 100644 --- a/plugins/azure-devops-backend/api-report.md +++ b/plugins/azure-devops-backend/api-report.md @@ -7,36 +7,69 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; import { Build } from 'azure-devops-node-api/interfaces/BuildInterfaces'; import { BuildDefinitionReference } from 'azure-devops-node-api/interfaces/BuildInterfaces'; import { BuildRun } from '@backstage/plugin-azure-devops-common'; +import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common'; +import { Entity } from '@backstage/catalog-model'; import express from 'express'; import { GitRepository } from 'azure-devops-node-api/interfaces/GitInterfaces'; import { GitTag } from '@backstage/plugin-azure-devops-common'; +import { LocationSpec } from '@backstage/plugin-catalog-common'; import { Logger } from 'winston'; import { Project } from '@backstage/plugin-azure-devops-common'; import { PullRequest } from '@backstage/plugin-azure-devops-common'; import { PullRequestOptions } from '@backstage/plugin-azure-devops-common'; import { RepoBuild } from '@backstage/plugin-azure-devops-common'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import { Team } from '@backstage/plugin-azure-devops-common'; import { TeamMember } from '@backstage/plugin-azure-devops-common'; import { UrlReader } from '@backstage/backend-common'; -import { WebApi } from 'azure-devops-node-api'; + +// @public (undocumented) +export class AzureDevOpsAnnotatorProcessor implements CatalogProcessor { + constructor(opts: { + scmIntegrationRegistry: ScmIntegrationRegistry; + kinds?: string[]; + }); + // (undocumented) + static fromConfig( + config: Config, + options?: { + kinds?: string[]; + }, + ): AzureDevOpsAnnotatorProcessor; + // (undocumented) + getProcessorName(): string; + // (undocumented) + preProcessEntity(entity: Entity, location: LocationSpec): Promise; +} // @public (undocumented) export class AzureDevOpsApi { - constructor(logger: Logger, webApi: WebApi, urlReader: UrlReader); + // (undocumented) + static fromConfig( + config: Config, + options: { + logger: Logger; + urlReader: UrlReader; + }, + ): AzureDevOpsApi; // (undocumented) getAllTeams(): Promise; // (undocumented) getBuildDefinitions( projectName: string, definitionName: string, + host?: string, + org?: string, ): Promise; // (undocumented) getBuildList( projectName: string, repoId: string, top: number, + host?: string, + org?: string, ): Promise; // (undocumented) getBuildRuns( @@ -44,6 +77,8 @@ export class AzureDevOpsApi { top: number, repoName?: string, definitionName?: string, + host?: string, + org?: string, ): Promise; // (undocumented) getBuilds( @@ -51,6 +86,8 @@ export class AzureDevOpsApi { top: number, repoId?: string, definitions?: number[], + host?: string, + org?: string, ): Promise; // (undocumented) getDashboardPullRequests( @@ -61,16 +98,25 @@ export class AzureDevOpsApi { getGitRepository( projectName: string, repoName: string, + host?: string, + org?: string, ): Promise; // (undocumented) - getGitTags(projectName: string, repoName: string): Promise; + getGitTags( + projectName: string, + repoName: string, + host?: string, + org?: string, + ): Promise; // (undocumented) - getProjects(): Promise; + getProjects(host?: string, org?: string): Promise; // (undocumented) getPullRequests( projectName: string, repoName: string, options: PullRequestOptions, + host?: string, + org?: string, ): Promise; // (undocumented) getReadme( @@ -87,6 +133,8 @@ export class AzureDevOpsApi { projectName: string, repoName: string, top: number, + host?: string, + org?: string, ): Promise; // (undocumented) getTeamMembers(options: { diff --git a/plugins/azure-devops-backend/config.d.ts b/plugins/azure-devops-backend/config.d.ts index 433e4df22f..86fefeeef9 100644 --- a/plugins/azure-devops-backend/config.d.ts +++ b/plugins/azure-devops-backend/config.d.ts @@ -15,7 +15,9 @@ */ export interface Config { - /** Configuration options for the azure-devops-backend plugin */ + /** + * Configuration options for the azure-devops-backend plugin + */ azureDevOps: { /** * The hostname of the given Azure instance diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index 5bf6b9b39f..7b210e5759 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops-backend", - "version": "0.4.5-next.0", + "version": "0.5.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,12 +30,17 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", + "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/integration": "workspace:^", "@backstage/plugin-azure-devops-common": "workspace:^", + "@backstage/plugin-catalog-common": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@types/express": "^4.17.6", - "azure-devops-node-api": "^11.0.1", + "azure-devops-node-api": "^12.0.0", "express": "^4.17.1", "express-promise-router": "^4.1.0", + "lodash": "^4.17.21", "mime-types": "^2.1.27", "p-limit": "^3.1.0", "winston": "^3.2.1", @@ -44,7 +49,6 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.8", - "msw": "^1.0.0", "supertest": "^6.1.6" }, "files": [ diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts index dade60f1ed..174c5ff6cd 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Backstage Authors + * 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. @@ -14,560 +14,751 @@ * limitations under the License. */ +jest.mock('azure-devops-node-api', () => ({ + WebApi: jest.fn(), + getHandlerFromToken: jest.fn().mockReturnValue(() => {}), +})); + +import { UrlReader, getVoidLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { AzureDevOpsApi } from './AzureDevOpsApi'; +import { WebApi } from 'azure-devops-node-api'; +import { TeamProjectReference } from 'azure-devops-node-api/interfaces/CoreInterfaces'; +import { GitRepository } from 'azure-devops-node-api/interfaces/TfvcInterfaces'; import { Build, - DefinitionReference, -} from 'azure-devops-node-api/interfaces/BuildInterfaces'; -import { BuildResult, BuildStatus, - GitTag, - PullRequest, - PullRequestStatus, - RepoBuild, -} from '@backstage/plugin-azure-devops-common'; +} from 'azure-devops-node-api/interfaces/BuildInterfaces'; import { GitPullRequest, GitRef, - GitRepository, + PullRequestStatus, } from 'azure-devops-node-api/interfaces/GitInterfaces'; -import { - mappedBuildRun, - mappedGitTag, - mappedPullRequest, - mappedRepoBuild, -} from './AzureDevOpsApi'; - -import { IdentityRef } from 'azure-devops-node-api/interfaces/common/VSSInterfaces'; +import { PullRequestOptions } from '@backstage/plugin-azure-devops-common'; describe('AzureDevOpsApi', () => { - describe('mappedRepoBuild', () => { - describe('mappedRepoBuild happy path', () => { - it('should return RepoBuild from Build', () => { - const inputBuildDefinition: DefinitionReference = { - name: 'My Build Definition', - }; - - const inputLinks: any = { - web: { - href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - }, - }; - - const inputIdentityRef: IdentityRef = { - displayName: 'Jane Doe', - uniqueName: 'DOMAIN\\jdoe', - }; - - const inputBuild: Build = { - id: 1, - buildNumber: 'Build-1', - status: BuildStatus.Completed, - result: BuildResult.Succeeded, - queueTime: new Date('2020-09-12T06:10:23.932Z'), - startTime: new Date('2020-09-12T06:15:23.932Z'), - finishTime: new Date('2020-09-12T06:20:23.932Z'), - sourceBranch: 'refs/heads/develop', - sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', - definition: inputBuildDefinition, - _links: inputLinks, - requestedFor: inputIdentityRef, - }; - - const outputRepoBuild: RepoBuild = { - id: 1, - title: 'My Build Definition - Build-1', - link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - status: BuildStatus.Completed, - result: BuildResult.Succeeded, - queueTime: '2020-09-12T06:10:23.932Z', - startTime: '2020-09-12T06:15:23.932Z', - finishTime: '2020-09-12T06:20:23.932Z', - source: 'refs/heads/develop (f4f78b31)', - uniqueName: 'DOMAIN\\jdoe', - }; - - expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild); - }); - }); - - describe('mappedRepoBuild with no Build definition name', () => { - it('should return RepoBuild with only Build Number for title', () => { - const inputLinks: any = { - web: { - href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - }, - }; - - const inputIdentityRef: IdentityRef = { - displayName: 'Jane Doe', - uniqueName: 'DOMAIN\\jdoe', - }; - - const inputBuild: Build = { - id: 1, - buildNumber: 'Build-1', - status: BuildStatus.Completed, - result: BuildResult.Succeeded, - queueTime: new Date('2020-09-12T06:10:23.932Z'), - startTime: new Date('2020-09-12T06:15:23.932Z'), - finishTime: new Date('2020-09-12T06:20:23.932Z'), - sourceBranch: 'refs/heads/develop', - sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', - definition: undefined, - _links: inputLinks, - requestedFor: inputIdentityRef, - }; - - const outputRepoBuild: RepoBuild = { - id: 1, - title: 'Build-1', - link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - status: BuildStatus.Completed, - result: BuildResult.Succeeded, - queueTime: '2020-09-12T06:10:23.932Z', - startTime: '2020-09-12T06:15:23.932Z', - finishTime: '2020-09-12T06:20:23.932Z', - source: 'refs/heads/develop (f4f78b31)', - uniqueName: 'DOMAIN\\jdoe', - }; - - expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild); - }); - }); - - describe('mappedRepoBuild with undefined status', () => { - it('should return BuildStatus of None for status', () => { - const inputLinks: any = { - web: { - href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - }, - }; - - const inputIdentityRef: IdentityRef = { - displayName: 'Jane Doe', - uniqueName: 'DOMAIN\\jdoe', - }; - - const inputBuild: Build = { - id: 1, - buildNumber: 'Build-1', - status: undefined, - result: BuildResult.Succeeded, - queueTime: new Date('2020-09-12T06:10:23.932Z'), - startTime: new Date('2020-09-12T06:15:23.932Z'), - finishTime: new Date('2020-09-12T06:20:23.932Z'), - sourceBranch: 'refs/heads/develop', - sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', - definition: undefined, - _links: inputLinks, - requestedFor: inputIdentityRef, - }; - - const outputRepoBuild: RepoBuild = { - id: 1, - title: 'Build-1', - link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - status: BuildStatus.None, - result: BuildResult.Succeeded, - queueTime: '2020-09-12T06:10:23.932Z', - startTime: '2020-09-12T06:15:23.932Z', - finishTime: '2020-09-12T06:20:23.932Z', - source: 'refs/heads/develop (f4f78b31)', - uniqueName: 'DOMAIN\\jdoe', - }; - - expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild); - }); - }); - - describe('mappedRepoBuild with undefined result', () => { - it('should return BuildResult of None for result', () => { - const inputLinks: any = { - web: { - href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - }, - }; - - const inputIdentityRef: IdentityRef = { - displayName: 'Jane Doe', - uniqueName: 'DOMAIN\\jdoe', - }; - - const inputBuild: Build = { - id: 1, - buildNumber: 'Build-1', - status: BuildStatus.InProgress, - result: undefined, - queueTime: new Date('2020-09-12T06:10:23.932Z'), - startTime: new Date('2020-09-12T06:15:23.932Z'), - finishTime: new Date('2020-09-12T06:20:23.932Z'), - sourceBranch: 'refs/heads/develop', - sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', - definition: undefined, - _links: inputLinks, - requestedFor: inputIdentityRef, - }; - - const outputRepoBuild: RepoBuild = { - id: 1, - title: 'Build-1', - link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - status: BuildStatus.InProgress, - result: BuildResult.None, - queueTime: '2020-09-12T06:10:23.932Z', - startTime: '2020-09-12T06:15:23.932Z', - finishTime: '2020-09-12T06:20:23.932Z', - source: 'refs/heads/develop (f4f78b31)', - uniqueName: 'DOMAIN\\jdoe', - }; - - expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild); - }); - }); - - describe('mappedRepoBuild with undefined link', () => { - it('should return empty string for link', () => { - const inputIdentityRef: IdentityRef = { - displayName: 'Jane Doe', - uniqueName: 'DOMAIN\\jdoe', - }; - - const inputBuild: Build = { - id: 1, - buildNumber: 'Build-1', - status: BuildStatus.InProgress, - result: undefined, - queueTime: new Date('2020-09-12T06:10:23.932Z'), - startTime: new Date('2020-09-12T06:15:23.932Z'), - finishTime: new Date('2020-09-12T06:20:23.932Z'), - sourceBranch: 'refs/heads/develop', - sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', - definition: undefined, - _links: undefined, - requestedFor: inputIdentityRef, - }; - - const outputRepoBuild: RepoBuild = { - id: 1, - title: 'Build-1', - link: '', - status: BuildStatus.InProgress, - result: BuildResult.None, - queueTime: '2020-09-12T06:10:23.932Z', - startTime: '2020-09-12T06:15:23.932Z', - finishTime: '2020-09-12T06:20:23.932Z', - source: 'refs/heads/develop (f4f78b31)', - uniqueName: 'DOMAIN\\jdoe', - }; - - expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild); - }); - }); + beforeEach(() => { + jest.clearAllMocks(); }); - describe('mappedGitTag', () => { - describe('mappedGitTag happy path', () => { - it('should return GitTag from GitRef', () => { - const inputIdentityRef: IdentityRef = { - displayName: 'Jane Doe', - }; - const inputGitRef: GitRef = { - name: 'refs/tags/v1.1.2', - creator: inputIdentityRef, - objectId: '1111aaaa2222bbbb3333cccc4444dddd5555eeee', - peeledObjectId: '1234567890abcdef1234567890abcdef12345678', - }; - const inputLinkBaseUrl = - 'https://host.com/myOrg/_git/super-feature-repo?version=GT'; - const inputCommitBaseUrl = - 'https://host.com/myOrg/_git/super-feature-repo/commit'; - const outputGitTag: GitTag = { - name: 'v1.1.2', - createdBy: 'Jane Doe', - commitLink: - 'https://host.com/myOrg/_git/super-feature-repo/commit/1234567890abcdef1234567890abcdef12345678', - objectId: '1111aaaa2222bbbb3333cccc4444dddd5555eeee', - peeledObjectId: '1234567890abcdef1234567890abcdef12345678', - link: 'https://host.com/myOrg/_git/super-feature-repo?version=GTv1.1.2', - }; - expect( - mappedGitTag(inputGitRef, inputLinkBaseUrl, inputCommitBaseUrl), - ).toEqual(outputGitTag); - }); - }); + const mockConfig = new ConfigReader({ + azureDevOps: { + host: 'dev.azure.com', + token: 'token', + organization: 'org', + }, + integrations: { + azure: [ + { + host: 'dev.azure.com', + credentials: [ + { + personalAccessToken: 'pat', + }, + ], + }, + ], + }, }); - describe('mappedPullRequest', () => { - describe('mappedPullRequest happy path', () => { - it('should return PullRequest from GitPullRequest', () => { - const inputGitRepository: GitRepository = { - name: 'super-feature-repo', - }; + const mockLogger = getVoidLogger(); - const inputIdentityRef: IdentityRef = { - displayName: 'Jane Doe', - uniqueName: 'DOMAIN\\jdoe', - }; + const mockUrlReader: UrlReader = { + readUrl: url => + Promise.resolve({ + buffer: async () => Buffer.from(url), + etag: 'buffer', + stream: jest.fn(), + }), + readTree: jest.fn(), + search: jest.fn(), + }; - const inputPullRequest: GitPullRequest = { - pullRequestId: 7181, - repository: inputGitRepository, - title: 'My Awesome New Feature', - createdBy: inputIdentityRef, - creationDate: new Date('2020-09-12T06:10:23.932Z'), - sourceRefName: 'refs/heads/topic/super-awesome-feature', - targetRefName: 'refs/heads/main', - status: PullRequestStatus.Active, - isDraft: false, - }; + it('should get projects', async () => { + const mockProjects: TeamProjectReference[] = [ + { + id: 'one', + name: 'one', + description: 'one', + }, + { + id: 'two', + name: 'two', + description: 'two', + }, + { + id: 'three', + name: 'three', + description: 'three', + }, + ]; - const inputBaseUrl = - 'https://host.com/myOrg/_git/super-feature-repo/pullrequest'; + const mockCoreApiClient = { + getProjects: jest.fn().mockResolvedValue(mockProjects), + }; - const outputPullRequest: PullRequest = { - pullRequestId: 7181, - repoName: 'super-feature-repo', - title: 'My Awesome New Feature', - uniqueName: 'DOMAIN\\jdoe', - createdBy: 'Jane Doe', - creationDate: '2020-09-12T06:10:23.932Z', - sourceRefName: 'refs/heads/topic/super-awesome-feature', - targetRefName: 'refs/heads/main', - status: PullRequestStatus.Active, - isDraft: false, - link: 'https://host.com/myOrg/_git/super-feature-repo/pullrequest/7181', - }; + const mockCoreApi = { + getCoreApi: jest.fn().mockResolvedValue(mockCoreApiClient), + }; - expect(mappedPullRequest(inputPullRequest, inputBaseUrl)).toEqual( - outputPullRequest, - ); - }); + (WebApi as unknown as jest.Mock).mockImplementation(() => mockCoreApi); + + const api = AzureDevOpsApi.fromConfig(mockConfig, { + logger: mockLogger, + urlReader: mockUrlReader, }); + + const result = await api.getProjects(); + + expect(result).toEqual([ + { + id: 'one', + name: 'one', + description: 'one', + }, + { + id: 'three', + name: 'three', + description: 'three', + }, + { + id: 'two', + name: 'two', + description: 'two', + }, + ]); }); - describe('mappedBuildRun', () => { - describe('mappedBuildRun happy path', () => { - it('should return RepoBuild from Build', () => { - const inputBuildDefinition: DefinitionReference = { - name: 'My Build Definition', - }; + it('should get git repository', async () => { + const mockGitRepository: GitRepository = { + id: 'repo', + }; - const inputLinks: any = { - web: { - href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - }, - }; + const mockGitClient = { + getRepository: jest.fn().mockResolvedValue(mockGitRepository), + }; + const mockGitApi = { + getGitApi: jest.fn().mockReturnValue(mockGitClient), + }; - const inputIdentityRef: IdentityRef = { - displayName: 'Jane Doe', - uniqueName: 'DOMAIN\\jdoe', - }; + (WebApi as unknown as jest.Mock).mockImplementation(() => mockGitApi); - const inputBuild: Build = { - id: 1, - buildNumber: 'Build-1', - status: BuildStatus.Completed, - result: BuildResult.Succeeded, - queueTime: new Date('2020-09-12T06:10:23.932Z'), - startTime: new Date('2020-09-12T06:15:23.932Z'), - finishTime: new Date('2020-09-12T06:20:23.932Z'), - sourceBranch: 'refs/heads/develop', - sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', - definition: inputBuildDefinition, - _links: inputLinks, - requestedFor: inputIdentityRef, - }; - - const outputRepoBuild: RepoBuild = { - id: 1, - title: 'My Build Definition - Build-1', - link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - status: BuildStatus.Completed, - result: BuildResult.Succeeded, - queueTime: '2020-09-12T06:10:23.932Z', - startTime: '2020-09-12T06:15:23.932Z', - finishTime: '2020-09-12T06:20:23.932Z', - source: 'refs/heads/develop (f4f78b31)', - uniqueName: 'DOMAIN\\jdoe', - }; - - expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild); - }); + const api = AzureDevOpsApi.fromConfig(mockConfig, { + logger: mockLogger, + urlReader: mockUrlReader, }); - describe('mappedBuildRun with no Build definition name', () => { - it('should return RepoBuild with only Build Number for title', () => { - const inputLinks: any = { - web: { - href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - }, - }; + const result = await api.getGitRepository('project', 'repo'); - const inputIdentityRef: IdentityRef = { - displayName: 'Jane Doe', - uniqueName: 'DOMAIN\\jdoe', - }; + expect(result).toEqual({ id: 'repo' }); + }); - const inputBuild: Build = { - id: 1, - buildNumber: 'Build-1', - status: BuildStatus.Completed, - result: BuildResult.Succeeded, - queueTime: new Date('2020-09-12T06:10:23.932Z'), - startTime: new Date('2020-09-12T06:15:23.932Z'), - finishTime: new Date('2020-09-12T06:20:23.932Z'), - sourceBranch: 'refs/heads/develop', - sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', - definition: undefined, - _links: inputLinks, - requestedFor: inputIdentityRef, - }; + it('should get build list', async () => { + const mockBuilds: Build[] = [ + { + id: 1, + }, + { + id: 2, + }, + { + id: 3, + }, + ]; - const outputRepoBuild: RepoBuild = { - id: 1, - title: 'Build-1', - link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - status: BuildStatus.Completed, - result: BuildResult.Succeeded, - queueTime: '2020-09-12T06:10:23.932Z', - startTime: '2020-09-12T06:15:23.932Z', - finishTime: '2020-09-12T06:20:23.932Z', - source: 'refs/heads/develop (f4f78b31)', - uniqueName: 'DOMAIN\\jdoe', - }; + const mockBuildClient = { + getBuilds: jest.fn().mockResolvedValue(mockBuilds), + }; + const mockBuildApi = { + getBuildApi: jest.fn().mockReturnValue(mockBuildClient), + }; - expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild); - }); + (WebApi as unknown as jest.Mock).mockImplementation(() => mockBuildApi); + + const api = AzureDevOpsApi.fromConfig(mockConfig, { + logger: mockLogger, + urlReader: mockUrlReader, }); - describe('mappedBuildRun with undefined status', () => { - it('should return BuildStatus of None for status', () => { - const inputLinks: any = { - web: { - href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - }, - }; + const result = await api.getBuildList('project', 'repo', 10); - const inputIdentityRef: IdentityRef = { - displayName: 'Jane Doe', - uniqueName: 'DOMAIN\\jdoe', - }; + expect(result).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]); + }); - const inputBuild: Build = { - id: 1, - buildNumber: 'Build-1', - status: undefined, - result: BuildResult.Succeeded, - queueTime: new Date('2020-09-12T06:10:23.932Z'), - startTime: new Date('2020-09-12T06:15:23.932Z'), - finishTime: new Date('2020-09-12T06:20:23.932Z'), - sourceBranch: 'refs/heads/develop', - sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', - definition: undefined, - _links: inputLinks, - requestedFor: inputIdentityRef, - }; + it('should get repo builds', async () => { + const mockGitRepository: GitRepository = { + id: 'repo', + }; - const outputRepoBuild: RepoBuild = { - id: 1, - title: 'Build-1', - link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - status: BuildStatus.None, - result: BuildResult.Succeeded, - queueTime: '2020-09-12T06:10:23.932Z', - startTime: '2020-09-12T06:15:23.932Z', - finishTime: '2020-09-12T06:20:23.932Z', - source: 'refs/heads/develop (f4f78b31)', - uniqueName: 'DOMAIN\\jdoe', - }; + const mockBuilds: Build[] = [ + { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'main', + sourceVersion: 'abcd', + }, + { + id: 2, + buildNumber: 'Build-2', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'main', + sourceVersion: 'abcd', + }, + { + id: 3, + buildNumber: 'Build-3', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'main', + sourceVersion: 'abcd', + }, + ]; - expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild); - }); + const mockBuildClient = { + getBuilds: jest.fn().mockResolvedValue(mockBuilds), + }; + const mockGitClient = { + getRepository: jest.fn().mockResolvedValue(mockGitRepository), + }; + const mockApi = { + getGitApi: jest.fn().mockReturnValue(mockGitClient), + getBuildApi: jest.fn().mockReturnValue(mockBuildClient), + }; + + (WebApi as unknown as jest.Mock).mockImplementation(() => mockApi); + + const api = AzureDevOpsApi.fromConfig(mockConfig, { + logger: mockLogger, + urlReader: mockUrlReader, }); - describe('mappedBuildRun with undefined result', () => { - it('should return BuildResult of None for result', () => { - const inputLinks: any = { - web: { - href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - }, - }; + const result = await api.getRepoBuilds('project', 'repo', 10); - const inputIdentityRef: IdentityRef = { - displayName: 'Jane Doe', - uniqueName: 'DOMAIN\\jdoe', - }; + expect(result).toEqual([ + { + id: 1, + title: 'Build-1', + link: '', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'main (abcd)', + uniqueName: 'N/A', + }, + { + id: 2, + title: 'Build-2', + link: '', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'main (abcd)', + uniqueName: 'N/A', + }, + { + id: 3, + title: 'Build-3', + link: '', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'main (abcd)', + uniqueName: 'N/A', + }, + ]); + }); - const inputBuild: Build = { - id: 1, - buildNumber: 'Build-1', - status: BuildStatus.InProgress, - result: undefined, - queueTime: new Date('2020-09-12T06:10:23.932Z'), - startTime: new Date('2020-09-12T06:15:23.932Z'), - finishTime: new Date('2020-09-12T06:20:23.932Z'), - sourceBranch: 'refs/heads/develop', - sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', - definition: undefined, - _links: inputLinks, - requestedFor: inputIdentityRef, - }; + it('should get git tags', async () => { + const mockGitRepository: GitRepository = { + id: 'repo', + }; - const outputRepoBuild: RepoBuild = { - id: 1, - title: 'Build-1', - link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', - status: BuildStatus.InProgress, - result: BuildResult.None, - queueTime: '2020-09-12T06:10:23.932Z', - startTime: '2020-09-12T06:15:23.932Z', - finishTime: '2020-09-12T06:20:23.932Z', - source: 'refs/heads/develop (f4f78b31)', - uniqueName: 'DOMAIN\\jdoe', - }; + const mockGitRefs: GitRef[] = [ + { + objectId: '1.0', + peeledObjectId: '1.0', + name: 'v1.0', + }, + { + objectId: '2.0', + peeledObjectId: '2.0', + name: 'v2.0', + }, + { + objectId: '3.0', + peeledObjectId: '3.0', + name: 'v3.0', + }, + ]; - expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild); - }); + const mockGitClient = { + getRepository: jest.fn().mockResolvedValue(mockGitRepository), + getRefs: jest.fn().mockResolvedValue(mockGitRefs), + }; + const mockApi = { + getGitApi: jest.fn().mockReturnValue(mockGitClient), + serverUrl: 'serverUrl', + }; + + (WebApi as unknown as jest.Mock).mockImplementation(() => mockApi); + + const api = AzureDevOpsApi.fromConfig(mockConfig, { + logger: mockLogger, + urlReader: mockUrlReader, }); - describe('mappedBuildRun with undefined link', () => { - it('should return empty string for link', () => { - const inputIdentityRef: IdentityRef = { - displayName: 'Jane Doe', - uniqueName: 'DOMAIN\\jdoe', - }; + const result = await api.getGitTags('project', 'repo'); - const inputBuild: Build = { - id: 1, - buildNumber: 'Build-1', - status: BuildStatus.InProgress, - result: undefined, - queueTime: new Date('2020-09-12T06:10:23.932Z'), - startTime: new Date('2020-09-12T06:15:23.932Z'), - finishTime: new Date('2020-09-12T06:20:23.932Z'), - sourceBranch: 'refs/heads/develop', - sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', - definition: undefined, - _links: undefined, - requestedFor: inputIdentityRef, - }; + expect(result).toEqual([ + { + objectId: '1.0', + peeledObjectId: '1.0', + name: 'v1.0', + commitLink: 'serverUrl/project/_git/repo/commit/1.0', + createdBy: 'N/A', + link: 'serverUrl/project/_git/repo?version=GTv1.0', + }, + { + objectId: '2.0', + peeledObjectId: '2.0', + name: 'v2.0', + commitLink: 'serverUrl/project/_git/repo/commit/2.0', + createdBy: 'N/A', + link: 'serverUrl/project/_git/repo?version=GTv2.0', + }, + { + objectId: '3.0', + peeledObjectId: '3.0', + name: 'v3.0', + commitLink: 'serverUrl/project/_git/repo/commit/3.0', + createdBy: 'N/A', + link: 'serverUrl/project/_git/repo?version=GTv3.0', + }, + ]); + }); - const outputRepoBuild: RepoBuild = { - id: 1, - title: 'Build-1', - link: '', - status: BuildStatus.InProgress, - result: BuildResult.None, - queueTime: '2020-09-12T06:10:23.932Z', - startTime: '2020-09-12T06:15:23.932Z', - finishTime: '2020-09-12T06:20:23.932Z', - source: 'refs/heads/develop (f4f78b31)', - uniqueName: 'DOMAIN\\jdoe', - }; + it('should get pull requests', async () => { + const mockGitRepository: GitRepository = { + id: 'repo', + name: 'repo', + }; - expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild); - }); + const mockGitPullRequests: GitPullRequest[] = [ + { + pullRequestId: 1, + repository: mockGitRepository, + title: 'PR1', + creationDate: new Date('2020-09-12T06:10:23.932Z'), + sourceRefName: 'refs/heads/topic/pr1', + targetRefName: 'refs/heads/main', + status: PullRequestStatus.Active, + isDraft: false, + }, + { + pullRequestId: 2, + repository: mockGitRepository, + title: 'PR2', + creationDate: new Date('2020-09-12T06:10:23.932Z'), + sourceRefName: 'refs/heads/topic/pr2', + targetRefName: 'refs/heads/main', + status: PullRequestStatus.Active, + isDraft: false, + }, + { + pullRequestId: 3, + repository: mockGitRepository, + title: 'PR3', + creationDate: new Date('2020-09-12T06:10:23.932Z'), + sourceRefName: 'refs/heads/topic/pr3', + targetRefName: 'refs/heads/main', + status: PullRequestStatus.Active, + isDraft: false, + }, + ]; + + const mockGitClient = { + getRepository: jest.fn().mockResolvedValue(mockGitRepository), + getPullRequests: jest.fn().mockResolvedValue(mockGitPullRequests), + }; + const mockApi = { + getGitApi: jest.fn().mockReturnValue(mockGitClient), + serverUrl: 'serverUrl', + }; + + (WebApi as unknown as jest.Mock).mockImplementation(() => mockApi); + + const api = AzureDevOpsApi.fromConfig(mockConfig, { + logger: mockLogger, + urlReader: mockUrlReader, }); + + const pullRequestOptions: PullRequestOptions = { + top: 10, + status: PullRequestStatus.Active, + }; + + const result = await api.getPullRequests( + 'project', + 'repo', + pullRequestOptions, + ); + + expect(result).toEqual([ + { + pullRequestId: 1, + repoName: 'repo', + title: 'PR1', + uniqueName: 'N/A', + createdBy: 'N/A', + creationDate: '2020-09-12T06:10:23.932Z', + sourceRefName: 'refs/heads/topic/pr1', + targetRefName: 'refs/heads/main', + status: PullRequestStatus.Active, + isDraft: false, + link: 'serverUrl/project/_git/repo/pullrequest/1', + }, + { + pullRequestId: 2, + repoName: 'repo', + title: 'PR2', + uniqueName: 'N/A', + createdBy: 'N/A', + creationDate: '2020-09-12T06:10:23.932Z', + sourceRefName: 'refs/heads/topic/pr2', + targetRefName: 'refs/heads/main', + status: PullRequestStatus.Active, + isDraft: false, + link: 'serverUrl/project/_git/repo/pullrequest/2', + }, + { + pullRequestId: 3, + repoName: 'repo', + title: 'PR3', + uniqueName: 'N/A', + createdBy: 'N/A', + creationDate: '2020-09-12T06:10:23.932Z', + sourceRefName: 'refs/heads/topic/pr3', + targetRefName: 'refs/heads/main', + status: PullRequestStatus.Active, + isDraft: false, + link: 'serverUrl/project/_git/repo/pullrequest/3', + }, + ]); + }); + + it('should get build definitions', async () => { + const mockBuilds: Build[] = [ + { + id: 1, + }, + { + id: 2, + }, + { + id: 3, + }, + ]; + + const mockBuildClient = { + getDefinitions: jest.fn().mockResolvedValue(mockBuilds), + }; + const mockBuildApi = { + getBuildApi: jest.fn().mockReturnValue(mockBuildClient), + }; + + (WebApi as unknown as jest.Mock).mockImplementation(() => mockBuildApi); + + const api = AzureDevOpsApi.fromConfig(mockConfig, { + logger: mockLogger, + urlReader: mockUrlReader, + }); + + const result = await api.getBuildDefinitions('project', 'definition'); + + expect(result).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]); + }); + + it('should get build builds with repoId', async () => { + const mockBuilds: Build[] = [ + { + id: 1, + }, + { + id: 2, + }, + { + id: 3, + }, + ]; + + const mockBuildClient = { + getBuilds: jest.fn().mockResolvedValue(mockBuilds), + }; + const mockBuildApi = { + getBuildApi: jest.fn().mockReturnValue(mockBuildClient), + }; + + (WebApi as unknown as jest.Mock).mockImplementation(() => mockBuildApi); + + const api = AzureDevOpsApi.fromConfig(mockConfig, { + logger: mockLogger, + urlReader: mockUrlReader, + }); + + const result = await api.getBuilds('project', 10, 'repo', undefined); + + expect(result).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]); + }); + + it('should get build builds with definitions', async () => { + const mockBuilds: Build[] = [ + { + id: 1, + }, + { + id: 2, + }, + { + id: 3, + }, + ]; + + const mockBuildClient = { + getBuilds: jest.fn().mockResolvedValue(mockBuilds), + }; + const mockBuildApi = { + getBuildApi: jest.fn().mockReturnValue(mockBuildClient), + }; + + (WebApi as unknown as jest.Mock).mockImplementation(() => mockBuildApi); + + const api = AzureDevOpsApi.fromConfig(mockConfig, { + logger: mockLogger, + urlReader: mockUrlReader, + }); + + const result = await api.getBuilds('project', 10, undefined, [1, 2, 3]); + + expect(result).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]); + }); + + it('should get build runs with repoName', async () => { + const mockGitRepository: GitRepository = { + id: 'repo', + }; + + const mockBuilds: Build[] = [ + { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'main', + sourceVersion: 'abcd', + }, + { + id: 2, + buildNumber: 'Build-2', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'main', + sourceVersion: 'abcd', + }, + { + id: 3, + buildNumber: 'Build-3', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'main', + sourceVersion: 'abcd', + }, + ]; + + const mockBuildClient = { + getBuilds: jest.fn().mockResolvedValue(mockBuilds), + }; + const mockGitClient = { + getRepository: jest.fn().mockResolvedValue(mockGitRepository), + }; + const mockApi = { + getGitApi: jest.fn().mockReturnValue(mockGitClient), + getBuildApi: jest.fn().mockReturnValue(mockBuildClient), + }; + + (WebApi as unknown as jest.Mock).mockImplementation(() => mockApi); + + const api = AzureDevOpsApi.fromConfig(mockConfig, { + logger: mockLogger, + urlReader: mockUrlReader, + }); + + const result = await api.getBuildRuns('project', 10, 'repo', undefined); + + expect(result).toEqual([ + { + id: 1, + title: 'Build-1', + link: '', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'main (abcd)', + uniqueName: 'N/A', + }, + { + id: 2, + title: 'Build-2', + link: '', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'main (abcd)', + uniqueName: 'N/A', + }, + { + id: 3, + title: 'Build-3', + link: '', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'main (abcd)', + uniqueName: 'N/A', + }, + ]); + }); + + it('should get build runs with definitionName', async () => { + const mockBuilds: Build[] = [ + { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'main', + sourceVersion: 'abcd', + }, + { + id: 2, + buildNumber: 'Build-2', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'main', + sourceVersion: 'abcd', + }, + { + id: 3, + buildNumber: 'Build-3', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'main', + sourceVersion: 'abcd', + }, + ]; + + const mockBuildClient = { + getBuilds: jest.fn().mockResolvedValue(mockBuilds), + getDefinitions: jest.fn().mockResolvedValue(mockBuilds), + }; + + const mockApi = { + getBuildApi: jest.fn().mockReturnValue(mockBuildClient), + }; + + (WebApi as unknown as jest.Mock).mockImplementation(() => mockApi); + + const api = AzureDevOpsApi.fromConfig(mockConfig, { + logger: mockLogger, + urlReader: mockUrlReader, + }); + + const result = await api.getBuildRuns( + 'project', + 10, + undefined, + 'definition', + ); + + expect(result).toEqual([ + { + id: 1, + title: 'Build-1', + link: '', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'main (abcd)', + uniqueName: 'N/A', + }, + { + id: 2, + title: 'Build-2', + link: '', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'main (abcd)', + uniqueName: 'N/A', + }, + { + id: 3, + title: 'Build-3', + link: '', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'main (abcd)', + uniqueName: 'N/A', + }, + ]); }); }); diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index 87ded2f0e9..d50888006b 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -19,9 +19,7 @@ import { BuildDefinitionReference, } from 'azure-devops-node-api/interfaces/BuildInterfaces'; import { - BuildResult, BuildRun, - BuildStatus, DashboardPullRequest, GitTag, Policy, @@ -49,23 +47,92 @@ import { import { TeamMember as AdoTeamMember } from 'azure-devops-node-api/interfaces/common/VSSInterfaces'; import { Logger } from 'winston'; import { PolicyEvaluationRecord } from 'azure-devops-node-api/interfaces/PolicyInterfaces'; -import { WebApi } from 'azure-devops-node-api'; +import { + WebApi, + getHandlerFromToken, + getPersonalAccessTokenHandler, +} from 'azure-devops-node-api'; import { TeamProjectReference, WebApiTeam, } from 'azure-devops-node-api/interfaces/CoreInterfaces'; import { UrlReader } from '@backstage/backend-common'; +import { Config } from '@backstage/config'; +import { + AzureDevOpsCredentialsProvider, + DefaultAzureDevOpsCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; +import { + mappedBuildRun, + mappedGitTag, + mappedPullRequest, + mappedRepoBuild, +} from './mappers'; /** @public */ export class AzureDevOpsApi { - public constructor( - private readonly logger: Logger, - private readonly webApi: WebApi, - private readonly urlReader: UrlReader, - ) {} + private readonly logger: Logger; + private readonly urlReader: UrlReader; + private readonly config: Config; + private readonly credentialsProvider: AzureDevOpsCredentialsProvider; - public async getProjects(): Promise { - const client = await this.webApi.getCoreApi(); + private constructor( + logger: Logger, + urlReader: UrlReader, + config: Config, + credentialsProvider: AzureDevOpsCredentialsProvider, + ) { + this.logger = logger; + this.urlReader = urlReader; + this.config = config; + this.credentialsProvider = credentialsProvider; + } + + static fromConfig( + config: Config, + options: { logger: Logger; urlReader: UrlReader }, + ) { + const scmIntegrations = ScmIntegrations.fromConfig(config); + const credentialsProvider = + DefaultAzureDevOpsCredentialsProvider.fromIntegrations(scmIntegrations); + return new AzureDevOpsApi( + options.logger, + options.urlReader, + config, + credentialsProvider, + ); + } + + private async getWebApi(host?: string, org?: string): Promise { + // If no host or org is provided we fall back to the values from the `azureDevOps` config section + // these may have been setup in the `integrations.azure` config section + // which is why use them here and not just falling back on them entirely + const validHost = host ?? this.config.getString('azureDevOps.host'); + const validOrg = org ?? this.config.getString('azureDevOps.organization'); + const url = `https://${validHost}/${encodeURIComponent(validOrg)}`; + + const credentials = await this.credentialsProvider.getCredentials({ + url, + }); + + let authHandler; + if (!credentials) { + // No credentials found for the provided host and org in the `integrations.azure` config section + // use the fall back personal access token from `azureDevOps.token` + const token = this.config.getString('azureDevOps.token'); + authHandler = getPersonalAccessTokenHandler(token); + } else { + authHandler = getHandlerFromToken(credentials.token); + } + + const webApi = new WebApi(url, authHandler); + return webApi; + } + + public async getProjects(host?: string, org?: string): Promise { + const webApi = await this.getWebApi(host, org); + const client = await webApi.getCoreApi(); const projectList: TeamProjectReference[] = await client.getProjects(); const projects: Project[] = projectList.map(project => ({ @@ -82,12 +149,15 @@ export class AzureDevOpsApi { public async getGitRepository( projectName: string, repoName: string, + host?: string, + org?: string, ): Promise { this.logger?.debug( `Calling Azure DevOps REST API, getting Repository ${repoName} for Project ${projectName}`, ); - const client = await this.webApi.getGitApi(); + const webApi = await this.getWebApi(host, org); + const client = await webApi.getGitApi(); return client.getRepository(repoName, projectName); } @@ -95,12 +165,15 @@ export class AzureDevOpsApi { projectName: string, repoId: string, top: number, + host?: string, + org?: string, ): Promise { this.logger?.debug( `Calling Azure DevOps REST API, getting up to ${top} Builds for Repository Id ${repoId} for Project ${projectName}`, ); - const client = await this.webApi.getBuildApi(); + const webApi = await this.getWebApi(host, org); + const client = await webApi.getBuildApi(); return client.getBuilds( projectName, undefined, @@ -130,16 +203,25 @@ export class AzureDevOpsApi { projectName: string, repoName: string, top: number, + host?: string, + org?: string, ) { this.logger?.debug( `Calling Azure DevOps REST API, getting up to ${top} Builds for Repository ${repoName} for Project ${projectName}`, ); - const gitRepository = await this.getGitRepository(projectName, repoName); + const gitRepository = await this.getGitRepository( + projectName, + repoName, + host, + org, + ); const buildList = await this.getBuildList( projectName, gitRepository.id as string, top, + host, + org, ); const repoBuilds: RepoBuild[] = buildList.map(build => { @@ -152,13 +234,21 @@ export class AzureDevOpsApi { public async getGitTags( projectName: string, repoName: string, + host?: string, + org?: string, ): Promise { this.logger?.debug( `Calling Azure DevOps REST API, getting Git Tags for Repository ${repoName} for Project ${projectName}`, ); - const gitRepository = await this.getGitRepository(projectName, repoName); - const client = await this.webApi.getGitApi(); + const gitRepository = await this.getGitRepository( + projectName, + repoName, + host, + org, + ); + const webApi = await this.getWebApi(host, org); + const client = await webApi.getGitApi(); const tagRefs: GitRef[] = await client.getRefs( gitRepository.id as string, projectName, @@ -169,10 +259,10 @@ export class AzureDevOpsApi { false, true, ); - const linkBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent( + const linkBaseUrl = `${webApi.serverUrl}/${encodeURIComponent( projectName, )}/_git/${encodeURIComponent(repoName)}?version=GT`; - const commitBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent( + const commitBaseUrl = `${webApi.serverUrl}/${encodeURIComponent( projectName, )}/_git/${encodeURIComponent(repoName)}/commit`; const gitTags: GitTag[] = tagRefs.map(tagRef => { @@ -186,13 +276,21 @@ export class AzureDevOpsApi { projectName: string, repoName: string, options: PullRequestOptions, + host?: string, + org?: string, ): Promise { this.logger?.debug( `Calling Azure DevOps REST API, getting up to ${options.top} Pull Requests for Repository ${repoName} for Project ${projectName}`, ); - const gitRepository = await this.getGitRepository(projectName, repoName); - const client = await this.webApi.getGitApi(); + const gitRepository = await this.getGitRepository( + projectName, + repoName, + host, + org, + ); + const webApi = await this.getWebApi(host, org); + const client = await webApi.getGitApi(); const searchCriteria: GitPullRequestSearchCriteria = { status: options.status, }; @@ -204,7 +302,7 @@ export class AzureDevOpsApi { undefined, options.top, ); - const linkBaseUrl = `${this.webApi.serverUrl}/${encodeURIComponent( + const linkBaseUrl = `${webApi.serverUrl}/${encodeURIComponent( projectName, )}/_git/${encodeURIComponent(repoName)}/pullrequest`; const pullRequests: PullRequest[] = gitPullRequests.map(gitPullRequest => { @@ -222,7 +320,8 @@ export class AzureDevOpsApi { `Getting dashboard pull requests for project '${projectName}'.`, ); - const client = await this.webApi.getGitApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getGitApi(); const searchCriteria: GitPullRequestSearchCriteria = { status: options.status, @@ -254,7 +353,7 @@ export class AzureDevOpsApi { return convertDashboardPullRequest( gitPullRequest, - this.webApi.serverUrl, + webApi.serverUrl, policies, ); }), @@ -270,7 +369,8 @@ export class AzureDevOpsApi { `Getting pull request policies for pull request id '${pullRequestId}'.`, ); - const client = await this.webApi.getPolicyApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getPolicyApi(); const artifactId = getArtifactId(projectId, pullRequestId); @@ -285,7 +385,8 @@ export class AzureDevOpsApi { public async getAllTeams(): Promise { this.logger?.debug('Getting all teams.'); - const client = await this.webApi.getCoreApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getCoreApi(); const webApiTeams: WebApiTeam[] = await client.getAllTeams(); const teams: Team[] = webApiTeams.map(team => ({ @@ -307,7 +408,8 @@ export class AzureDevOpsApi { const { projectId, teamId } = options; this.logger?.debug(`Getting team member ids for team '${teamId}'.`); - const client = await this.webApi.getCoreApi(); + const webApi = await this.getWebApi(); + const client = await webApi.getCoreApi(); const teamMembers: AdoTeamMember[] = await client.getTeamMembersWithExtendedProperties(projectId, teamId); @@ -322,12 +424,15 @@ export class AzureDevOpsApi { public async getBuildDefinitions( projectName: string, definitionName: string, + host?: string, + org?: string, ): Promise { this.logger?.debug( `Calling Azure DevOps REST API, getting Build Definitions for ${definitionName} in Project ${projectName}`, ); - const client = await this.webApi.getBuildApi(); + const webApi = await this.getWebApi(host, org); + const client = await webApi.getBuildApi(); return client.getDefinitions(projectName, definitionName); } @@ -336,12 +441,15 @@ export class AzureDevOpsApi { top: number, repoId?: string, definitions?: number[], + host?: string, + org?: string, ): Promise { this.logger?.debug( `Calling Azure DevOps REST API, getting up to ${top} Builds for Repository Id ${repoId} for Project ${projectName}`, ); - const client = await this.webApi.getBuildApi(); + const webApi = await this.getWebApi(host, org); + const client = await webApi.getBuildApi(); return client.getBuilds( projectName, definitions, @@ -372,12 +480,19 @@ export class AzureDevOpsApi { top: number, repoName?: string, definitionName?: string, + host?: string, + org?: string, ) { let repoId: string | undefined; let definitions: number[] | undefined; if (repoName) { - const gitRepository = await this.getGitRepository(projectName, repoName); + const gitRepository = await this.getGitRepository( + projectName, + repoName, + host, + org, + ); repoId = gitRepository.id; } @@ -385,13 +500,22 @@ export class AzureDevOpsApi { const buildDefinitions = await this.getBuildDefinitions( projectName, definitionName, + host, + org, ); definitions = buildDefinitions .map(bd => bd.id) .filter((bd): bd is number => Boolean(bd)); } - const builds = await this.getBuilds(projectName, top, repoId, definitions); + const builds = await this.getBuilds( + projectName, + top, + repoId, + definitions, + host, + org, + ); const buildRuns: BuildRun[] = builds.map(mappedBuildRun); @@ -421,75 +545,3 @@ export class AzureDevOpsApi { return { url, content }; } } - -export function mappedRepoBuild(build: Build): RepoBuild { - return { - id: build.id, - title: [build.definition?.name, build.buildNumber] - .filter(Boolean) - .join(' - '), - link: build._links?.web.href ?? '', - status: build.status ?? BuildStatus.None, - result: build.result ?? BuildResult.None, - queueTime: build.queueTime?.toISOString(), - startTime: build.startTime?.toISOString(), - finishTime: build.finishTime?.toISOString(), - source: `${build.sourceBranch} (${build.sourceVersion?.slice(0, 8)})`, - uniqueName: build.requestedFor?.uniqueName ?? 'N/A', - }; -} - -export function mappedGitTag( - gitRef: GitRef, - linkBaseUrl: string, - commitBaseUrl: string, -): GitTag { - return { - objectId: gitRef.objectId, - peeledObjectId: gitRef.peeledObjectId, - name: gitRef.name?.replace('refs/tags/', ''), - createdBy: gitRef.creator?.displayName ?? 'N/A', - link: `${linkBaseUrl}${encodeURIComponent( - gitRef.name?.replace('refs/tags/', '') ?? '', - )}`, - commitLink: `${commitBaseUrl}/${encodeURIComponent( - gitRef.peeledObjectId ?? '', - )}`, - }; -} - -export function mappedPullRequest( - pullRequest: GitPullRequest, - linkBaseUrl: string, -): PullRequest { - return { - pullRequestId: pullRequest.pullRequestId, - repoName: pullRequest.repository?.name, - title: pullRequest.title, - uniqueName: pullRequest.createdBy?.uniqueName ?? 'N/A', - createdBy: pullRequest.createdBy?.displayName ?? 'N/A', - creationDate: pullRequest.creationDate?.toISOString(), - sourceRefName: pullRequest.sourceRefName, - targetRefName: pullRequest.targetRefName, - status: pullRequest.status, - isDraft: pullRequest.isDraft, - link: `${linkBaseUrl}/${pullRequest.pullRequestId}`, - }; -} - -export function mappedBuildRun(build: Build): BuildRun { - return { - id: build.id, - title: [build.definition?.name, build.buildNumber] - .filter(Boolean) - .join(' - '), - link: build._links?.web.href ?? '', - status: build.status ?? BuildStatus.None, - result: build.result ?? BuildResult.None, - queueTime: build.queueTime?.toISOString(), - startTime: build.startTime?.toISOString(), - finishTime: build.finishTime?.toISOString(), - source: `${build.sourceBranch} (${build.sourceVersion?.slice(0, 8)})`, - uniqueName: build.requestedFor?.uniqueName ?? 'N/A', - }; -} diff --git a/plugins/azure-devops-backend/src/api/mappers.test.ts b/plugins/azure-devops-backend/src/api/mappers.test.ts new file mode 100644 index 0000000000..5f7fe5eb28 --- /dev/null +++ b/plugins/azure-devops-backend/src/api/mappers.test.ts @@ -0,0 +1,573 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Build, + DefinitionReference, +} from 'azure-devops-node-api/interfaces/BuildInterfaces'; +import { + BuildResult, + BuildStatus, + GitTag, + PullRequest, + PullRequestStatus, + RepoBuild, +} from '@backstage/plugin-azure-devops-common'; +import { + GitPullRequest, + GitRef, + GitRepository, +} from 'azure-devops-node-api/interfaces/GitInterfaces'; +import { + mappedBuildRun, + mappedGitTag, + mappedPullRequest, + mappedRepoBuild, +} from './mappers'; + +import { IdentityRef } from 'azure-devops-node-api/interfaces/common/VSSInterfaces'; + +describe('mappers', () => { + describe('mappedRepoBuild', () => { + describe('mappedRepoBuild happy path', () => { + it('should return RepoBuild from Build', () => { + const inputBuildDefinition: DefinitionReference = { + name: 'My Build Definition', + }; + + const inputLinks: any = { + web: { + href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + }, + }; + + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + uniqueName: 'DOMAIN\\jdoe', + }; + + const inputBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', + definition: inputBuildDefinition, + _links: inputLinks, + requestedFor: inputIdentityRef, + }; + + const outputRepoBuild: RepoBuild = { + id: 1, + title: 'My Build Definition - Build-1', + link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'refs/heads/develop (f4f78b31)', + uniqueName: 'DOMAIN\\jdoe', + }; + + expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild); + }); + }); + + describe('mappedRepoBuild with no Build definition name', () => { + it('should return RepoBuild with only Build Number for title', () => { + const inputLinks: any = { + web: { + href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + }, + }; + + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + uniqueName: 'DOMAIN\\jdoe', + }; + + const inputBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', + definition: undefined, + _links: inputLinks, + requestedFor: inputIdentityRef, + }; + + const outputRepoBuild: RepoBuild = { + id: 1, + title: 'Build-1', + link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'refs/heads/develop (f4f78b31)', + uniqueName: 'DOMAIN\\jdoe', + }; + + expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild); + }); + }); + + describe('mappedRepoBuild with undefined status', () => { + it('should return BuildStatus of None for status', () => { + const inputLinks: any = { + web: { + href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + }, + }; + + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + uniqueName: 'DOMAIN\\jdoe', + }; + + const inputBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: undefined, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', + definition: undefined, + _links: inputLinks, + requestedFor: inputIdentityRef, + }; + + const outputRepoBuild: RepoBuild = { + id: 1, + title: 'Build-1', + link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + status: BuildStatus.None, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'refs/heads/develop (f4f78b31)', + uniqueName: 'DOMAIN\\jdoe', + }; + + expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild); + }); + }); + + describe('mappedRepoBuild with undefined result', () => { + it('should return BuildResult of None for result', () => { + const inputLinks: any = { + web: { + href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + }, + }; + + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + uniqueName: 'DOMAIN\\jdoe', + }; + + const inputBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.InProgress, + result: undefined, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', + definition: undefined, + _links: inputLinks, + requestedFor: inputIdentityRef, + }; + + const outputRepoBuild: RepoBuild = { + id: 1, + title: 'Build-1', + link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + status: BuildStatus.InProgress, + result: BuildResult.None, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'refs/heads/develop (f4f78b31)', + uniqueName: 'DOMAIN\\jdoe', + }; + + expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild); + }); + }); + + describe('mappedRepoBuild with undefined link', () => { + it('should return empty string for link', () => { + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + uniqueName: 'DOMAIN\\jdoe', + }; + + const inputBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.InProgress, + result: undefined, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', + definition: undefined, + _links: undefined, + requestedFor: inputIdentityRef, + }; + + const outputRepoBuild: RepoBuild = { + id: 1, + title: 'Build-1', + link: '', + status: BuildStatus.InProgress, + result: BuildResult.None, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'refs/heads/develop (f4f78b31)', + uniqueName: 'DOMAIN\\jdoe', + }; + + expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild); + }); + }); + }); + + describe('mappedGitTag', () => { + describe('mappedGitTag happy path', () => { + it('should return GitTag from GitRef', () => { + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + }; + const inputGitRef: GitRef = { + name: 'refs/tags/v1.1.2', + creator: inputIdentityRef, + objectId: '1111aaaa2222bbbb3333cccc4444dddd5555eeee', + peeledObjectId: '1234567890abcdef1234567890abcdef12345678', + }; + const inputLinkBaseUrl = + 'https://host.com/myOrg/_git/super-feature-repo?version=GT'; + const inputCommitBaseUrl = + 'https://host.com/myOrg/_git/super-feature-repo/commit'; + const outputGitTag: GitTag = { + name: 'v1.1.2', + createdBy: 'Jane Doe', + commitLink: + 'https://host.com/myOrg/_git/super-feature-repo/commit/1234567890abcdef1234567890abcdef12345678', + objectId: '1111aaaa2222bbbb3333cccc4444dddd5555eeee', + peeledObjectId: '1234567890abcdef1234567890abcdef12345678', + link: 'https://host.com/myOrg/_git/super-feature-repo?version=GTv1.1.2', + }; + expect( + mappedGitTag(inputGitRef, inputLinkBaseUrl, inputCommitBaseUrl), + ).toEqual(outputGitTag); + }); + }); + }); + + describe('mappedPullRequest', () => { + describe('mappedPullRequest happy path', () => { + it('should return PullRequest from GitPullRequest', () => { + const inputGitRepository: GitRepository = { + name: 'super-feature-repo', + }; + + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + uniqueName: 'DOMAIN\\jdoe', + }; + + const inputPullRequest: GitPullRequest = { + pullRequestId: 7181, + repository: inputGitRepository, + title: 'My Awesome New Feature', + createdBy: inputIdentityRef, + creationDate: new Date('2020-09-12T06:10:23.932Z'), + sourceRefName: 'refs/heads/topic/super-awesome-feature', + targetRefName: 'refs/heads/main', + status: PullRequestStatus.Active, + isDraft: false, + }; + + const inputBaseUrl = + 'https://host.com/myOrg/_git/super-feature-repo/pullrequest'; + + const outputPullRequest: PullRequest = { + pullRequestId: 7181, + repoName: 'super-feature-repo', + title: 'My Awesome New Feature', + uniqueName: 'DOMAIN\\jdoe', + createdBy: 'Jane Doe', + creationDate: '2020-09-12T06:10:23.932Z', + sourceRefName: 'refs/heads/topic/super-awesome-feature', + targetRefName: 'refs/heads/main', + status: PullRequestStatus.Active, + isDraft: false, + link: 'https://host.com/myOrg/_git/super-feature-repo/pullrequest/7181', + }; + + expect(mappedPullRequest(inputPullRequest, inputBaseUrl)).toEqual( + outputPullRequest, + ); + }); + }); + }); + + describe('mappedBuildRun', () => { + describe('mappedBuildRun happy path', () => { + it('should return RepoBuild from Build', () => { + const inputBuildDefinition: DefinitionReference = { + name: 'My Build Definition', + }; + + const inputLinks: any = { + web: { + href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + }, + }; + + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + uniqueName: 'DOMAIN\\jdoe', + }; + + const inputBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', + definition: inputBuildDefinition, + _links: inputLinks, + requestedFor: inputIdentityRef, + }; + + const outputRepoBuild: RepoBuild = { + id: 1, + title: 'My Build Definition - Build-1', + link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'refs/heads/develop (f4f78b31)', + uniqueName: 'DOMAIN\\jdoe', + }; + + expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild); + }); + }); + + describe('mappedBuildRun with no Build definition name', () => { + it('should return RepoBuild with only Build Number for title', () => { + const inputLinks: any = { + web: { + href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + }, + }; + + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + uniqueName: 'DOMAIN\\jdoe', + }; + + const inputBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', + definition: undefined, + _links: inputLinks, + requestedFor: inputIdentityRef, + }; + + const outputRepoBuild: RepoBuild = { + id: 1, + title: 'Build-1', + link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + status: BuildStatus.Completed, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'refs/heads/develop (f4f78b31)', + uniqueName: 'DOMAIN\\jdoe', + }; + + expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild); + }); + }); + + describe('mappedBuildRun with undefined status', () => { + it('should return BuildStatus of None for status', () => { + const inputLinks: any = { + web: { + href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + }, + }; + + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + uniqueName: 'DOMAIN\\jdoe', + }; + + const inputBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: undefined, + result: BuildResult.Succeeded, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', + definition: undefined, + _links: inputLinks, + requestedFor: inputIdentityRef, + }; + + const outputRepoBuild: RepoBuild = { + id: 1, + title: 'Build-1', + link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + status: BuildStatus.None, + result: BuildResult.Succeeded, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'refs/heads/develop (f4f78b31)', + uniqueName: 'DOMAIN\\jdoe', + }; + + expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild); + }); + }); + + describe('mappedBuildRun with undefined result', () => { + it('should return BuildResult of None for result', () => { + const inputLinks: any = { + web: { + href: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + }, + }; + + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + uniqueName: 'DOMAIN\\jdoe', + }; + + const inputBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.InProgress, + result: undefined, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', + definition: undefined, + _links: inputLinks, + requestedFor: inputIdentityRef, + }; + + const outputRepoBuild: RepoBuild = { + id: 1, + title: 'Build-1', + link: 'https://host.com/myOrg/0bcc0c0d-2d02/_build/results?buildId=1', + status: BuildStatus.InProgress, + result: BuildResult.None, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'refs/heads/develop (f4f78b31)', + uniqueName: 'DOMAIN\\jdoe', + }; + + expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild); + }); + }); + + describe('mappedBuildRun with undefined link', () => { + it('should return empty string for link', () => { + const inputIdentityRef: IdentityRef = { + displayName: 'Jane Doe', + uniqueName: 'DOMAIN\\jdoe', + }; + + const inputBuild: Build = { + id: 1, + buildNumber: 'Build-1', + status: BuildStatus.InProgress, + result: undefined, + queueTime: new Date('2020-09-12T06:10:23.932Z'), + startTime: new Date('2020-09-12T06:15:23.932Z'), + finishTime: new Date('2020-09-12T06:20:23.932Z'), + sourceBranch: 'refs/heads/develop', + sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c', + definition: undefined, + _links: undefined, + requestedFor: inputIdentityRef, + }; + + const outputRepoBuild: RepoBuild = { + id: 1, + title: 'Build-1', + link: '', + status: BuildStatus.InProgress, + result: BuildResult.None, + queueTime: '2020-09-12T06:10:23.932Z', + startTime: '2020-09-12T06:15:23.932Z', + finishTime: '2020-09-12T06:20:23.932Z', + source: 'refs/heads/develop (f4f78b31)', + uniqueName: 'DOMAIN\\jdoe', + }; + + expect(mappedBuildRun(inputBuild)).toEqual(outputRepoBuild); + }); + }); + }); +}); diff --git a/plugins/azure-devops-backend/src/api/mappers.ts b/plugins/azure-devops-backend/src/api/mappers.ts new file mode 100644 index 0000000000..72097a25b8 --- /dev/null +++ b/plugins/azure-devops-backend/src/api/mappers.ts @@ -0,0 +1,101 @@ +/* + * 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 { + RepoBuild, + BuildStatus, + BuildResult, + GitTag, + PullRequest, + BuildRun, +} from '@backstage/plugin-azure-devops-common'; +import { Build } from 'azure-devops-node-api/interfaces/BuildInterfaces'; +import { + GitRef, + GitPullRequest, +} from 'azure-devops-node-api/interfaces/GitInterfaces'; + +export function mappedRepoBuild(build: Build): RepoBuild { + return { + id: build.id, + title: [build.definition?.name, build.buildNumber] + .filter(Boolean) + .join(' - '), + link: build._links?.web.href ?? '', + status: build.status ?? BuildStatus.None, + result: build.result ?? BuildResult.None, + queueTime: build.queueTime?.toISOString(), + startTime: build.startTime?.toISOString(), + finishTime: build.finishTime?.toISOString(), + source: `${build.sourceBranch} (${build.sourceVersion?.slice(0, 8)})`, + uniqueName: build.requestedFor?.uniqueName ?? 'N/A', + }; +} + +export function mappedGitTag( + gitRef: GitRef, + linkBaseUrl: string, + commitBaseUrl: string, +): GitTag { + return { + objectId: gitRef.objectId, + peeledObjectId: gitRef.peeledObjectId, + name: gitRef.name?.replace('refs/tags/', ''), + createdBy: gitRef.creator?.displayName ?? 'N/A', + link: `${linkBaseUrl}${encodeURIComponent( + gitRef.name?.replace('refs/tags/', '') ?? '', + )}`, + commitLink: `${commitBaseUrl}/${encodeURIComponent( + gitRef.peeledObjectId ?? '', + )}`, + }; +} + +export function mappedPullRequest( + pullRequest: GitPullRequest, + linkBaseUrl: string, +): PullRequest { + return { + pullRequestId: pullRequest.pullRequestId, + repoName: pullRequest.repository?.name, + title: pullRequest.title, + uniqueName: pullRequest.createdBy?.uniqueName ?? 'N/A', + createdBy: pullRequest.createdBy?.displayName ?? 'N/A', + creationDate: pullRequest.creationDate?.toISOString(), + sourceRefName: pullRequest.sourceRefName, + targetRefName: pullRequest.targetRefName, + status: pullRequest.status, + isDraft: pullRequest.isDraft, + link: `${linkBaseUrl}/${pullRequest.pullRequestId}`, + }; +} + +export function mappedBuildRun(build: Build): BuildRun { + return { + id: build.id, + title: [build.definition?.name, build.buildNumber] + .filter(Boolean) + .join(' - '), + link: build._links?.web.href ?? '', + status: build.status ?? BuildStatus.None, + result: build.result ?? BuildResult.None, + queueTime: build.queueTime?.toISOString(), + startTime: build.startTime?.toISOString(), + finishTime: build.finishTime?.toISOString(), + source: `${build.sourceBranch} (${build.sourceVersion?.slice(0, 8)})`, + uniqueName: build.requestedFor?.uniqueName ?? 'N/A', + }; +} diff --git a/plugins/azure-devops-backend/src/index.ts b/plugins/azure-devops-backend/src/index.ts index 3c802e7144..61cf7c7a6f 100644 --- a/plugins/azure-devops-backend/src/index.ts +++ b/plugins/azure-devops-backend/src/index.ts @@ -23,3 +23,4 @@ export { AzureDevOpsApi } from './api'; export * from './service/router'; export { azureDevOpsPlugin as default } from './plugin'; +export { AzureDevOpsAnnotatorProcessor } from './processor'; diff --git a/plugins/azure-devops-backend/src/processor/AzureDevOpsAnnotatorProcessor.test.ts b/plugins/azure-devops-backend/src/processor/AzureDevOpsAnnotatorProcessor.test.ts new file mode 100644 index 0000000000..dd83c56f70 --- /dev/null +++ b/plugins/azure-devops-backend/src/processor/AzureDevOpsAnnotatorProcessor.test.ts @@ -0,0 +1,271 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { ConfigReader } from '@backstage/config'; +import { AzureDevOpsAnnotatorProcessor } from './AzureDevOpsAnnotatorProcessor'; +import { LocationSpec } from '@backstage/plugin-catalog-common'; + +describe('AzureDevOpsAnnotatorProcessor', () => { + it('adds annotation', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + }, + }; + const location: LocationSpec = { + type: 'url', + target: + 'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml', + }; + + const processor = AzureDevOpsAnnotatorProcessor.fromConfig( + new ConfigReader({}), + ); + + expect(await processor.preProcessEntity(entity, location)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + annotations: { + 'dev.azure.com/host-org': 'dev.azure.com/organization', + 'dev.azure.com/project-repo': 'project/repository', + }, + }, + }); + }); + + it('adds annotation for Azure DevOps Server url', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + }, + }; + const location: LocationSpec = { + type: 'url', + target: + 'https://example.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml', + }; + + const processor = AzureDevOpsAnnotatorProcessor.fromConfig( + new ConfigReader({ + integrations: { + azure: [ + { + host: 'example.com', + credentials: [ + { + personalAccessToken: 'pat', + }, + ], + }, + ], + }, + }), + ); + + expect(await processor.preProcessEntity(entity, location)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + annotations: { + 'dev.azure.com/host-org': 'example.com/organization', + 'dev.azure.com/project-repo': 'project/repository', + }, + }, + }); + }); + + it('adds annotation for TFS subpath url', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + }, + }; + const location: LocationSpec = { + type: 'url', + target: + 'https://example.com/tfs/organization/project/_git/repository?path=%2Fcatalog-info.yaml', + }; + + const processor = AzureDevOpsAnnotatorProcessor.fromConfig( + new ConfigReader({ + integrations: { + azure: [ + { + host: 'example.com', + credentials: [ + { + personalAccessToken: 'pat', + }, + ], + }, + ], + }, + }), + ); + + expect(await processor.preProcessEntity(entity, location)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + annotations: { + 'dev.azure.com/host-org': 'example.com/tfs/organization', + 'dev.azure.com/project-repo': 'project/repository', + }, + }, + }); + }); + + it('does not override existing annotation', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + annotations: { + 'dev.azure.com/host-org': 'myhost/myorg', + 'dev.azure.com/project-repo': 'myproj/myrepo', + }, + }, + }; + const location: LocationSpec = { + type: 'url', + target: + 'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml', + }; + + const processor = AzureDevOpsAnnotatorProcessor.fromConfig( + new ConfigReader({}), + ); + + expect(await processor.preProcessEntity(entity, location)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + annotations: { + 'dev.azure.com/host-org': 'myhost/myorg', + 'dev.azure.com/project-repo': 'myproj/myrepo', + }, + }, + }); + }); + + it('should not add annotation for other providers', async () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + }, + }; + const location: LocationSpec = { + type: 'url', + target: + 'https://not-in-mock-config.example.com/backstage/backstage/-/blob/master/catalog-info.yaml', + }; + + const processor = AzureDevOpsAnnotatorProcessor.fromConfig( + new ConfigReader({}), + ); + + expect(await processor.preProcessEntity(entity, location)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + }, + }); + }); + + it('should only process applicable kinds', async () => { + const component: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + }, + }; + + const api: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { + name: 'my-component', + }, + }; + + const system: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'System', + metadata: { + name: 'my-component', + }, + }; + + const location: LocationSpec = { + type: 'url', + target: + 'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml', + }; + + const processor = AzureDevOpsAnnotatorProcessor.fromConfig( + new ConfigReader({}), + { kinds: ['API', 'Component'] }, + ); + + expect(await processor.preProcessEntity(component, location)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'my-component', + annotations: { + 'dev.azure.com/host-org': 'dev.azure.com/organization', + 'dev.azure.com/project-repo': 'project/repository', + }, + }, + }); + + expect(await processor.preProcessEntity(api, location)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { + name: 'my-component', + annotations: { + 'dev.azure.com/host-org': 'dev.azure.com/organization', + 'dev.azure.com/project-repo': 'project/repository', + }, + }, + }); + + expect(await processor.preProcessEntity(system, location)).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'System', + metadata: { + name: 'my-component', + }, + }); + }); +}); diff --git a/plugins/azure-devops-backend/src/processor/AzureDevOpsAnnotatorProcessor.ts b/plugins/azure-devops-backend/src/processor/AzureDevOpsAnnotatorProcessor.ts new file mode 100644 index 0000000000..c27195b8ac --- /dev/null +++ b/plugins/azure-devops-backend/src/processor/AzureDevOpsAnnotatorProcessor.ts @@ -0,0 +1,127 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { + ScmIntegrationRegistry, + ScmIntegrations, +} from '@backstage/integration'; +import { identity, merge, pickBy } from 'lodash'; +import { LocationSpec } from '@backstage/plugin-catalog-common'; +import { CatalogProcessor } from '@backstage/plugin-catalog-node'; +import { + AZURE_DEVOPS_HOST_ORG_ANNOTATION, + AZURE_DEVOPS_REPO_ANNOTATION, +} from '@backstage/plugin-azure-devops-common'; +import { parseAzureDevOpsUrl } from '../utils'; + +/** @public */ +export class AzureDevOpsAnnotatorProcessor implements CatalogProcessor { + constructor( + private readonly opts: { + scmIntegrationRegistry: ScmIntegrationRegistry; + kinds?: string[]; + }, + ) {} + + getProcessorName(): string { + return 'AzureDevOpsAnnotatorProcessor'; + } + + static fromConfig( + config: Config, + options?: { kinds?: string[] }, + ): AzureDevOpsAnnotatorProcessor { + return new AzureDevOpsAnnotatorProcessor({ + scmIntegrationRegistry: ScmIntegrations.fromConfig(config), + kinds: options?.kinds, + }); + } + + async preProcessEntity( + entity: Entity, + location: LocationSpec, + ): Promise { + const applicableKinds = (this.opts.kinds ?? ['Component']).map(k => + k.toLocaleLowerCase('en-US'), + ); + if ( + !applicableKinds.includes(entity.kind.toLocaleLowerCase('en-US')) || + location.type !== 'url' + ) { + return entity; + } + + const scmIntegration = this.opts.scmIntegrationRegistry.byUrl( + location.target, + ); + + if (!scmIntegration) { + return entity; + } + + if (scmIntegration.type !== 'azure') { + return entity; + } + + const { host, org, project, repo } = parseAzureDevOpsUrl(location.target); + + if (!org || !project || !repo) { + return entity; + } + + const hostOrgAnnotation = AZURE_DEVOPS_HOST_ORG_ANNOTATION; + let hostOrgValue = entity.metadata.annotations?.[hostOrgAnnotation]; + if (!hostOrgValue) { + hostOrgValue = `${host}/${org}`; + } + + const projectRepoAnnotation = AZURE_DEVOPS_REPO_ANNOTATION; + let projectRepoValue = entity.metadata.annotations?.[projectRepoAnnotation]; + if (!projectRepoValue) { + projectRepoValue = `${project}/${repo}`; + } + + const result = merge( + { + metadata: { + annotations: pickBy( + { + [hostOrgAnnotation]: hostOrgValue, + }, + identity, + ), + }, + }, + entity, + ); + + return merge( + { + metadata: { + annotations: pickBy( + { + [projectRepoAnnotation]: projectRepoValue, + }, + identity, + ), + }, + }, + result, + ); + } +} diff --git a/plugins/scaffolder/src/legacy/index.ts b/plugins/azure-devops-backend/src/processor/index.ts similarity index 84% rename from plugins/scaffolder/src/legacy/index.ts rename to plugins/azure-devops-backend/src/processor/index.ts index 2393be0f69..46747c4802 100644 --- a/plugins/scaffolder/src/legacy/index.ts +++ b/plugins/azure-devops-backend/src/processor/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { LegacyRouter, type LegacyRouterProps } from './Router'; -export { LegacyScaffolderPage } from '../plugin'; + +export { AzureDevOpsAnnotatorProcessor } from './AzureDevOpsAnnotatorProcessor'; diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index a77600feb0..65cb27271e 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -161,6 +161,8 @@ describe('createRouter', () => { 'myProject', 'af4ae3af-e747-4129-9bbc-d1329f6b0998', 40, + undefined, + undefined, ); expect(response.status).toEqual(200); expect(response.body).toEqual(builds); @@ -215,6 +217,8 @@ describe('createRouter', () => { 'myProject', 'myRepo', 50, + undefined, + undefined, ); expect(response.status).toEqual(200); expect(response.body).toEqual(repoBuilds); @@ -252,6 +256,8 @@ describe('createRouter', () => { expect(azureDevOpsApi.getGitTags).toHaveBeenCalledWith( 'myProject', 'myRepo', + undefined, + undefined, ); expect(response.status).toEqual(200); expect(response.body).toEqual(gitTags); @@ -315,6 +321,8 @@ describe('createRouter', () => { 'myProject', 'myRepo', { status: 1, top: 50 }, + undefined, + undefined, ); expect(response.status).toEqual(200); expect(response.body).toEqual(pullRequests); @@ -341,6 +349,8 @@ describe('createRouter', () => { expect(azureDevOpsApi.getBuildDefinitions).toHaveBeenCalledWith( 'myProject', 'myBuildDefinition', + undefined, + undefined, ); expect(response.status).toEqual(200); expect(response.body).toEqual(inputDefinitions); @@ -397,6 +407,8 @@ describe('createRouter', () => { 50, 'myRepo', undefined, + undefined, + undefined, ); expect(response.status).toEqual(200); expect(response.body).toEqual(buildRuns); @@ -452,6 +464,8 @@ describe('createRouter', () => { 50, undefined, 'myDefinition', + undefined, + undefined, ); expect(response.status).toEqual(200); expect(response.body).toEqual(buildRuns); diff --git a/plugins/azure-devops-backend/src/service/router.ts b/plugins/azure-devops-backend/src/service/router.ts index b6b8a6fdea..9a71c8f03d 100644 --- a/plugins/azure-devops-backend/src/service/router.ts +++ b/plugins/azure-devops-backend/src/service/router.ts @@ -19,7 +19,6 @@ import { PullRequestOptions, PullRequestStatus, } from '@backstage/plugin-azure-devops-common'; -import { WebApi, getPersonalAccessTokenHandler } from 'azure-devops-node-api'; import { AzureDevOpsApi } from '../api'; import { Config } from '@backstage/config'; @@ -43,18 +42,11 @@ export interface RouterOptions { export async function createRouter( options: RouterOptions, ): Promise { - const { logger, reader } = options; - const config = options.config.getConfig('azureDevOps'); - - const token = config.getString('token'); - const host = config.getString('host'); - const organization = config.getString('organization'); - - const authHandler = getPersonalAccessTokenHandler(token); - const webApi = new WebApi(`https://${host}/${organization}`, authHandler); + const { logger, reader, config } = options; const azureDevOpsApi = - options.azureDevOpsApi || new AzureDevOpsApi(logger, webApi, reader); + options.azureDevOpsApi || + AzureDevOpsApi.fromConfig(config, { logger, urlReader: reader }); const pullRequestsDashboardProvider = await PullRequestsDashboardProvider.create(logger, azureDevOpsApi); @@ -83,10 +75,14 @@ export async function createRouter( router.get('/builds/:projectName/:repoId', async (req, res) => { const { projectName, repoId } = req.params; const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP; + const host = req.query.host?.toString(); + const org = req.query.org?.toString(); const buildList = await azureDevOpsApi.getBuildList( projectName, repoId, top, + host, + org, ); res.status(200).json(buildList); }); @@ -95,11 +91,14 @@ export async function createRouter( const { projectName, repoName } = req.params; const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP; - + const host = req.query.host?.toString(); + const org = req.query.org?.toString(); const gitRepository = await azureDevOpsApi.getRepoBuilds( projectName, repoName, top, + host, + org, ); res.status(200).json(gitRepository); @@ -107,7 +106,14 @@ export async function createRouter( router.get('/git-tags/:projectName/:repoName', async (req, res) => { const { projectName, repoName } = req.params; - const gitTags = await azureDevOpsApi.getGitTags(projectName, repoName); + const host = req.query.host?.toString(); + const org = req.query.org?.toString(); + const gitTags = await azureDevOpsApi.getGitTags( + projectName, + repoName, + host, + org, + ); res.status(200).json(gitTags); }); @@ -115,7 +121,8 @@ export async function createRouter( const { projectName, repoName } = req.params; const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP; - + const host = req.query.host?.toString(); + const org = req.query.org?.toString(); const status = req.query.status ? Number(req.query.status) : PullRequestStatus.Active; @@ -129,6 +136,8 @@ export async function createRouter( projectName, repoName, pullRequestOptions, + host, + org, ); res.status(200).json(gitPullRequest); @@ -166,9 +175,13 @@ export async function createRouter( '/build-definitions/:projectName/:definitionName', async (req, res) => { const { projectName, definitionName } = req.params; + const host = req.query.host?.toString(); + const org = req.query.org?.toString(); const buildDefinitionList = await azureDevOpsApi.getBuildDefinitions( projectName, definitionName, + host, + org, ); res.status(200).json(buildDefinitionList); }, @@ -179,11 +192,15 @@ export async function createRouter( const repoName = req.query.repoName?.toString(); const definitionName = req.query.definitionName?.toString(); const top = req.query.top ? Number(req.query.top) : DEFAULT_TOP; + const host = req.query.host?.toString(); + const org = req.query.org?.toString(); const builds = await azureDevOpsApi.getBuildRuns( projectName, top, repoName, definitionName, + host, + org, ); res.status(200).json(builds); }); @@ -195,10 +212,14 @@ export async function createRouter( }); router.get('/readme/:projectName/:repoName', async (req, res) => { + const host = + req.query.host?.toString() ?? config.getString('azureDevOps.host'); + const org = + req.query.org?.toString() ?? config.getString('azureDevOps.organization'); const { projectName, repoName } = req.params; const readme = await azureDevOpsApi.getReadme( host, - organization, + org, projectName, repoName, ); diff --git a/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts b/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts index 427980be14..c3e2cd1106 100644 --- a/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts +++ b/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts @@ -28,6 +28,7 @@ import { getPullRequestLink, replaceReadme, buildEncodedUrl, + parseAzureDevOpsUrl, } from './azure-devops-utils'; import { GitPullRequest } from 'azure-devops-node-api/interfaces/GitInterfaces'; import { UrlReader } from '@backstage/backend-common'; @@ -234,6 +235,15 @@ describe('extractPartsFromAsset', () => { ext: '.gif', }); }); + + it('should return parts from asset with leading . without /', () => { + const result = extractPartsFromAsset('[Image 1](.images/sample-1.PNG)'); + expect(result).toEqual({ + label: 'Image 1', + path: '.images/sample-1', + ext: '.PNG', + }); + }); }); describe('replaceReadme', () => { @@ -295,3 +305,38 @@ describe('buildEncodedUrl', () => { ); }); }); + +describe('parseAzureDevOpsUrl', () => { + it('parses Azure DevOps Cloud url', async () => { + const result = parseAzureDevOpsUrl( + 'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml', + ); + + expect(result.host).toEqual('dev.azure.com'); + expect(result.org).toEqual('organization'); + expect(result.project).toEqual('project'); + expect(result.repo).toEqual('repository'); + }); + + it('parses Azure DevOps Server url', async () => { + const result = parseAzureDevOpsUrl( + 'https://server.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml', + ); + + expect(result.host).toEqual('server.com'); + expect(result.org).toEqual('organization'); + expect(result.project).toEqual('project'); + expect(result.repo).toEqual('repository'); + }); + + it('parses TFS subpath Url', async () => { + const result = parseAzureDevOpsUrl( + 'https://server.com/tfs/organization/project/_git/repository?path=%2Fcatalog-info.yaml', + ); + + expect(result.host).toEqual('server.com/tfs'); + expect(result.org).toEqual('organization'); + expect(result.project).toEqual('project'); + expect(result.repo).toEqual('repository'); + }); +}); diff --git a/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts b/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts index aa946e7a74..d24b104bae 100644 --- a/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts +++ b/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts @@ -325,6 +325,32 @@ export function extractPartsFromAsset(content: string): { return { ext, label, - path: path.startsWith('.') ? path.substring(1, path.length) : path, + path: path.startsWith('./') ? path.substring(1, path.length) : path, }; } + +export function parseAzureDevOpsUrl(sourceUrl: string) { + const url = new URL(sourceUrl); + + let host = url.host; + let org; + let project; + let repo; + + const parts = url.pathname.split('/').map(part => decodeURIComponent(part)); + if (parts[2] === '_git') { + org = parts[1]; + project = repo = parts[3]; + } else if (parts[3] === '_git') { + org = parts[1]; + project = parts[2]; + repo = parts[4]; + } else if (parts[4] === '_git') { + host = `${host}/${parts[1]}`; + org = parts[2]; + project = parts[3]; + repo = parts[5]; + } + + return { host, org, project, repo }; +} diff --git a/plugins/azure-devops-common/CHANGELOG.md b/plugins/azure-devops-common/CHANGELOG.md index e5637a9ff4..1709b96073 100644 --- a/plugins/azure-devops-common/CHANGELOG.md +++ b/plugins/azure-devops-common/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-azure-devops-common +## 0.3.2 + +### Patch Changes + +- c70e4f5: Added multi-org support +- 043b724: Introduced new `AzureDevOpsAnnotatorProcessor` that adds the needed annotations automatically. Also, moved constants to common package so they can be shared more easily + +## 0.3.2-next.1 + +### Patch Changes + +- c70e4f5: Added multi-org support + +## 0.3.2-next.0 + +### Patch Changes + +- 043b724c56: Introduced new `AzureDevOpsAnnotatorProcessor` that adds the needed annotations automatically. Also, moved constants to common package so they can be shared more easily + ## 0.3.1 ### Patch Changes diff --git a/plugins/azure-devops-common/api-report.md b/plugins/azure-devops-common/api-report.md index ea9f7130c0..5bd1674fc8 100644 --- a/plugins/azure-devops-common/api-report.md +++ b/plugins/azure-devops-common/api-report.md @@ -3,6 +3,22 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +// @public (undocumented) +export const AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION = + 'dev.azure.com/build-definition'; + +// @public (undocumented) +export const AZURE_DEVOPS_DEFAULT_TOP: number; + +// @public (undocumented) +export const AZURE_DEVOPS_HOST_ORG_ANNOTATION = 'dev.azure.com/host-org'; + +// @public (undocumented) +export const AZURE_DEVOPS_PROJECT_ANNOTATION = 'dev.azure.com/project'; + +// @public (undocumented) +export const AZURE_DEVOPS_REPO_ANNOTATION = 'dev.azure.com/project-repo'; + // @public (undocumented) export enum BuildResult { Canceled = 32, @@ -207,6 +223,10 @@ export interface Readme { // @public (undocumented) export interface ReadmeConfig { + // (undocumented) + host?: string; + // (undocumented) + org?: string; // (undocumented) project: string; // (undocumented) diff --git a/plugins/azure-devops-common/package.json b/plugins/azure-devops-common/package.json index 7648e39539..21e9245e38 100644 --- a/plugins/azure-devops-common/package.json +++ b/plugins/azure-devops-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops-common", - "version": "0.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-devops/src/constants.ts b/plugins/azure-devops-common/src/constants.ts similarity index 92% rename from plugins/azure-devops/src/constants.ts rename to plugins/azure-devops-common/src/constants.ts index aba0ec093a..5cc6b54592 100644 --- a/plugins/azure-devops/src/constants.ts +++ b/plugins/azure-devops-common/src/constants.ts @@ -14,9 +14,14 @@ * limitations under the License. */ +/** @public */ export const AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION = 'dev.azure.com/build-definition'; +/** @public */ export const AZURE_DEVOPS_HOST_ORG_ANNOTATION = 'dev.azure.com/host-org'; +/** @public */ export const AZURE_DEVOPS_PROJECT_ANNOTATION = 'dev.azure.com/project'; +/** @public */ export const AZURE_DEVOPS_REPO_ANNOTATION = 'dev.azure.com/project-repo'; +/** @public */ export const AZURE_DEVOPS_DEFAULT_TOP: number = 10; diff --git a/plugins/azure-devops-common/src/index.ts b/plugins/azure-devops-common/src/index.ts index 26a854de89..9a4a007317 100644 --- a/plugins/azure-devops-common/src/index.ts +++ b/plugins/azure-devops-common/src/index.ts @@ -15,3 +15,4 @@ */ export * from './types'; +export * from './constants'; diff --git a/plugins/azure-devops-common/src/types.ts b/plugins/azure-devops-common/src/types.ts index 380cb3e585..afb6083867 100644 --- a/plugins/azure-devops-common/src/types.ts +++ b/plugins/azure-devops-common/src/types.ts @@ -210,6 +210,8 @@ export interface Team { export interface ReadmeConfig { project: string; repo: string; + host?: string; + org?: string; } /** @public */ diff --git a/plugins/azure-devops/.eslintrc.js b/plugins/azure-devops/.eslintrc.js index e2a53a6ad2..e487f765b2 100644 --- a/plugins/azure-devops/.eslintrc.js +++ b/plugins/azure-devops/.eslintrc.js @@ -1 +1,5 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + '@backstage/no-top-level-material-ui-4-imports': 'error', + }, +}); diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md index 919b4b7a25..a2c1f6f118 100644 --- a/plugins/azure-devops/CHANGELOG.md +++ b/plugins/azure-devops/CHANGELOG.md @@ -1,5 +1,122 @@ # @backstage/plugin-azure-devops +## 0.3.12-next.0 + +### Patch Changes + +- 995d280: Updated imports from named to default imports to help with the Material UI v4 to v5 migration +- cb0afaa: Prefer `dev.azure.com/build-definition` annotation when it is provided, as it is more specific than `dev.azure.com/project-repo`. This can also be used as a filter for mono-repos. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-azure-devops-common@0.3.2 + +## 0.3.11 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-azure-devops-common@0.3.2 + +## 0.3.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-azure-devops-common@0.3.2 + +## 0.3.11-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-azure-devops-common@0.3.2 + +## 0.3.10 + +### Patch Changes + +- c70e4f5: Added multi-org support +- 7c9af0b: Added support for annotations that use a subpath for the host. Also validated that the annotations have the correct number of slashes. +- 043b724: Introduced new `AzureDevOpsAnnotatorProcessor` that adds the needed annotations automatically. Also, moved constants to common package so they can be shared more easily +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-azure-devops-common@0.3.2 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.3.10-next.3 + +### Patch Changes + +- c70e4f5: Added multi-org support +- 7c9af0b: Added support for annotations that use a subpath for the host. Also validated that the annotations have the correct number of slashes. +- Updated dependencies + - @backstage/plugin-azure-devops-common@0.3.2-next.1 + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-azure-devops-common@0.3.2-next.0 + +## 0.3.10-next.1 + +### Patch Changes + +- 043b724c56: Introduced new `AzureDevOpsAnnotatorProcessor` that adds the needed annotations automatically. Also, moved constants to common package so they can be shared more easily +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-azure-devops-common@0.3.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.3.10-next.0 ### Patch Changes diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index 7eeae09813..7119cb0980 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -61,6 +61,25 @@ spec: # ... ``` +#### Mono repos + +If you have multiple entities within a single repo, you will need to specify which pipelines belong to each entity. + +```yaml +dev.azure.com/project-repo: / +dev.azure.com/build-definition: +``` + +#### Pipeline in different project to repo + +If your pipeline is in a different project to the source code, you will need to specify this in the project annotation. + +```yaml +dev.azure.com/project-repo: / +dev.azure.com/build-definition: +dev.azure.com/project: +``` + #### Azure Pipelines Only If you are only using Azure Pipelines along with a different SCM tool then you can use the following two annotations to see Builds: @@ -72,6 +91,53 @@ dev.azure.com/build-definition: In this case `` will be the name of your Team Project and `` will be the name of the Build Definition you would like to see Builds for, and it's possible to add more Builds separated by a comma. If the Build Definition name has spaces in it make sure to put quotes around it. +#### Multiple Organizations + +If you have multiple organizations you'll need to also add this annotation: + +```yaml +dev.azure.com/host-org: / +``` + +For this annotation `` will match the `host` value in the `integrations.azure` section in your `app-config.yaml` and `` will be the name of the Organization that is part of the `host`. Let's break this down with an example: + +Say we have the following `integrations.azure` section: + +```yaml +integrations: + azure: + - host: dev.azure.com + credentials: + - organizations: + - my-org + - my-other-org + clientId: ${AZURE_CLIENT_ID} + clientSecret: ${AZURE_CLIENT_SECRET} + tenantId: ${AZURE_TENANT_ID} + - organizations: + - another-org + clientId: ${AZURE_CLIENT_ID} + - host: server.company.com + credentials: + - organizations: + - yet-another-org + personalAccessToken: ${PERSONAL_ACCESS_TOKEN} +``` + +If the entity we are viewing lives in the `my-other-org` organization then the `dev.azure.com/host-org` annotation would look like this: + +```yaml +dev.azure.com/host-org: dev.azure.com/my-other-org +``` + +And if the entity was from `yet-another-org` it would look like this: + +```yaml +dev.azure.com/host-org: server.company.com/yet-another-org +``` + +**Note:** To save you time, effort, and confusion setting up these annotations manually you can use the `AzureDevOpsAnnotatorProcessor` processor which will add the `dev.azure.com/host-org` and `dev.azure.com/project-repo` annotations for you with the correct values. The Azure DevOps backend plugin has details on how to [add this processor](https://github.com/backstage/backstage/tree/master/plugins/azure-devops-backend#processor). + ### Azure Pipelines Component To get the Azure Pipelines component working you'll need to do the following two steps: @@ -247,8 +313,3 @@ To get the README component working you'll need to do the following two steps: - You'll need to add the `EntitySwitch.Case` above from step 2 to all the entity sections you want to see Readme in. For example if you wanted to see Readme when looking at Website entities then you would need to add this to the `websiteEntityPage` section. - The `if` prop is optional on the `EntitySwitch.Case`, you can remove it if you always want to see the tab even if the entity being viewed does not have the needed annotation - The `maxHeight` property on the `EntityAzureReadmeCard` will set the maximum screen size you would like to see, if not set it will default to 100% - -## Limitations - -- Currently multiple organizations are not supported -- Mixing Azure DevOps Services (cloud) and Azure DevOps Server (on-premise) is not supported diff --git a/plugins/azure-devops/api-report.md b/plugins/azure-devops/api-report.md index 6b33762639..cfff4348ee 100644 --- a/plugins/azure-devops/api-report.md +++ b/plugins/azure-devops/api-report.md @@ -22,7 +22,7 @@ import { Readme } from '@backstage/plugin-azure-devops-common'; import { ReadmeConfig } from '@backstage/plugin-azure-devops-common'; import { RepoBuild } from '@backstage/plugin-azure-devops-common'; import { RepoBuildOptions } from '@backstage/plugin-azure-devops-common'; -import { SvgIconProps } from '@material-ui/core'; +import { SvgIconProps } from '@material-ui/core/SvgIcon'; import { Team } from '@backstage/plugin-azure-devops-common'; // @public (undocumented) @@ -71,6 +71,8 @@ export interface AzureDevOpsApi { projectName: string, repoName?: string, definitionName?: string, + host?: string, + org?: string, options?: BuildRunOptions, ): Promise<{ items: BuildRun[]; @@ -83,6 +85,8 @@ export interface AzureDevOpsApi { getGitTags( projectName: string, repoName: string, + host?: string, + org?: string, ): Promise<{ items: GitTag[]; }>; @@ -90,6 +94,8 @@ export interface AzureDevOpsApi { getPullRequests( projectName: string, repoName: string, + host?: string, + org?: string, options?: PullRequestOptions, ): Promise<{ items: PullRequest[]; @@ -100,6 +106,8 @@ export interface AzureDevOpsApi { getRepoBuilds( projectName: string, repoName: string, + host?: string, + org?: string, options?: RepoBuildOptions, ): Promise<{ items: RepoBuild[]; @@ -124,6 +132,8 @@ export class AzureDevOpsClient implements AzureDevOpsApi { projectName: string, repoName?: string, definitionName?: string, + host?: string, + org?: string, options?: BuildRunOptions, ): Promise<{ items: BuildRun[]; @@ -136,6 +146,8 @@ export class AzureDevOpsClient implements AzureDevOpsApi { getGitTags( projectName: string, repoName: string, + host?: string, + org?: string, ): Promise<{ items: GitTag[]; }>; @@ -143,6 +155,8 @@ export class AzureDevOpsClient implements AzureDevOpsApi { getPullRequests( projectName: string, repoName: string, + host?: string, + org?: string, options?: PullRequestOptions, ): Promise<{ items: PullRequest[]; @@ -153,6 +167,8 @@ export class AzureDevOpsClient implements AzureDevOpsApi { getRepoBuilds( projectName: string, repoName: string, + host?: string, + org?: string, options?: RepoBuildOptions, ): Promise<{ items: RepoBuild[]; diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index 9040d51e8d..d0b5932b31 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops", - "version": "0.3.10-next.0", + "version": "0.3.12-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,10 +35,8 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-azure-devops-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "humanize-duration": "^3.27.0", "luxon": "^3.0.0", @@ -51,14 +49,11 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/azure-devops/src/api/AzureDevOpsApi.ts b/plugins/azure-devops/src/api/AzureDevOpsApi.ts index ce6a3cac39..89aa4b7d6e 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsApi.ts @@ -40,17 +40,23 @@ export interface AzureDevOpsApi { getRepoBuilds( projectName: string, repoName: string, + host?: string, + org?: string, options?: RepoBuildOptions, ): Promise<{ items: RepoBuild[] }>; getGitTags( projectName: string, repoName: string, + host?: string, + org?: string, ): Promise<{ items: GitTag[] }>; getPullRequests( projectName: string, repoName: string, + host?: string, + org?: string, options?: PullRequestOptions, ): Promise<{ items: PullRequest[] }>; @@ -66,6 +72,8 @@ export interface AzureDevOpsApi { projectName: string, repoName?: string, definitionName?: string, + host?: string, + org?: string, options?: BuildRunOptions, ): Promise<{ items: BuildRun[] }>; diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts index a202008798..76e38b064c 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts @@ -47,12 +47,20 @@ export class AzureDevOpsClient implements AzureDevOpsApi { public async getRepoBuilds( projectName: string, repoName: string, + host?: string, + org?: string, options?: RepoBuildOptions, ): Promise<{ items: RepoBuild[] }> { const queryString = new URLSearchParams(); if (options?.top) { queryString.append('top', options.top.toString()); } + if (host) { + queryString.append('host', host); + } + if (org) { + queryString.append('org', org); + } const urlSegment = `repo-builds/${encodeURIComponent( projectName, )}/${encodeURIComponent(repoName)}?${queryString}`; @@ -64,10 +72,19 @@ export class AzureDevOpsClient implements AzureDevOpsApi { public async getGitTags( projectName: string, repoName: string, + host?: string, + org?: string, ): Promise<{ items: GitTag[] }> { + const queryString = new URLSearchParams(); + if (host) { + queryString.append('host', host); + } + if (org) { + queryString.append('org', org); + } const urlSegment = `git-tags/${encodeURIComponent( projectName, - )}/${encodeURIComponent(repoName)}`; + )}/${encodeURIComponent(repoName)}?${queryString}`; const items = await this.get(urlSegment); return { items }; @@ -76,6 +93,8 @@ export class AzureDevOpsClient implements AzureDevOpsApi { public async getPullRequests( projectName: string, repoName: string, + host?: string, + org?: string, options?: PullRequestOptions, ): Promise<{ items: PullRequest[] }> { const queryString = new URLSearchParams(); @@ -85,6 +104,12 @@ export class AzureDevOpsClient implements AzureDevOpsApi { if (options?.status) { queryString.append('status', options.status.toString()); } + if (host) { + queryString.append('host', host); + } + if (org) { + queryString.append('org', org); + } const urlSegment = `pull-requests/${encodeURIComponent( projectName, )}/${encodeURIComponent(repoName)}?${queryString}`; @@ -113,12 +138,20 @@ export class AzureDevOpsClient implements AzureDevOpsApi { projectName: string, repoName?: string, definitionName?: string, + host?: string, + org?: string, options?: BuildRunOptions, ): Promise<{ items: BuildRun[] }> { const queryString = new URLSearchParams(); if (repoName) { queryString.append('repoName', repoName); } + if (host) { + queryString.append('host', host); + } + if (org) { + queryString.append('org', org); + } if (definitionName) { const definitionNames = definitionName.split(','); if (definitionNames.length > 1) { @@ -149,10 +182,17 @@ export class AzureDevOpsClient implements AzureDevOpsApi { } public async getReadme(opts: ReadmeConfig): Promise { + const queryString = new URLSearchParams(); + if (opts.host) { + queryString.append('host', opts.host); + } + if (opts.org) { + queryString.append('org', opts.org); + } return await this.get( `readme/${encodeURIComponent(opts.project)}/${encodeURIComponent( opts.repo, - )}`, + )}?${queryString}`, ); } diff --git a/plugins/azure-devops/src/components/AzureGitTagsIcon/AzureGitTagsIcon.tsx b/plugins/azure-devops/src/components/AzureGitTagsIcon/AzureGitTagsIcon.tsx index c8b63e3259..0d317e3ce1 100644 --- a/plugins/azure-devops/src/components/AzureGitTagsIcon/AzureGitTagsIcon.tsx +++ b/plugins/azure-devops/src/components/AzureGitTagsIcon/AzureGitTagsIcon.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { SvgIcon, SvgIconProps } from '@material-ui/core'; +import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon'; import React from 'react'; diff --git a/plugins/azure-devops/src/components/AzurePipelinesIcon/AzurePipelinesIcon.tsx b/plugins/azure-devops/src/components/AzurePipelinesIcon/AzurePipelinesIcon.tsx index bd19331f6e..10cdb3a905 100644 --- a/plugins/azure-devops/src/components/AzurePipelinesIcon/AzurePipelinesIcon.tsx +++ b/plugins/azure-devops/src/components/AzurePipelinesIcon/AzurePipelinesIcon.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { SvgIcon, SvgIconProps } from '@material-ui/core'; +import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon'; import React from 'react'; diff --git a/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx b/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx index 99e3f74e90..e298f8bc25 100644 --- a/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx +++ b/plugins/azure-devops/src/components/AzurePullRequestsIcon/AzurePullRequestsIcon.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { SvgIcon, SvgIconProps } from '@material-ui/core'; +import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon'; /** @public */ export const AzurePullRequestsIcon = (props: SvgIconProps) => ( diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx index 6707929a6c..09eba5f5a2 100644 --- a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx +++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx @@ -14,7 +14,8 @@ * limitations under the License. */ -import { Box, Typography } from '@material-ui/core'; +import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography'; import { BuildResult, BuildRun, diff --git a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx index c6abba55e1..f34a1929fe 100644 --- a/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx +++ b/plugins/azure-devops/src/components/GitTagTable/GitTagTable.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Box } from '@material-ui/core'; +import Box from '@material-ui/core/Box'; import { Link, ResponseErrorPanel, diff --git a/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/PullRequestStatusButtonGroup.tsx b/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/PullRequestStatusButtonGroup.tsx index f516bfb0e5..1b0b9a8afe 100644 --- a/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/PullRequestStatusButtonGroup.tsx +++ b/plugins/azure-devops/src/components/PullRequestStatusButtonGroup/PullRequestStatusButtonGroup.tsx @@ -14,7 +14,8 @@ * limitations under the License. */ -import { Button, ButtonGroup } from '@material-ui/core'; +import Button from '@material-ui/core/Button'; +import ButtonGroup from '@material-ui/core/ButtonGroup'; import { PullRequestStatus } from '@backstage/plugin-azure-devops-common'; import React from 'react'; diff --git a/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx b/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx index b37ba6e73c..d672102945 100644 --- a/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx +++ b/plugins/azure-devops/src/components/PullRequestTable/PullRequestTable.tsx @@ -14,7 +14,8 @@ * limitations under the License. */ -import { Box, Chip } from '@material-ui/core'; +import Box from '@material-ui/core/Box'; +import Chip from '@material-ui/core/Chip'; import { Link, ResponseErrorPanel, diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx index 98237cb2a3..dd9fff86b5 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestCard/PullRequestCard.tsx @@ -15,7 +15,9 @@ */ import { Avatar, Link } from '@backstage/core-components'; -import { Card, CardContent, CardHeader } from '@material-ui/core'; +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import CardHeader from '@material-ui/core/CardHeader'; import Typography from '@material-ui/core/Typography'; import { AutoCompleteIcon } from '../AutoCompleteIcon'; diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/PullRequestGrid.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/PullRequestGrid.tsx index 8b9c49beb4..0244ea3bb1 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/PullRequestGrid.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGrid/PullRequestGrid.tsx @@ -17,7 +17,7 @@ import { PullRequestGridColumn } from '../PullRequestGridColumn'; import { PullRequestGroup } from '../types'; import React from 'react'; -import { styled } from '@material-ui/core'; +import { styled } from '@material-ui/core/styles'; const GridDiv = styled('div')(({ theme }) => ({ display: 'flex', diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/PullRequestGridColumn.tsx b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/PullRequestGridColumn.tsx index 94a648188d..f06216ac76 100644 --- a/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/PullRequestGridColumn.tsx +++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/PullRequestGridColumn/PullRequestGridColumn.tsx @@ -14,7 +14,9 @@ * limitations under the License. */ -import { Paper, Typography, styled, withStyles } from '@material-ui/core'; +import Paper from '@material-ui/core/Paper'; +import Typography from '@material-ui/core/Typography'; +import { styled, withStyles } from '@material-ui/core/styles'; import { PullRequestCard } from '../PullRequestCard'; import { PullRequestGroup } from '../types'; diff --git a/plugins/azure-devops/src/components/ReadmeCard/ReadmeCard.tsx b/plugins/azure-devops/src/components/ReadmeCard/ReadmeCard.tsx index 420fe6ecf4..300c9e0d7c 100644 --- a/plugins/azure-devops/src/components/ReadmeCard/ReadmeCard.tsx +++ b/plugins/azure-devops/src/components/ReadmeCard/ReadmeCard.tsx @@ -14,7 +14,9 @@ * limitations under the License. */ -import { Box, Button, makeStyles } from '@material-ui/core'; +import Box from '@material-ui/core/Box'; +import Button from '@material-ui/core/Button'; +import { makeStyles } from '@material-ui/core/styles'; import { InfoCard, Progress, diff --git a/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx b/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx index 409602eec4..e165236fe8 100644 --- a/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx +++ b/plugins/azure-devops/src/hooks/useBuildRuns.test.tsx @@ -99,7 +99,7 @@ describe('useBuildRuns', () => { renderHook(() => useBuildRuns(entity), { wrapper: Wrapper, }), - ).toThrow('Value for annotation "dev.azure.com/project" was not found'); + ).toThrow('Expected "dev.azure.com" annotations were not found'); }); it('should return throw when annotation invalid', async () => { diff --git a/plugins/azure-devops/src/hooks/useBuildRuns.ts b/plugins/azure-devops/src/hooks/useBuildRuns.ts index 2b70ea63a5..e335d2ffaf 100644 --- a/plugins/azure-devops/src/hooks/useBuildRuns.ts +++ b/plugins/azure-devops/src/hooks/useBuildRuns.ts @@ -15,11 +15,11 @@ */ import { + AZURE_DEVOPS_DEFAULT_TOP, BuildRun, BuildRunOptions, } from '@backstage/plugin-azure-devops-common'; -import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants'; import { azureDevOpsApiRef } from '../api'; import { useApi } from '@backstage/core-plugin-api'; import useAsync from 'react-use/lib/useAsync'; @@ -42,8 +42,9 @@ export function useBuildRuns( const api = useApi(azureDevOpsApiRef); const { value, loading, error } = useAsync(() => { - const { project, repo, definition } = getAnnotationValuesFromEntity(entity); - return api.getBuildRuns(project, repo, definition, options); + const { project, repo, definition, host, org } = + getAnnotationValuesFromEntity(entity); + return api.getBuildRuns(project, repo, definition, host, org, options); }, [api]); return { diff --git a/plugins/azure-devops/src/hooks/useGitTags.test.tsx b/plugins/azure-devops/src/hooks/useGitTags.test.tsx index 1a5620e16e..1c07b8868e 100644 --- a/plugins/azure-devops/src/hooks/useGitTags.test.tsx +++ b/plugins/azure-devops/src/hooks/useGitTags.test.tsx @@ -103,7 +103,7 @@ describe('useGitTags', () => { renderHook(() => useGitTags(entity), { wrapper: Wrapper, }), - ).toThrow('Value for annotation "dev.azure.com/project" was not found'); + ).toThrow('Expected "dev.azure.com" annotations were not found'); }); it('should return throw when annotation invalid', async () => { diff --git a/plugins/azure-devops/src/hooks/useGitTags.ts b/plugins/azure-devops/src/hooks/useGitTags.ts index 8a2fb2825d..57489316da 100644 --- a/plugins/azure-devops/src/hooks/useGitTags.ts +++ b/plugins/azure-devops/src/hooks/useGitTags.ts @@ -30,8 +30,8 @@ export function useGitTags(entity: Entity): { const api = useApi(azureDevOpsApiRef); const { value, loading, error } = useAsync(() => { - const { project, repo } = getAnnotationValuesFromEntity(entity); - return api.getGitTags(project, repo as string); + const { project, repo, host, org } = getAnnotationValuesFromEntity(entity); + return api.getGitTags(project, repo as string, host, org); }, [api]); return { diff --git a/plugins/azure-devops/src/hooks/usePullRequests.test.tsx b/plugins/azure-devops/src/hooks/usePullRequests.test.tsx index 3e51c003e6..65579a2adf 100644 --- a/plugins/azure-devops/src/hooks/usePullRequests.test.tsx +++ b/plugins/azure-devops/src/hooks/usePullRequests.test.tsx @@ -102,7 +102,7 @@ describe('usePullRequests', () => { renderHook(() => usePullRequests(entity), { wrapper: Wrapper, }), - ).toThrow('Value for annotation "dev.azure.com/project" was not found'); + ).toThrow('Expected "dev.azure.com" annotations were not found'); }); it('should return throw when annotation invalid', async () => { diff --git a/plugins/azure-devops/src/hooks/usePullRequests.ts b/plugins/azure-devops/src/hooks/usePullRequests.ts index de886919eb..af2c4b1cc1 100644 --- a/plugins/azure-devops/src/hooks/usePullRequests.ts +++ b/plugins/azure-devops/src/hooks/usePullRequests.ts @@ -15,12 +15,12 @@ */ import { + AZURE_DEVOPS_DEFAULT_TOP, PullRequest, PullRequestOptions, PullRequestStatus, } from '@backstage/plugin-azure-devops-common'; -import { AZURE_DEVOPS_DEFAULT_TOP } from '../constants'; import { Entity } from '@backstage/catalog-model'; import { azureDevOpsApiRef } from '../api'; import { useApi } from '@backstage/core-plugin-api'; @@ -46,8 +46,8 @@ export function usePullRequests( const api = useApi(azureDevOpsApiRef); const { value, loading, error } = useAsync(() => { - const { project, repo } = getAnnotationValuesFromEntity(entity); - return api.getPullRequests(project, repo as string, options); + const { project, repo, host, org } = getAnnotationValuesFromEntity(entity); + return api.getPullRequests(project, repo as string, host, org, options); }, [api, top, status]); return { diff --git a/plugins/azure-devops/src/hooks/useReadme.test.tsx b/plugins/azure-devops/src/hooks/useReadme.test.tsx index ffc33c136b..fdb9080bba 100644 --- a/plugins/azure-devops/src/hooks/useReadme.test.tsx +++ b/plugins/azure-devops/src/hooks/useReadme.test.tsx @@ -81,7 +81,7 @@ describe('useReadme', () => { renderHook(() => useReadme(entity), { wrapper: Wrapper, }), - ).toThrow('Value for annotation "dev.azure.com/project" was not found'); + ).toThrow('Expected "dev.azure.com" annotations were not found'); }); it('should return throw when annotation invalid', async () => { diff --git a/plugins/azure-devops/src/hooks/useReadme.ts b/plugins/azure-devops/src/hooks/useReadme.ts index 67d3c5dfd0..348b4219f0 100644 --- a/plugins/azure-devops/src/hooks/useReadme.ts +++ b/plugins/azure-devops/src/hooks/useReadme.ts @@ -30,8 +30,8 @@ export function useReadme(entity: Entity): { const api = useApi(azureDevOpsApiRef); const { value, loading, error } = useAsync(() => { - const { project, repo } = getAnnotationValuesFromEntity(entity); - return api.getReadme({ project, repo: repo as string }); + const { project, repo, host, org } = getAnnotationValuesFromEntity(entity); + return api.getReadme({ project, repo: repo as string, host, org }); }, [api]); return { diff --git a/plugins/azure-devops/src/plugin.test.ts b/plugins/azure-devops/src/plugin.test.ts index f23639798f..d69fed6bab 100644 --- a/plugins/azure-devops/src/plugin.test.ts +++ b/plugins/azure-devops/src/plugin.test.ts @@ -13,10 +13,102 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { azureDevOpsPlugin } from './plugin'; +import { Entity } from '@backstage/catalog-model'; +import { azureDevOpsPlugin, isAzurePipelinesAvailable } from './plugin'; describe('azure-devops', () => { it('should export plugin', () => { expect(azureDevOpsPlugin).toBeDefined(); }); + + describe('isAzurePipelinesAvailable', () => { + it('should be true when project-repo annotation is present', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'sample', + annotations: { + 'dev.azure.com/project-repo': 'projectName/repoName', + }, + }, + }; + expect(isAzurePipelinesAvailable(entity)).toBe(true); + }); + + it('should be true when project and build-definition annotation is present', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'sample', + annotations: { + 'dev.azure.com/project': 'projectName', + 'dev.azure.com/build-definition': 'buildDefinitionName', + }, + }, + }; + expect(isAzurePipelinesAvailable(entity)).toBe(true); + }); + + it('should be true when project-repo and build-definition annotation is present', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'sample', + annotations: { + 'dev.azure.com/project-repo': 'projectName/repoName', + 'dev.azure.com/build-definition': 'buildDefinitionName', + }, + }, + }; + expect(isAzurePipelinesAvailable(entity)).toBe(true); + }); + + it('should be false when no annotations are present', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'sample', + }, + }; + expect(isAzurePipelinesAvailable(entity)).toBe(false); + }); + + it('should be false when only project annotation is present', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'sample', + annotations: { + 'dev.azure.com/project': 'projectName', + }, + }, + }; + expect(isAzurePipelinesAvailable(entity)).toBe(false); + }); + + it('should be false when only build-definition annotation is present', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'sample', + annotations: { + 'dev.azure.com/build-definition': 'buildDefinitionName', + }, + }, + }; + expect(isAzurePipelinesAvailable(entity)).toBe(false); + }); + }); }); diff --git a/plugins/azure-devops/src/plugin.ts b/plugins/azure-devops/src/plugin.ts index 4dd423f35a..1e76c9e53a 100644 --- a/plugins/azure-devops/src/plugin.ts +++ b/plugins/azure-devops/src/plugin.ts @@ -14,11 +14,6 @@ * limitations under the License. */ -import { - AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION, - AZURE_DEVOPS_PROJECT_ANNOTATION, - AZURE_DEVOPS_REPO_ANNOTATION, -} from './constants'; import { azurePipelinesEntityContentRouteRef, azurePullRequestDashboardRouteRef, @@ -37,6 +32,11 @@ import { import { AzureDevOpsClient } from './api/AzureDevOpsClient'; import { Entity } from '@backstage/catalog-model'; import { azureDevOpsApiRef } from './api/AzureDevOpsApi'; +import { + AZURE_DEVOPS_REPO_ANNOTATION, + AZURE_DEVOPS_PROJECT_ANNOTATION, + AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION, +} from '@backstage/plugin-azure-devops-common'; /** @public */ export const isAzureDevOpsAvailable = (entity: Entity) => diff --git a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts index 67e46cee7b..9325a79039 100644 --- a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts +++ b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts @@ -18,6 +18,22 @@ import { Entity } from '@backstage/catalog-model'; import { getAnnotationValuesFromEntity } from './getAnnotationValuesFromEntity'; describe('getAnnotationValuesFromEntity', () => { + describe('without any annotations', () => { + it('should throw annotations not found', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + }, + }; + expect(() => getAnnotationValuesFromEntity(entity)).toThrow( + 'Expected "dev.azure.com" annotations were not found', + ); + }); + }); + describe('with valid project-repo annotation', () => { it('should return project and repo', () => { const entity: Entity = { @@ -67,7 +83,7 @@ describe('getAnnotationValuesFromEntity', () => { }); describe('with project-repo annotation missing project', () => { - it('should throw missing project error', () => { + it('should throw incorrect format error', () => { const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -91,7 +107,7 @@ describe('getAnnotationValuesFromEntity', () => { }); describe('with project-repo annotation missing repo', () => { - it('should throw missing repo error', () => { + it('should throw incorrect format error', () => { const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -140,7 +156,7 @@ describe('getAnnotationValuesFromEntity', () => { }); describe('with only project annotation', () => { - it('should should throw annotation not found error', () => { + it('should throw annotation not found error', () => { const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -163,7 +179,7 @@ describe('getAnnotationValuesFromEntity', () => { }); describe('with only build-definition annotation', () => { - it('should should throw annotation not found error', () => { + it('should throw annotation not found error', () => { const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -261,7 +277,7 @@ describe('getAnnotationValuesFromEntity', () => { }); describe('with host-org annotation missing host', () => { - it('should throw missing project error', () => { + it('should throw incorrect format error', () => { const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -285,7 +301,7 @@ describe('getAnnotationValuesFromEntity', () => { }); describe('with host-org annotation missing org', () => { - it('should throw missing repo error', () => { + it('should throw incorrect format error', () => { const entity: Entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -307,4 +323,129 @@ describe('getAnnotationValuesFromEntity', () => { ); }); }); + + describe('with tfs subpath for org', () => { + it('should return host and org', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'tfs-subpath', + annotations: { + 'dev.azure.com/project-repo': 'projectName/repoName', + 'dev.azure.com/host-org': 'company.com/tfs/organizationName', + }, + }, + }; + + const values = getAnnotationValuesFromEntity(entity); + expect(values).toEqual({ + project: 'projectName', + repo: 'repoName', + definition: undefined, + host: 'company.com/tfs', + org: 'organizationName', + }); + }); + }); + + describe('host-org with more then expected slashes', () => { + it('should throw incorrect format error', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'host-org', + annotations: { + 'dev.azure.com/host-org': 'host/subpath/another-path/org/project', + }, + }, + }; + + const test = () => { + return getAnnotationValuesFromEntity(entity); + }; + + expect(test).toThrow( + 'Invalid value for annotation "dev.azure.com/host-org"; expected format is: /, found: "host/subpath/another-path/org/project"', + ); + }); + }); + + describe('project-repo with more then expected slashes', () => { + it('should throw incorrect format error', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + annotations: { + 'dev.azure.com/project-repo': 'project/another/repo/final', + }, + }, + }; + + const test = () => { + return getAnnotationValuesFromEntity(entity); + }; + + expect(test).toThrow( + 'Invalid value for annotation "dev.azure.com/project-repo"; expected format is: /, found: "project/another/repo/final"', + ); + }); + }); + + describe('projectRepo and buildDefinition are provided', () => { + it('should return project, repo and buildDefinition', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + annotations: { + 'dev.azure.com/build-definition': 'buildDefinitionName', + 'dev.azure.com/project-repo': 'projectName/repoName', + }, + }, + }; + const values = getAnnotationValuesFromEntity(entity); + expect(values).toEqual({ + project: 'projectName', + repo: 'repoName', + definition: 'buildDefinitionName', + host: undefined, + org: undefined, + }); + }); + }); + + describe('project, projectRepo and buildDefinition are provided', () => { + it('should prefer project over project-repo.project and return no repo', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + annotations: { + 'dev.azure.com/project': 'projectName', + 'dev.azure.com/build-definition': 'buildDefinitionName', + 'dev.azure.com/project-repo': 'ignoredProject/repoName', + }, + }, + }; + const values = getAnnotationValuesFromEntity(entity); + expect(values).toEqual({ + project: 'projectName', + repo: undefined, + definition: 'buildDefinitionName', + host: undefined, + org: undefined, + }); + }); + }); }); diff --git a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts index 9feec8bf95..7533c473ed 100644 --- a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts +++ b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts @@ -14,14 +14,13 @@ * limitations under the License. */ -import { - AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION, - AZURE_DEVOPS_HOST_ORG_ANNOTATION, - AZURE_DEVOPS_PROJECT_ANNOTATION, - AZURE_DEVOPS_REPO_ANNOTATION, -} from '../constants'; - import { Entity } from '@backstage/catalog-model'; +import { + AZURE_DEVOPS_PROJECT_ANNOTATION, + AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION, + AZURE_DEVOPS_REPO_ANNOTATION, + AZURE_DEVOPS_HOST_ORG_ANNOTATION, +} from '@backstage/plugin-azure-devops-common'; export function getAnnotationValuesFromEntity(entity: Entity): { project: string; @@ -30,34 +29,49 @@ export function getAnnotationValuesFromEntity(entity: Entity): { host?: string; org?: string; } { - const { host, org } = getHostOrg(entity.metadata.annotations); - - const projectRepoValues = getProjectRepo(entity.metadata.annotations); - if (projectRepoValues.project && projectRepoValues.repo) { - return { - project: projectRepoValues.project, - repo: projectRepoValues.repo, - host, - org, - }; - } - + const hostOrg = getHostOrg(entity.metadata.annotations); + const projectRepo = getProjectRepo(entity.metadata.annotations); const project = entity.metadata.annotations?.[AZURE_DEVOPS_PROJECT_ANNOTATION]; - if (!project) { + const definition = + entity.metadata.annotations?.[AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION]; + + if (definition) { + if (project) { + return { + project, + definition, + ...hostOrg, + }; + } + if (projectRepo.project) { + return { + project: projectRepo.project, + repo: projectRepo.repo, + definition, + ...hostOrg, + }; + } throw new Error( `Value for annotation "${AZURE_DEVOPS_PROJECT_ANNOTATION}" was not found`, ); + } else { + if (projectRepo.project) { + return { + project: projectRepo.project, + repo: projectRepo.repo, + ...hostOrg, + }; + } + + if (project) { + throw new Error( + `Value for annotation "${AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION}" was not found`, + ); + } } - const definition = - entity.metadata.annotations?.[AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION]; - if (!definition) { - throw new Error( - `Value for annotation "${AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION}" was not found`, - ); - } - return { project, definition, host, org }; + throw new Error('Expected "dev.azure.com" annotations were not found'); } function getProjectRepo(annotations?: Record): { @@ -69,7 +83,7 @@ function getProjectRepo(annotations?: Record): { return { project: undefined, repo: undefined }; } - if (annotation.includes('/')) { + if (annotation.split('/').length === 2) { const [project, repo] = annotation.split('/'); if (project && repo) { return { project, repo }; @@ -90,11 +104,15 @@ function getHostOrg(annotations?: Record): { return { host: undefined, org: undefined }; } - if (annotation.includes('/')) { - const [host, org] = annotation.split('/'); + const segments = annotation.split('/'); + if (segments.length === 2) { + const [host, org] = segments; if (host && org) { return { host, org }; } + } else if (segments.length === 3) { + const [host, subpath, org] = segments; + return { host: `${host}/${subpath}`, org }; } throw new Error( diff --git a/plugins/azure-sites-backend/CHANGELOG.md b/plugins/azure-sites-backend/CHANGELOG.md index 75cf7ac7a5..1e9cd2162e 100644 --- a/plugins/azure-sites-backend/CHANGELOG.md +++ b/plugins/azure-sites-backend/CHANGELOG.md @@ -1,5 +1,89 @@ # @backstage/plugin-azure-sites-backend +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.18 + +### Patch Changes + +- 99fb541: Updated dependency `@azure/identity` to `^4.0.0`. +- b7a13ed: Updated dependency `@azure/arm-appservice` to `^14.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.18-next.1 + +### Patch Changes + +- 99fb54183b: Updated dependency `@azure/identity` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-azure-sites-common@0.1.1 + ## 0.1.18-next.0 ### Patch Changes diff --git a/plugins/azure-sites-backend/README.md b/plugins/azure-sites-backend/README.md index 23152f9760..5017be8a8f 100644 --- a/plugins/azure-sites-backend/README.md +++ b/plugins/azure-sites-backend/README.md @@ -36,11 +36,11 @@ Here's how to get the backend plugin up and running: 1. First we need to add the `@backstage/plugin-azure-sites-backend` package to your backend: ```sh - # From your Backstage root directory + # From the Backstage root directory yarn add --cwd packages/backend @backstage/plugin-azure-sites-backend ``` -2. Then we will create a new file named `packages/backend/src/plugins/azure.ts`, and add the following to it: +2. Then we will create a new file named `packages/backend/src/plugins/azure-sites.ts`, and add the following to it: ```ts import { @@ -56,6 +56,7 @@ Here's how to get the backend plugin up and running: return await createRouter({ logger: env.logger, azureSitesApi: AzureSitesApi.fromConfig(env.config), + permissions: env.permissions, }); } ``` @@ -63,7 +64,7 @@ Here's how to get the backend plugin up and running: 3. Next we wire this into the overall backend router, edit `packages/backend/src/index.ts`: ```ts - import azure from './plugins/azure'; + import azureSites from './plugins/azure-sites'; // Removed for clarity... @@ -76,10 +77,34 @@ Here's how to get the backend plugin up and running: // ... // Insert this line under the other lines that add their routers to apiRouter in the same way - apiRouter.use('/azure-sites', await azure(azureSitesEnv)); + apiRouter.use('/azure-sites', await azureSites(azureSitesEnv)); } ``` -4. Now run `yarn start-backend` from the repo root. +4. Enable permissions and that the below is just an example policy that forbids anyone but the owner of the catalog entity to trigger actions towards a site tied to an entity, edit your `packages/backend/src/plugins/permission.ts` -5. Finally, open `http://localhost:7007/api/azure-sites/health` in a browser, it should return `{"status":"ok"}`. + ```diff + // packages/backend/src/plugins/permission.ts + + import { azureSitesActionPermission } from '@backstage/plugin-azure-sites-common'; + ... + class TestPermissionPolicy implements PermissionPolicy { + - async handle(): Promise { + + async handle(request: PolicyQuery, user?: BackstageIdentityResponse): Promise { + if (isPermission(request.permission, azureSitesActionPermission)) { + return createCatalogConditionalDecision( + request.permission, + catalogConditions.isEntityOwner({ + claims: user?.identity.ownershipEntityRefs ?? [], + }), + ); + } + ... + return { + result: AuthorizeResult.ALLOW, + }; + } + ``` + +5. Now run `yarn start-backend` from the repo root. + +6. Finally, open `http://localhost:7007/api/azure/health` in a browser, it should return `{"status":"ok"}`. diff --git a/plugins/azure-sites-backend/api-report.md b/plugins/azure-sites-backend/api-report.md index 751ff3f033..04cd22596a 100644 --- a/plugins/azure-sites-backend/api-report.md +++ b/plugins/azure-sites-backend/api-report.md @@ -6,9 +6,11 @@ import { AzureSiteListRequest } from '@backstage/plugin-azure-sites-common'; import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common'; import { AzureSiteStartStopRequest } from '@backstage/plugin-azure-sites-common'; +import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import express from 'express'; import { Logger } from 'winston'; +import { PermissionEvaluator } from '@backstage/plugin-permission-common'; // @public (undocumented) export class AzureSitesApi { @@ -21,6 +23,8 @@ export class AzureSitesApi { start(request: AzureSiteStartStopRequest): Promise; // (undocumented) stop(request: AzureSiteStartStopRequest): Promise; + // (undocumented) + validateSite(annotationName: string, siteName: string): Promise; } // @public (undocumented) @@ -54,7 +58,11 @@ export interface RouterOptions { // (undocumented) azureSitesApi: AzureSitesApi; // (undocumented) + catalogApi: CatalogApi; + // (undocumented) logger: Logger; + // (undocumented) + permissions: PermissionEvaluator; } // (No @packageDocumentation comment for this package) diff --git a/plugins/azure-sites-backend/package.json b/plugins/azure-sites-backend/package.json index 932df28a55..2599bfc360 100644 --- a/plugins/azure-sites-backend/package.json +++ b/plugins/azure-sites-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-sites-backend", - "version": "0.1.18-next.0", + "version": "0.1.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,16 @@ "dependencies": { "@azure/arm-appservice": "^14.0.0", "@azure/arm-resourcegraph": "^4.2.1", - "@azure/identity": "^3.2.1", + "@azure/identity": "^4.0.0", "@backstage/backend-common": "workspace:^", + "@backstage/catalog-client": "workspace:^", + "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-azure-sites-common": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", + "@backstage/plugin-permission-node": "workspace:^", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -47,8 +53,7 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@types/supertest": "^2.0.8", - "msw": "^1.0.0" + "@types/supertest": "^2.0.8" }, "files": [ "dist" diff --git a/plugins/azure-sites-backend/src/api/AzureSitesApi.ts b/plugins/azure-sites-backend/src/api/AzureSitesApi.ts index 73e92ea6a0..b8660c5989 100644 --- a/plugins/azure-sites-backend/src/api/AzureSitesApi.ts +++ b/plugins/azure-sites-backend/src/api/AzureSitesApi.ts @@ -95,4 +95,15 @@ export class AzureSitesApi { } return { items: items }; } + + async validateSite(annotationName: string, siteName: string) { + const azureSites = await this.list({ + name: annotationName, + }); + for (const site of azureSites.items) { + if (site.name === siteName) return true; + } + + return false; + } } diff --git a/plugins/azure-sites-backend/src/service/router.ts b/plugins/azure-sites-backend/src/service/router.ts index bea9a15396..9ed198cc20 100644 --- a/plugins/azure-sites-backend/src/service/router.ts +++ b/plugins/azure-sites-backend/src/service/router.ts @@ -19,19 +19,33 @@ import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; +import { InputError, NotAllowedError, NotFoundError } from '@backstage/errors'; +import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; +import { + PermissionEvaluator, + AuthorizeResult, +} from '@backstage/plugin-permission-common'; +import { + azureSitesActionPermission, + AZURE_WEB_SITE_NAME_ANNOTATION, +} from '@backstage/plugin-azure-sites-common'; +import { CatalogApi } from '@backstage/catalog-client'; + import { AzureSitesApi } from '../api'; /** @public */ export interface RouterOptions { logger: Logger; azureSitesApi: AzureSitesApi; + catalogApi: CatalogApi; + permissions: PermissionEvaluator; } /** @public */ export async function createRouter( options: RouterOptions, ): Promise { - const { logger, azureSitesApi } = options; + const { logger, azureSitesApi, permissions, catalogApi } = options; const router = Router(); router.use(express.json()); @@ -52,28 +66,116 @@ export async function createRouter( '/:subscription/:resourceGroup/:name/start', async (request, response) => { const { subscription, resourceGroup, name } = request.params; - console.log('starting...'); - response.json( - await azureSitesApi.start({ - subscription, - resourceGroup, - name, - }), + const token = getBearerTokenFromAuthorizationHeader( + request.header('authorization'), ); + const entityRef = request.body.entityRef; + if (typeof entityRef !== 'string') { + throw new InputError('Invalid entityRef, not a string'); + } + const entity = await catalogApi.getEntityByRef(entityRef, { token }); + + if (entity) { + const annotationName = + entity.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION]; + if ( + annotationName && + !(await azureSitesApi.validateSite(annotationName, name)) + ) { + throw new NotFoundError(); + } + + const decision = permissions + ? ( + await permissions.authorize( + [ + { + permission: azureSitesActionPermission, + resourceRef: entityRef, + }, + ], + { + token, + }, + ) + )[0] + : undefined; + if (decision && decision.result === AuthorizeResult.DENY) { + throw new NotAllowedError('Unauthorized'); + } + + logger.info( + `entity ${entity.metadata.name} - azure site "${name}" is starting...`, + ); + response.json( + await azureSitesApi.start({ + subscription, + resourceGroup, + name, + }), + ); + } else { + throw new NotFoundError(); + } }, ); router.post( '/:subscription/:resourceGroup/:name/stop', async (request, response) => { const { subscription, resourceGroup, name } = request.params; - console.log('stopping...'); - response.json( - await azureSitesApi.stop({ - subscription, - resourceGroup, - name, - }), + const token = getBearerTokenFromAuthorizationHeader( + request.header('authorization'), ); + + const entityRef = request.body.entityRef; + if (typeof entityRef !== 'string') { + throw new InputError('Invalid entityRef, not a string'); + } + const entity = await catalogApi.getEntityByRef(entityRef, { token }); + + if (entity) { + const annotationName = + entity.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION]; + + if ( + annotationName && + !(await azureSitesApi.validateSite(annotationName, name)) + ) { + throw new NotFoundError('annotation mismatched!'); + } + + const decision = permissions + ? ( + await permissions.authorize( + [ + { + permission: azureSitesActionPermission, + resourceRef: entityRef, + }, + ], + { + token, + }, + ) + )[0] + : undefined; + if (decision && decision.result === AuthorizeResult.DENY) { + throw new NotAllowedError('Unauthorized'); + } + + logger.info( + `entity ${entity.metadata.name} - azure site "${name}" is stopping...`, + ); + response.json( + await azureSitesApi.stop({ + subscription, + resourceGroup, + name, + }), + ); + } else { + throw new NotFoundError(); + } }, ); router.use(errorHandler()); diff --git a/plugins/azure-sites-backend/src/service/standaloneServer.ts b/plugins/azure-sites-backend/src/service/standaloneServer.ts index 1998bee023..202c1a128d 100644 --- a/plugins/azure-sites-backend/src/service/standaloneServer.ts +++ b/plugins/azure-sites-backend/src/service/standaloneServer.ts @@ -17,11 +17,15 @@ import { createServiceBuilder, loadBackendConfig, + ServerTokenManager, + SingleHostDiscovery, } from '@backstage/backend-common'; import { Server } from 'http'; import { Logger } from 'winston'; import { AzureSitesApi } from '../api'; import { createRouter } from './router'; +import { ServerPermissionClient } from '@backstage/plugin-permission-node'; +import { CatalogClient } from '@backstage/catalog-client'; export interface ServerOptions { port: number; @@ -34,10 +38,21 @@ export async function startStandaloneServer( ): Promise { const logger = options.logger.child({ service: 'azure-backend' }); const config = await loadBackendConfig({ logger, argv: process.argv }); + const discovery = SingleHostDiscovery.fromConfig(config); + const tokenManager = ServerTokenManager.fromConfig(config, { + logger, + }); + const permissions = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager, + }); + const catalogApi = new CatalogClient({ discoveryApi: discovery }); logger.debug('Starting application server...'); const router = await createRouter({ logger, + permissions, azureSitesApi: AzureSitesApi.fromConfig(config), + catalogApi, }); let service = createServiceBuilder(module) diff --git a/plugins/azure-sites-common/api-report.md b/plugins/azure-sites-common/api-report.md index 64b4275085..e3f582107e 100644 --- a/plugins/azure-sites-common/api-report.md +++ b/plugins/azure-sites-common/api-report.md @@ -3,6 +3,11 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { ResourcePermission } from '@backstage/plugin-permission-common'; + +// @public (undocumented) +export const AZURE_WEB_SITE_NAME_ANNOTATION = 'azure.com/microsoft-web-sites'; + // @public (undocumented) export type AzureSite = { href: string; @@ -19,6 +24,14 @@ export type AzureSite = { tags: {}; }; +// @public (undocumented) +export type AzureSiteBackendRequest = { + subscription: string; + resourceGroup: string; + name: string; + entityRef: string; +}; + // @public (undocumented) export type AzureSiteListRequest = { name: string; @@ -29,6 +42,12 @@ export type AzureSiteListResponse = { items: AzureSite[]; }; +// @public (undocumented) +export const azureSitesActionPermission: ResourcePermission<'catalog-entity'>; + +// @public (undocumented) +export const azureSitesPermissions: ResourcePermission<'catalog-entity'>[]; + // @public (undocumented) export type AzureSiteStartStopRequest = { subscription: string; diff --git a/plugins/azure-sites-common/package.json b/plugins/azure-sites-common/package.json index 05815edd02..0365daa844 100644 --- a/plugins/azure-sites-common/package.json +++ b/plugins/azure-sites-common/package.json @@ -32,6 +32,11 @@ "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack" }, + "dependencies": { + "@backstage/catalog-model": "workspace:^", + "@backstage/plugin-catalog-common": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^" + }, "devDependencies": { "@backstage/cli": "workspace:^" }, diff --git a/plugins/azure-sites-common/src/index.ts b/plugins/azure-sites-common/src/index.ts index db229eae34..999a9c713c 100644 --- a/plugins/azure-sites-common/src/index.ts +++ b/plugins/azure-sites-common/src/index.ts @@ -15,3 +15,5 @@ */ export * from './types'; +export * from './utils'; +export * from './permissions'; diff --git a/plugins/azure-sites-common/src/permissions.ts b/plugins/azure-sites-common/src/permissions.ts new file mode 100644 index 0000000000..03b50f3edc --- /dev/null +++ b/plugins/azure-sites-common/src/permissions.ts @@ -0,0 +1,31 @@ +/* + * 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 { createPermission } from '@backstage/plugin-permission-common'; +import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common/alpha'; + +/** + * @public + */ +export const azureSitesActionPermission = createPermission({ + name: 'azure.sites.update', + attributes: { action: 'update' }, + resourceType: RESOURCE_TYPE_CATALOG_ENTITY, +}); + +/** + * @public + */ +export const azureSitesPermissions = [azureSitesActionPermission]; diff --git a/plugins/azure-sites-common/src/types.ts b/plugins/azure-sites-common/src/types.ts index 7c8be7ce24..4273085aab 100644 --- a/plugins/azure-sites-common/src/types.ts +++ b/plugins/azure-sites-common/src/types.ts @@ -46,3 +46,11 @@ export type AzureSiteStartStopRequest = { resourceGroup: string; name: string; }; + +/** @public */ +export type AzureSiteBackendRequest = { + subscription: string; + resourceGroup: string; + name: string; + entityRef: string; +}; diff --git a/plugins/azure-sites-common/src/utils.ts b/plugins/azure-sites-common/src/utils.ts new file mode 100644 index 0000000000..017f38a380 --- /dev/null +++ b/plugins/azure-sites-common/src/utils.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * @public + */ +export const AZURE_WEB_SITE_NAME_ANNOTATION = 'azure.com/microsoft-web-sites'; diff --git a/plugins/azure-sites/CHANGELOG.md b/plugins/azure-sites/CHANGELOG.md index 3c36d729f3..66a6d4ecaa 100644 --- a/plugins/azure-sites/CHANGELOG.md +++ b/plugins/azure-sites/CHANGELOG.md @@ -1,5 +1,108 @@ # @backstage/plugin-azure-sites +## 0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.18 + +### Patch Changes + +- a31f688: Show Azure site tags in `EntityAzureSitesOverviewWidget`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.18-next.1 + +### Patch Changes + +- a31f688: Show Azure site tags in `EntityAzureSitesOverviewWidget`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.18-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-azure-sites-common@0.1.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-azure-sites-common@0.1.1 + +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-azure-sites-common@0.1.1 + ## 0.1.17-next.0 ### Patch Changes diff --git a/plugins/azure-sites/README.md b/plugins/azure-sites/README.md index 57bf84ccc5..a19b314b77 100644 --- a/plugins/azure-sites/README.md +++ b/plugins/azure-sites/README.md @@ -14,7 +14,7 @@ The following sections will help you get the Azure plugin setup and running ### Azure Sites Backend -You need to set up the Azure Sites Backend plugin before you move forward with any of these steps if you haven't already. +You need to set up the [Azure Sites Backend plugin](https://github.com/backstage/backstage/tree/master/plugins/azure-sites-backend) before you move forward with any of these steps if you haven't already. ### Entity Annotation diff --git a/plugins/azure-sites/api-report.md b/plugins/azure-sites/api-report.md index abc10ec723..92e785caa2 100644 --- a/plugins/azure-sites/api-report.md +++ b/plugins/azure-sites/api-report.md @@ -6,9 +6,9 @@ /// import { ApiRef } from '@backstage/core-plugin-api'; +import { AzureSiteBackendRequest } from '@backstage/plugin-azure-sites-common'; import { AzureSiteListRequest } from '@backstage/plugin-azure-sites-common'; import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common'; -import { AzureSiteStartStopRequest } from '@backstage/plugin-azure-sites-common'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; @@ -23,8 +23,8 @@ export const azureSiteApiRef: ApiRef; // @public (undocumented) export type AzureSitesApi = { list: (request: AzureSiteListRequest) => Promise; - start: (request: AzureSiteStartStopRequest) => Promise; - stop: (request: AzureSiteStartStopRequest) => Promise; + start: (request: AzureSiteBackendRequest) => Promise; + stop: (request: AzureSiteBackendRequest) => Promise; }; // @public (undocumented) @@ -36,9 +36,9 @@ export class AzureSitesApiBackendClient implements AzureSitesApi { // (undocumented) list(request: AzureSiteListRequest): Promise; // (undocumented) - start(request: AzureSiteStartStopRequest): Promise; + start(request: AzureSiteBackendRequest): Promise; // (undocumented) - stop(request: AzureSiteStartStopRequest): Promise; + stop(request: AzureSiteBackendRequest): Promise; } // @public (undocumented) diff --git a/plugins/azure-sites/package.json b/plugins/azure-sites/package.json index 631d368bc8..c9777e73da 100644 --- a/plugins/azure-sites/package.json +++ b/plugins/azure-sites/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-sites", - "version": "0.1.17-next.0", + "version": "0.1.19-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -38,6 +38,8 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-azure-sites-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", + "@backstage/plugin-permission-react": "workspace:^", "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -53,13 +55,11 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0" }, "files": [ diff --git a/plugins/azure-sites/src/api/AzureSitesApi.ts b/plugins/azure-sites/src/api/AzureSitesApi.ts index 87a3b46a8d..067b2836a4 100644 --- a/plugins/azure-sites/src/api/AzureSitesApi.ts +++ b/plugins/azure-sites/src/api/AzureSitesApi.ts @@ -18,7 +18,7 @@ import { createApiRef } from '@backstage/core-plugin-api'; import { AzureSiteListRequest, AzureSiteListResponse, - AzureSiteStartStopRequest, + AzureSiteBackendRequest, } from '@backstage/plugin-azure-sites-common'; /** @public */ @@ -29,6 +29,6 @@ export const azureSiteApiRef = createApiRef({ /** @public */ export type AzureSitesApi = { list: (request: AzureSiteListRequest) => Promise; - start: (request: AzureSiteStartStopRequest) => Promise; - stop: (request: AzureSiteStartStopRequest) => Promise; + start: (request: AzureSiteBackendRequest) => Promise; + stop: (request: AzureSiteBackendRequest) => Promise; }; diff --git a/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts b/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts index b66a3cbb3f..59158df41a 100644 --- a/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts +++ b/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts @@ -18,7 +18,7 @@ import { AzureSitesApi } from './AzureSitesApi'; import { AzureSiteListRequest, AzureSiteListResponse, - AzureSiteStartStopRequest, + AzureSiteBackendRequest, } from '@backstage/plugin-azure-sites-common'; import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; @@ -34,30 +34,38 @@ export class AzureSitesApiBackendClient implements AzureSitesApi { this.identityApi = options.identityApi; } - async stop(request: AzureSiteStartStopRequest): Promise { + async stop(request: AzureSiteBackendRequest): Promise { const url = `${await this.discoveryApi.getBaseUrl('azure-sites')}/${ request.subscription }/${request.resourceGroup}/${request.name}/stop`; const { token: accessToken } = await this.identityApi.getCredentials(); + const entityRef = request.entityRef; await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(accessToken && { Authorization: `Bearer ${accessToken}` }), }, + body: JSON.stringify({ + entityRef, + }), }); } - async start(request: AzureSiteStartStopRequest): Promise { + async start(request: AzureSiteBackendRequest): Promise { const url = `${await this.discoveryApi.getBaseUrl('azure-sites')}/${ request.subscription }/${request.resourceGroup}/${request.name}/start`; const { token: accessToken } = await this.identityApi.getCredentials(); + const entityRef = request.entityRef; await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(accessToken && { Authorization: `Bearer ${accessToken}` }), }, + body: JSON.stringify({ + entityRef, + }), }); } diff --git a/plugins/azure-sites/src/components/AzureSitesOverviewComponent/AzureSitesOverview.tsx b/plugins/azure-sites/src/components/AzureSitesOverviewComponent/AzureSitesOverview.tsx index dbed6cdbd6..71e5639da2 100644 --- a/plugins/azure-sites/src/components/AzureSitesOverviewComponent/AzureSitesOverview.tsx +++ b/plugins/azure-sites/src/components/AzureSitesOverviewComponent/AzureSitesOverview.tsx @@ -17,11 +17,9 @@ import React from 'react'; import { Entity } from '@backstage/catalog-model'; import { useSites } from '../../hooks/useSites'; -import { - AZURE_WEB_SITE_NAME_ANNOTATION, - useServiceEntityAnnotations, -} from '../../hooks/useServiceEntityAnnotations'; import { ErrorBoundary, ResponseErrorPanel } from '@backstage/core-components'; +import { useServiceEntityAnnotations } from '../../hooks/useServiceEntityAnnotations'; +import { AZURE_WEB_SITE_NAME_ANNOTATION } from '@backstage/plugin-azure-sites-common'; import { useEntity, MissingAnnotationEmptyState, diff --git a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx index 5d1a5ee73b..7676169551 100644 --- a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx +++ b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx @@ -21,6 +21,11 @@ import { errorApiRef, identityApiRef, } from '@backstage/core-plugin-api'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { + PermissionApi, + permissionApiRef, +} from '@backstage/plugin-permission-react'; import { rest } from 'msw'; import { renderInTestApp, @@ -41,15 +46,39 @@ const identityApiMock = (getCredentials: any) => ({ }); const azureSitesApiMock = {}; +jest.mock('@backstage/plugin-catalog-react', () => ({ + useEntity: () => { + return { loading: false, entity: undefined }; + }, +})); + +jest.mock('@backstage/plugin-catalog-react/alpha', () => ({ + useEntityPermission: () => { + return { loading: false, allowed: true }; + }, +})); + +jest.mock('@backstage/catalog-model', () => ({ + stringifyEntityRef: () => { + return {}; + }, +})); + const config = { getString: (_: string) => 'https://test-url', }; +const mockAuthorize = jest + .fn() + .mockImplementation(async () => ({ result: AuthorizeResult.ALLOW })); +const permissionApi: Partial = { authorize: mockAuthorize }; + const apis: [AnyApiRef, Partial][] = [ [errorApiRef, errorApiMock], [configApiRef, config], [azureSiteApiRef, azureSitesApiMock], [identityApiRef, identityApiMock], + [permissionApiRef, permissionApi], ]; describe('AzureSitesOverviewWidget', () => { diff --git a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx index 44f5f1697d..2f891ec3d2 100644 --- a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx +++ b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx @@ -18,6 +18,7 @@ import React, { Dispatch, useEffect, useState } from 'react'; import { Box, Card, + Chip, IconButton, LinearProgress, Menu, @@ -26,7 +27,10 @@ import { Tooltip, } from '@material-ui/core'; import { default as MuiAlert } from '@material-ui/lab/Alert'; -import { AzureSite } from '@backstage/plugin-azure-sites-common'; +import { + AzureSite, + azureSitesActionPermission, +} from '@backstage/plugin-azure-sites-common'; import { Table, TableColumn, Link } from '@backstage/core-components'; import { useTheme } from '@material-ui/core/styles'; import FlashOnIcon from '@material-ui/icons/FlashOn'; @@ -39,6 +43,9 @@ import OpenInNewIcon from '@material-ui/icons/OpenInNew'; import { DateTime } from 'luxon'; import { useApi } from '@backstage/core-plugin-api'; import { azureSiteApiRef } from '../../api'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha'; +import { stringifyEntityRef } from '@backstage/catalog-model'; type States = 'Waiting' | 'Running' | 'Paused' | 'Failed' | 'Stopped'; type Kinds = 'app' | 'functionapp'; @@ -91,6 +98,18 @@ const Kind = ({ value }: { value: Kinds }) => { ); }; +const Tags = ({ tags }: { tags: any }) => { + return Object.keys(tags).map((key: any) => ( + + )); +}; + type TableProps = { data: AzureSite[]; loading: boolean; @@ -104,6 +123,8 @@ const ActionButtons = ({ onMenuItemClick: Dispatch>; }) => { const azureApi = useApi(azureSiteApiRef); + const { entity } = useEntity(); + const entityRef = stringifyEntityRef(entity); const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); @@ -119,6 +140,7 @@ const ActionButtons = ({ name: value.name, resourceGroup: value.resourceGroup, subscription: value.subscription, + entityRef: entityRef, }); onMenuItemClick('Starting, this may take some time...'); handleClose(); @@ -128,11 +150,15 @@ const ActionButtons = ({ name: value.name, resourceGroup: value.resourceGroup, subscription: value.subscription, + entityRef: entityRef, }); onMenuItemClick('Stopping, this may take some time...'); handleClose(); }; + const { loading: loadingPermission, allowed: canDoAction } = + useEntityPermission(azureSitesActionPermission); + return (
    - {value.state !== 'Running' && ( - + {value.state !== 'Running' && !loadingPermission && ( +  Start )} - {value.state !== 'Stopped' && ( - + {value.state !== 'Stopped' && !loadingPermission && ( +  Stop @@ -223,6 +249,11 @@ export const AzureSitesOverviewTable = ({ data, loading }: TableProps) => { field: 'status', render: (func: AzureSite) => , }, + { + title: 'Tags', + field: 'tags', + render: (func: AzureSite) => (func.tags ? : ''), + }, { title: 'last modified', field: 'lastModifiedDate', diff --git a/plugins/azure-sites/src/hooks/useServiceEntityAnnotations.ts b/plugins/azure-sites/src/hooks/useServiceEntityAnnotations.ts index 08070dc8e4..1695749640 100644 --- a/plugins/azure-sites/src/hooks/useServiceEntityAnnotations.ts +++ b/plugins/azure-sites/src/hooks/useServiceEntityAnnotations.ts @@ -15,8 +15,8 @@ */ import { Entity } from '@backstage/catalog-model'; +import { AZURE_WEB_SITE_NAME_ANNOTATION } from '@backstage/plugin-azure-sites-common'; -export const AZURE_WEB_SITE_NAME_ANNOTATION = 'azure.com/microsoft-web-sites'; export const useServiceEntityAnnotations = (entity: Entity) => { const webSiteName = entity?.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION] ?? ''; diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md index aab01f0504..61fd11ec9e 100644 --- a/plugins/badges-backend/CHANGELOG.md +++ b/plugins/badges-backend/CHANGELOG.md @@ -1,5 +1,120 @@ # @backstage/plugin-badges-backend +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.3.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.3.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.3.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.3.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index 657ad01b09..cf9051198e 100644 --- a/plugins/badges-backend/package.json +++ b/plugins/badges-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges-backend", "description": "A Backstage backend plugin that generates README badges for your entities", - "version": "0.3.5-next.0", + "version": "0.3.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -55,8 +55,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/catalog-client": "workspace:^", - "@backstage/cli": "workspace:^", - "cross-fetch": "^4.0.0" + "@backstage/cli": "workspace:^" }, "files": [ "dist", diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md index 0d1a0856c9..4a85075f2f 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,106 @@ # @backstage/plugin-badges +## 0.2.54-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## 0.2.53 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.53-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.2.53-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.53-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.2.52 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.52-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.2.52-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.2.52-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.2.52-next.0 ### Patch Changes diff --git a/plugins/badges/package.json b/plugins/badges/package.json index ff09805301..3d8e7d101d 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges", "description": "A Backstage plugin that generates README badges for your entities", - "version": "0.2.52-next.0", + "version": "0.2.54-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,10 +35,7 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "react-use": "^17.2.4" }, @@ -49,7 +46,6 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0" diff --git a/plugins/bazaar-backend/CHANGELOG.md b/plugins/bazaar-backend/CHANGELOG.md index ca50ba2d48..9cf47046c5 100644 --- a/plugins/bazaar-backend/CHANGELOG.md +++ b/plugins/bazaar-backend/CHANGELOG.md @@ -1,5 +1,100 @@ # @backstage/plugin-bazaar-backend +## 0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + +## 0.3.7 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + +## 0.3.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.3.7-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.3.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.3.6-next.0 ### Patch Changes diff --git a/plugins/bazaar-backend/alpha-api-report.md b/plugins/bazaar-backend/api-report-alpha.md similarity index 100% rename from plugins/bazaar-backend/alpha-api-report.md rename to plugins/bazaar-backend/api-report-alpha.md diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index f1deae24d8..1dfa398e26 100644 --- a/plugins/bazaar-backend/package.json +++ b/plugins/bazaar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar-backend", - "version": "0.3.6-next.0", + "version": "0.3.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -44,7 +44,6 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", - "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@types/express": "^4.17.6", "express": "^4.17.1", diff --git a/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts b/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts index 4e46581240..39fa01ed3f 100644 --- a/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts +++ b/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts @@ -36,7 +36,7 @@ jest.setTimeout(60_000); describe('DatabaseHandler', () => { const databases = TestDatabases.create({ - ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3', 'MYSQL_8'], + ids: ['POSTGRES_16', 'POSTGRES_12', 'SQLITE_3', 'MYSQL_8'], }); function createDatabaseManager( diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md index 8dff109d9f..0f7122a74d 100644 --- a/plugins/bazaar/CHANGELOG.md +++ b/plugins/bazaar/CHANGELOG.md @@ -1,5 +1,116 @@ # @backstage/plugin-bazaar +## 0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## 0.2.21 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.21-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.2.20 + +### Patch Changes + +- 5d79682: Internalize 'AboutField' to break catalog dependency +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.20-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.2.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.2.20-next.2 + +### Patch Changes + +- 5d796829bb: Internalize 'AboutField' to break catalog dependency +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.2.20-next.1 ### Patch Changes diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index 9c33e28395..dd7629a14f 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar", - "version": "0.2.20-next.1", + "version": "0.2.22-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,9 +34,7 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", - "@backstage/plugin-catalog": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@date-io/luxon": "1.x", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/bazaar/src/components/CardContentFields/AboutField.tsx b/plugins/bazaar/src/components/CardContentFields/AboutField.tsx new file mode 100644 index 0000000000..e8c49bf4d7 --- /dev/null +++ b/plugins/bazaar/src/components/CardContentFields/AboutField.tsx @@ -0,0 +1,72 @@ +/* + * 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 { useElementFilter } from '@backstage/core-plugin-api'; +import { Grid, makeStyles, Typography } from '@material-ui/core'; +import React from 'react'; + +const useStyles = makeStyles(theme => ({ + value: { + fontWeight: 'bold', + overflow: 'hidden', + lineHeight: '24px', + wordBreak: 'break-word', + }, + label: { + color: theme.palette.text.secondary, + textTransform: 'uppercase', + fontSize: '10px', + fontWeight: 'bold', + letterSpacing: 0.5, + overflow: 'hidden', + whiteSpace: 'nowrap', + }, +})); + +/** + * Props for {@link AboutField}. + */ +export interface AboutFieldProps { + label: string; + value?: string; + gridSizes?: Record; + children?: React.ReactNode; +} + +export function AboutField(props: AboutFieldProps) { + const { label, value, gridSizes, children } = props; + const classes = useStyles(); + + const childElements = useElementFilter(children, c => c.getElements()); + + // Content is either children or a string prop `value` + const content = + childElements.length > 0 ? ( + childElements + ) : ( + + {value || `unknown`} + + ); + return ( + + + {label} + + {content} + + ); +} diff --git a/plugins/bazaar/src/components/CardContentFields/CardContentFields.tsx b/plugins/bazaar/src/components/CardContentFields/CardContentFields.tsx index 82ab9df89d..f252efebee 100644 --- a/plugins/bazaar/src/components/CardContentFields/CardContentFields.tsx +++ b/plugins/bazaar/src/components/CardContentFields/CardContentFields.tsx @@ -25,8 +25,8 @@ import { import { parseEntityRef } from '@backstage/catalog-model'; import { Avatar, Link } from '@backstage/core-components'; import { useRouteRef } from '@backstage/core-plugin-api'; -import { AboutField } from '@backstage/plugin-catalog'; import { entityRouteRef } from '@backstage/plugin-catalog-react'; +import { AboutField } from './AboutField'; import { StatusTag } from '../StatusTag'; import { Member, BazaarProject } from '../../types'; diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 58bfdcea30..f175f210ed 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.2.16-next.0 + +### Patch Changes + +- 2e6af00: Updated dependency `ts-morph` to `^21.0.0`. +- Updated dependencies + - @backstage/integration@1.8.0 + +## 0.2.15 + +### Patch Changes + +- acf9390: Updated dependency `ts-morph` to `^20.0.0`. +- Updated dependencies + - @backstage/integration@1.8.0 + +## 0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + ## 0.2.15-next.0 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/api-report.md b/plugins/bitbucket-cloud-common/api-report.md index 95e6f36836..0f4e82d5d1 100644 --- a/plugins/bitbucket-cloud-common/api-report.md +++ b/plugins/bitbucket-cloud-common/api-report.md @@ -271,9 +271,7 @@ export namespace Models { description?: string; fork_policy?: RepositoryForkPolicyEnum; full_name?: string; - // (undocumented) has_issues?: boolean; - // (undocumented) has_wiki?: boolean; // (undocumented) is_private?: boolean; diff --git a/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json b/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json index 9de3c5f9b3..d897d83047 100644 --- a/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json +++ b/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json @@ -499,7 +499,7 @@ "/hook_events": { "get": { "tags": ["Webhooks"], - "description": "Returns the webhook resource or subject types on which webhooks can\nbe registered.\n\nEach resource/subject type contains an `events` link that returns the\npaginated list of specific events each individual subject type can\nemit.\n\nThis endpoint is publicly accessible and does not require\nauthentication or scopes.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/hook_events\n\n{\n \"repository\": {\n \"links\": {\n \"events\": {\n \"href\": \"https://api.bitbucket.org/2.0/hook_events/repository\"\n }\n }\n },\n \"workspace\": {\n \"links\": {\n \"events\": {\n \"href\": \"https://api.bitbucket.org/2.0/hook_events/workspace\"\n }\n }\n }\n}\n```", + "description": "Returns the webhook resource or subject types on which webhooks can\nbe registered.\n\nEach resource/subject type contains an `events` link that returns the\npaginated list of specific events each individual subject type can\nemit.\n\nThis endpoint is publicly accessible and does not require\nauthentication or scopes.", "summary": "Get a webhook resource", "responses": { "200": { @@ -508,6 +508,26 @@ "application/json": { "schema": { "$ref": "#/components/schemas/subject_types" + }, + "examples": { + "response": { + "value": { + "repository": { + "links": { + "events": { + "href": "https://api.bitbucket.org/2.0/hook_events/repository" + } + } + }, + "workspace": { + "links": { + "events": { + "href": "https://api.bitbucket.org/2.0/hook_events/workspace" + } + } + } + } + } } } } @@ -530,7 +550,7 @@ "/hook_events/{subject_type}": { "get": { "tags": ["Webhooks"], - "description": "Returns a paginated list of all valid webhook events for the\nspecified entity.\n**The team and user webhooks are deprecated, and you should use workspace instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**\n\nThis is public data that does not require any scopes or authentication.\n\nExample:\n\nNOTE: The following example is a truncated response object for the `workspace` `subject_type`.\nWe return the same structure for the other `subject_type` objects.\n\n```\n$ curl https://api.bitbucket.org/2.0/hook_events/workspace\n{\n \"page\": 1,\n \"pagelen\": 30,\n \"size\": 21,\n \"values\": [\n {\n \"category\": \"Repository\",\n \"description\": \"Whenever a repository push occurs\",\n \"event\": \"repo:push\",\n \"label\": \"Push\"\n },\n {\n \"category\": \"Repository\",\n \"description\": \"Whenever a repository fork occurs\",\n \"event\": \"repo:fork\",\n \"label\": \"Fork\"\n },\n {\n \"category\": \"Repository\",\n \"description\": \"Whenever a repository import occurs\",\n \"event\": \"repo:imported\",\n \"label\": \"Import\"\n },\n ...\n {\n \"category\":\"Pull Request\",\n \"label\":\"Approved\",\n \"description\":\"When someone has approved a pull request\",\n \"event\":\"pullrequest:approved\"\n },\n ]\n}\n```", + "description": "Returns a paginated list of all valid webhook events for the\nspecified entity.\n**The team and user webhooks are deprecated, and you should use workspace instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**\n\nThis is public data that does not require any scopes or authentication.\n\nNOTE: The example response is a truncated response object for the `workspace` `subject_type`.\nWe return the same structure for the other `subject_type` objects.", "summary": "List subscribable webhook types", "responses": { "200": { @@ -539,6 +559,41 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_hook_events" + }, + "examples": { + "response": { + "value": { + "page": 1, + "pagelen": 30, + "size": 4, + "values": [ + { + "category": "Repository", + "description": "Whenever a repository push occurs", + "event": "repo:push", + "label": "Push" + }, + { + "category": "Repository", + "description": "Whenever a repository fork occurs", + "event": "repo:fork", + "label": "Fork" + }, + { + "category": "Repository", + "description": "Whenever a repository import occurs", + "event": "repo:imported", + "label": "Import" + }, + { + "category": "Pull Request", + "label": "Approved", + "description": "When someone has approved a pull request", + "event": "pullrequest:approved" + } + ] + } + } } } } @@ -788,6 +843,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -853,6 +915,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["delete:repository:bitbucket"] + } ] }, "get": { @@ -901,6 +970,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "post": { @@ -959,6 +1035,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "put": { @@ -1043,6 +1126,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "parameters": [ @@ -1143,11 +1233,18 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "post": { "tags": ["Branch restrictions"], - "description": "Creates a new branch restriction rule for a repository.\n\n`kind` describes what will be restricted. Allowed values include:\n`push`, `force`, `delete` and `restrict_merges`.\n\nDifferent kinds of branch restrictions have different requirements:\n\n* `push` and `restrict_merges` require `users` and `groups` to be\n specified. Empty lists are allowed, in which case permission is\n denied for everybody.\n\nThe restriction applies to all branches that match. There are\ntwo ways to match a branch. It is configured in `branch_match_kind`:\n\n1. `glob`: Matches a branch against the `pattern`. A `'*'` in\n `pattern` will expand to match zero or more characters, and every\n other character matches itself. For example, `'foo*'` will match\n `'foo'` and `'foobar'`, but not `'barfoo'`. `'*'` will match all\n branches.\n2. `branching_model`: Matches a branch against the repository's\n branching model. The `branch_type` controls the type of branch\n to match. Allowed values include: `production`, `development`,\n `bugfix`, `release`, `feature` and `hotfix`.\n\nThe combination of `kind` and match must be unique. This means that\ntwo `glob` restrictions in a repository cannot have the same `kind` and\n`pattern`. Additionally, two `branching_model` restrictions in a\nrepository cannot have the same `kind` and `branch_type`.\n\n`users` and `groups` are lists of users and groups that are except from\nthe restriction. They can only be configured in `push` and\n`restrict_merges` restrictions. The `push` restriction stops a user\npushing to matching branches unless that user is in `users` or is a\nmember of a group in `groups`. The `restrict_merges` stops a user\nmerging pull requests to matching branches unless that user is in\n`users` or is a member of a group in `groups`. Adding new users or\ngroups to an existing restriction should be done via `PUT`.\n\nNote that branch restrictions with overlapping matchers is allowed,\nbut the resulting behavior may be surprising.", + "description": "Creates a new branch restriction rule for a repository.\n\n`kind` describes what will be restricted. Allowed values include:\n`push`, `force`, `delete`, `restrict_merges`, `require_tasks_to_be_completed`,\n`require_approvals_to_merge`, `require_default_reviewer_approvals_to_merge`,\n`require_no_changes_requested`, `require_passing_builds_to_merge`, `require_commits_behind`,\n`reset_pullrequest_approvals_on_change`, `smart_reset_pullrequest_approvals`,\n`reset_pullrequest_changes_requested_on_change`, `require_all_dependencies_merged`,\n`enforce_merge_checks`, and `allow_auto_merge_when_builds_pass`.\n\nDifferent kinds of branch restrictions have different requirements:\n\n* `push` and `restrict_merges` require `users` and `groups` to be\n specified. Empty lists are allowed, in which case permission is\n denied for everybody.\n\nThe restriction applies to all branches that match. There are\ntwo ways to match a branch. It is configured in `branch_match_kind`:\n\n1. `glob`: Matches a branch against the `pattern`. A `'*'` in\n `pattern` will expand to match zero or more characters, and every\n other character matches itself. For example, `'foo*'` will match\n `'foo'` and `'foobar'`, but not `'barfoo'`. `'*'` will match all\n branches.\n2. `branching_model`: Matches a branch against the repository's\n branching model. The `branch_type` controls the type of branch\n to match. Allowed values include: `production`, `development`,\n `bugfix`, `release`, `feature` and `hotfix`.\n\nThe combination of `kind` and match must be unique. This means that\ntwo `glob` restrictions in a repository cannot have the same `kind` and\n`pattern`. Additionally, two `branching_model` restrictions in a\nrepository cannot have the same `kind` and `branch_type`.\n\n`users` and `groups` are lists of users and groups that are except from\nthe restriction. They can only be configured in `push` and\n`restrict_merges` restrictions. The `push` restriction stops a user\npushing to matching branches unless that user is in `users` or is a\nmember of a group in `groups`. The `restrict_merges` stops a user\nmerging pull requests to matching branches unless that user is in\n`users` or is a member of a group in `groups`. Adding new users or\ngroups to an existing restriction should be done via `PUT`.\n\nNote that branch restrictions with overlapping matchers is allowed,\nbut the resulting behavior may be surprising.", "summary": "Create a branch restriction rule", "responses": { "201": { @@ -1212,6 +1309,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "parameters": [ @@ -1285,6 +1389,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "get": { @@ -1343,6 +1454,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "put": { @@ -1412,6 +1530,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "parameters": [ @@ -1447,7 +1572,7 @@ "/repositories/{workspace}/{repo_slug}/branching-model": { "get": { "tags": ["Branching model"], - "description": "Return the branching model as applied to the repository. This view is\nread-only. The branching model settings can be changed using the\n[settings](#api-repositories-workspace-repo-slug-branching-model-settings-get) API.\n\nThe returned object:\n\n1. Always has a `development` property. `development.branch` contains\n the actual repository branch object that is considered to be the\n `development` branch. `development.branch` will not be present\n if it does not exist.\n2. Might have a `production` property. `production` will not\n be present when `production` is disabled.\n `production.branch` contains the actual branch object that is\n considered to be the `production` branch. `production.branch` will\n not be present if it does not exist.\n3. Always has a `branch_types` array which contains all enabled branch\n types.\n\nExample body:\n\n```\n{\n \"development\": {\n \"name\": \"master\",\n \"branch\": {\n \"type\": \"branch\",\n \"name\": \"master\",\n \"target\": {\n \"hash\": \"16dffcb0de1b22e249db6799532074cf32efe80f\"\n }\n },\n \"use_mainbranch\": true\n },\n \"production\": {\n \"name\": \"production\",\n \"branch\": {\n \"type\": \"branch\",\n \"name\": \"production\",\n \"target\": {\n \"hash\": \"16dffcb0de1b22e249db6799532074cf32efe80f\"\n }\n },\n \"use_mainbranch\": false\n },\n \"branch_types\": [\n {\n \"kind\": \"release\",\n \"prefix\": \"release/\"\n },\n {\n \"kind\": \"hotfix\",\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"feature\",\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"bugfix\",\n \"prefix\": \"bugfix/\"\n }\n ],\n \"type\": \"branching_model\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/.../branching-model\"\n }\n }\n}\n```", + "description": "Return the branching model as applied to the repository. This view is\nread-only. The branching model settings can be changed using the\n[settings](#api-repositories-workspace-repo-slug-branching-model-settings-get) API.\n\nThe returned object:\n\n1. Always has a `development` property. `development.branch` contains\n the actual repository branch object that is considered to be the\n `development` branch. `development.branch` will not be present\n if it does not exist.\n2. Might have a `production` property. `production` will not\n be present when `production` is disabled.\n `production.branch` contains the actual branch object that is\n considered to be the `production` branch. `production.branch` will\n not be present if it does not exist.\n3. Always has a `branch_types` array which contains all enabled branch\n types.", "summary": "Get the branching model for a repository", "responses": { "200": { @@ -1456,6 +1581,58 @@ "application/json": { "schema": { "$ref": "#/components/schemas/branching_model" + }, + "examples": { + "response": { + "value": { + "development": { + "name": "master", + "branch": { + "type": "branch", + "name": "master", + "target": { + "hash": "16dffcb0de1b22e249db6799532074cf32efe80f" + } + }, + "use_mainbranch": true + }, + "production": { + "name": "production", + "branch": { + "type": "branch", + "name": "production", + "target": { + "hash": "16dffcb0de1b22e249db6799532074cf32efe80f" + } + }, + "use_mainbranch": false + }, + "branch_types": [ + { + "kind": "release", + "prefix": "release/" + }, + { + "kind": "hotfix", + "prefix": "hotfix/" + }, + { + "kind": "feature", + "prefix": "feature/" + }, + { + "kind": "bugfix", + "prefix": "bugfix/" + } + ], + "type": "branching_model", + "links": { + "self": { + "href": "https://api.bitbucket.org/.../branching-model" + } + } + } + } } } } @@ -1501,6 +1678,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -1527,7 +1711,7 @@ "/repositories/{workspace}/{repo_slug}/branching-model/settings": { "get": { "tags": ["Branching model"], - "description": "Return the branching model configuration for a repository. The returned\nobject:\n\n1. Always has a `development` property for the development branch.\n2. Always a `production` property for the production branch. The\n production branch can be disabled.\n3. The `branch_types` contains all the branch types.\n\nThis is the raw configuration for the branching model. A client\nwishing to see the branching model with its actual current branches may\nfind the [active model API](/cloud/bitbucket/rest/api-group-branching-model/#api-repositories-workspace-repo-slug-branching-model-get) more useful.\n\nExample body:\n\n```\n{\n \"development\": {\n \"is_valid\": true,\n \"name\": null,\n \"use_mainbranch\": true\n },\n \"production\": {\n \"is_valid\": true,\n \"name\": \"production\",\n \"use_mainbranch\": false,\n \"enabled\": false\n },\n \"branch_types\": [\n {\n \"kind\": \"release\",\n \"enabled\": true,\n \"prefix\": \"release/\"\n },\n {\n \"kind\": \"hotfix\",\n \"enabled\": true,\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"feature\",\n \"enabled\": true,\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"bugfix\",\n \"enabled\": false,\n \"prefix\": \"bugfix/\"\n }\n ],\n \"type\": \"branching_model_settings\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/.../branching-model/settings\"\n }\n }\n}\n```", + "description": "Return the branching model configuration for a repository. The returned\nobject:\n\n1. Always has a `development` property for the development branch.\n2. Always a `production` property for the production branch. The\n production branch can be disabled.\n3. The `branch_types` contains all the branch types.\n\nThis is the raw configuration for the branching model. A client\nwishing to see the branching model with its actual current branches may\nfind the [active model API](/cloud/bitbucket/rest/api-group-branching-model/#api-repositories-workspace-repo-slug-branching-model-get) more useful.", "summary": "Get the branching model config for a repository", "responses": { "200": { @@ -1536,6 +1720,51 @@ "application/json": { "schema": { "$ref": "#/components/schemas/branching_model_settings" + }, + "examples": { + "response": { + "value": { + "development": { + "is_valid": true, + "name": "null", + "use_mainbranch": true + }, + "production": { + "is_valid": true, + "name": "production", + "use_mainbranch": false, + "enabled": false + }, + "branch_types": [ + { + "kind": "release", + "enabled": true, + "prefix": "release/" + }, + { + "kind": "hotfix", + "enabled": true, + "prefix": "hotfix/" + }, + { + "kind": "feature", + "enabled": true, + "prefix": "feature/" + }, + { + "kind": "bugfix", + "enabled": false, + "prefix": "bugfix/" + } + ], + "type": "branching_model_settings", + "links": { + "self": { + "href": "https://api.bitbucket.org/.../branching-model/settings" + } + } + } + } } } } @@ -1581,11 +1810,18 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "put": { "tags": ["Branching model"], - "description": "Update the branching model configuration for a repository.\n\nThe `development` branch can be configured to a specific branch or to\ntrack the main branch. When set to a specific branch it must\ncurrently exist. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`development` property will leave the development branch unchanged.\n\nIt is possible for the `development` branch to be invalid. This\nhappens when it points at a specific branch that has been\ndeleted. This is indicated in the `is_valid` field for the branch. It is\nnot possible to update the settings for `development` if that\nwould leave the branch in an invalid state. Such a request will be\nrejected.\n\nThe `production` branch can be a specific branch, the main\nbranch or disabled. When set to a specific branch it must currently\nexist. The `enabled` property can be used to enable (`true`) or\ndisable (`false`) it. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`production` property will leave the production branch unchanged.\n\nIt is possible for the `production` branch to be invalid. This\nhappens when it points at a specific branch that has been\ndeleted. This is indicated in the `is_valid` field for the branch. A\nrequest that would leave `production` enabled and invalid will be\nrejected. It is possible to update `production` and make it invalid if\nit would also be left disabled.\n\nThe `branch_types` property contains the branch types to be updated.\nOnly the branch types passed will be updated. All updates will be\nrejected if it would leave the branching model in an invalid state.\nFor branch types this means that:\n\n1. The prefixes for all enabled branch types are valid. For example,\n it is not possible to use '*' inside a Git prefix.\n2. A prefix of an enabled branch type must not be a prefix of another\n enabled branch type. This is to ensure that a branch can be easily\n classified by its prefix unambiguously.\n\nIt is possible to store an invalid prefix if that branch type would be\nleft disabled. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. Each branch type must\nhave a `kind` property to identify it.\n\nExample Body:\n\n```\n {\n \"development\": {\n \"use_mainbranch\": true\n },\n \"production\": {\n \"enabled\": true,\n \"use_mainbranch\": false,\n \"name\": \"production\"\n },\n \"branch_types\": [\n {\n \"kind\": \"bugfix\",\n \"enabled\": true,\n \"prefix\": \"bugfix/\"\n },\n {\n \"kind\": \"feature\",\n \"enabled\": true,\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"hotfix\",\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"release\",\n \"enabled\": false,\n }\n ]\n }\n```\n\nThere is currently a side effect when using this API endpoint. If the\nrepository is inheriting branching model settings from its project,\nupdating the branching model for this repository will disable the\nproject setting inheritance.\n\n\nWe have deprecated this side effect and will remove it on 1 August 2022.", + "description": "Update the branching model configuration for a repository.\n\nThe `development` branch can be configured to a specific branch or to\ntrack the main branch. When set to a specific branch it must\ncurrently exist. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`development` property will leave the development branch unchanged.\n\nIt is possible for the `development` branch to be invalid. This\nhappens when it points at a specific branch that has been\ndeleted. This is indicated in the `is_valid` field for the branch. It is\nnot possible to update the settings for `development` if that\nwould leave the branch in an invalid state. Such a request will be\nrejected.\n\nThe `production` branch can be a specific branch, the main\nbranch or disabled. When set to a specific branch it must currently\nexist. The `enabled` property can be used to enable (`true`) or\ndisable (`false`) it. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`production` property will leave the production branch unchanged.\n\nIt is possible for the `production` branch to be invalid. This\nhappens when it points at a specific branch that has been\ndeleted. This is indicated in the `is_valid` field for the branch. A\nrequest that would leave `production` enabled and invalid will be\nrejected. It is possible to update `production` and make it invalid if\nit would also be left disabled.\n\nThe `branch_types` property contains the branch types to be updated.\nOnly the branch types passed will be updated. All updates will be\nrejected if it would leave the branching model in an invalid state.\nFor branch types this means that:\n\n1. The prefixes for all enabled branch types are valid. For example,\n it is not possible to use '*' inside a Git prefix.\n2. A prefix of an enabled branch type must not be a prefix of another\n enabled branch type. This is to ensure that a branch can be easily\n classified by its prefix unambiguously.\n\nIt is possible to store an invalid prefix if that branch type would be\nleft disabled. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. Each branch type must\nhave a `kind` property to identify it.\n\nThere is currently a side effect when using this API endpoint. If the\nrepository is inheriting branching model settings from its project,\nupdating the branching model for this repository will disable the\nproject setting inheritance.\n\n\nWe have deprecated this side effect and will remove it on 1 August 2022.", "summary": "Update the branching model config for a repository", "responses": { "200": { @@ -1594,6 +1830,40 @@ "application/json": { "schema": { "$ref": "#/components/schemas/branching_model_settings" + }, + "examples": { + "response": { + "value": { + "development": { + "use_mainbranch": true + }, + "production": { + "enabled": true, + "use_mainbranch": false, + "name": "production" + }, + "branch_types": [ + { + "kind": "bugfix", + "enabled": true, + "prefix": "bugfix/" + }, + { + "kind": "feature", + "enabled": true, + "prefix": "feature/" + }, + { + "kind": "hotfix", + "prefix": "hotfix/" + }, + { + "kind": "release", + "enabled": false + } + ] + } + } } } } @@ -1649,6 +1919,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "parameters": [ @@ -1675,7 +1952,7 @@ "/repositories/{workspace}/{repo_slug}/commit/{commit}": { "get": { "tags": ["Commits"], - "description": "Returns the specified commit.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a1\n{\n \"rendered\": {\n \"message\": {\n \"raw\": \"Add a GEORDI_OUTPUT_DIR setting\",\n \"markup\": \"markdown\",\n \"html\": \"

    Add a GEORDI_OUTPUT_DIR setting

    \",\n \"type\": \"rendered\"\n }\n },\n \"hash\": \"f7591a13eda445d9a9167f98eb870319f4b6c2d8\",\n \"repository\": {\n \"name\": \"geordi\",\n \"type\": \"repository\",\n \"full_name\": \"bitbucket/geordi\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/bitbucket/geordi\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B85d08b4e-571d-44e9-a507-fa476535aa98%7D?ts=1730260\"\n }\n },\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8/comments\"\n },\n \"patch\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/patch/f7591a13eda445d9a9167f98eb870319f4b6c2d8\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/bitbucket/geordi/commits/f7591a13eda445d9a9167f98eb870319f4b6c2d8\"\n },\n \"diff\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/diff/f7591a13eda445d9a9167f98eb870319f4b6c2d8\"\n },\n \"approve\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8/approve\"\n },\n \"statuses\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8/statuses\"\n }\n },\n \"author\": {\n \"raw\": \"Brodie Rao \",\n \"type\": \"author\",\n \"user\": {\n \"display_name\": \"Brodie Rao\",\n \"uuid\": \"{9484702e-c663-4afd-aefb-c93a8cd31c28}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/%7B9484702e-c663-4afd-aefb-c93a8cd31c28%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B9484702e-c663-4afd-aefb-c93a8cd31c28%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:3aae1e05-702a-41e5-81c8-f36f29afb6ca/613070db-28b0-421f-8dba-ae8a87e2a5c7/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"brodie\",\n \"account_id\": \"557058:3aae1e05-702a-41e5-81c8-f36f29afb6ca\"\n }\n },\n \"summary\": {\n \"raw\": \"Add a GEORDI_OUTPUT_DIR setting\",\n \"markup\": \"markdown\",\n \"html\": \"

    Add a GEORDI_OUTPUT_DIR setting

    \",\n \"type\": \"rendered\"\n },\n \"participants\": [],\n \"parents\": [\n {\n \"type\": \"commit\",\n \"hash\": \"f06941fec4ef6bcb0c2456927a0cf258fa4f899b\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f06941fec4ef6bcb0c2456927a0cf258fa4f899b\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/bitbucket/geordi/commits/f06941fec4ef6bcb0c2456927a0cf258fa4f899b\"\n }\n }\n }\n ],\n \"date\": \"2012-07-16T19:37:54+00:00\",\n \"message\": \"Add a GEORDI_OUTPUT_DIR setting\",\n \"type\": \"commit\"\n}\n```", + "description": "Returns the specified commit.", "summary": "Get a commit", "responses": { "200": { @@ -1684,6 +1961,107 @@ "application/json": { "schema": { "$ref": "#/components/schemas/commit" + }, + "examples": { + "response": { + "value": { + "rendered": { + "message": { + "raw": "Add a GEORDI_OUTPUT_DIR setting", + "markup": "markdown", + "html": "

    Add a GEORDI_OUTPUT_DIR setting

    ", + "type": "rendered" + } + }, + "hash": "f7591a13eda445d9a9167f98eb870319f4b6c2d8", + "repository": { + "name": "geordi", + "type": "repository", + "full_name": "bitbucket/geordi", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/bitbucket/geordi" + }, + "html": { + "href": "https://bitbucket.org/bitbucket/geordi" + }, + "avatar": { + "href": "https://bytebucket.org/ravatar/%7B85d08b4e-571d-44e9-a507-fa476535aa98%7D?ts=1730260" + } + }, + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8" + }, + "comments": { + "href": "https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8/comments" + }, + "patch": { + "href": "https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/patch/f7591a13eda445d9a9167f98eb870319f4b6c2d8" + }, + "html": { + "href": "https://bitbucket.org/bitbucket/geordi/commits/f7591a13eda445d9a9167f98eb870319f4b6c2d8" + }, + "diff": { + "href": "https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/diff/f7591a13eda445d9a9167f98eb870319f4b6c2d8" + }, + "approve": { + "href": "https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8/approve" + }, + "statuses": { + "href": "https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8/statuses" + } + }, + "author": { + "raw": "Brodie Rao ", + "type": "author", + "user": { + "display_name": "Brodie Rao", + "uuid": "{9484702e-c663-4afd-aefb-c93a8cd31c28}", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7B9484702e-c663-4afd-aefb-c93a8cd31c28%7D" + }, + "html": { + "href": "https://bitbucket.org/%7B9484702e-c663-4afd-aefb-c93a8cd31c28%7D/" + }, + "avatar": { + "href": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:3aae1e05-702a-41e5-81c8-f36f29afb6ca/613070db-28b0-421f-8dba-ae8a87e2a5c7/128" + } + }, + "type": "user", + "nickname": "brodie", + "account_id": "557058:3aae1e05-702a-41e5-81c8-f36f29afb6ca" + } + }, + "summary": { + "raw": "Add a GEORDI_OUTPUT_DIR setting", + "markup": "markdown", + "html": "

    Add a GEORDI_OUTPUT_DIR setting

    ", + "type": "rendered" + }, + "participants": [], + "parents": [ + { + "type": "commit", + "hash": "f06941fec4ef6bcb0c2456927a0cf258fa4f899b", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f06941fec4ef6bcb0c2456927a0cf258fa4f899b" + }, + "html": { + "href": "https://bitbucket.org/bitbucket/geordi/commits/f06941fec4ef6bcb0c2456927a0cf258fa4f899b" + } + } + } + ], + "date": "2012-07-16T19:37:54+00:00", + "message": "Add a GEORDI_OUTPUT_DIR setting", + "type": "commit" + } + } } } } @@ -1709,6 +2087,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -1771,6 +2156,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["write:repository:bitbucket"] + } ] }, "post": { @@ -1809,6 +2201,16 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": [ + "read:repository:bitbucket", + "write:repository:bitbucket" + ] + } ] }, "parameters": [ @@ -1888,6 +2290,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "post": { @@ -1934,6 +2343,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -1967,6 +2383,37 @@ ] }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/comments/{comment_id}": { + "delete": { + "tags": ["Commits"], + "description": "Deletes the specified commit comment.\n\nNote that deleting comments that have visible replies that point to\nthem will not really delete the resource. This is to retain the integrity\nof the original comment tree. Instead, the `deleted` element is set to\n`true` and the content is blanked out. The comment will continue to be\nreturned by the collections and self endpoints.", + "summary": "Delete a commit comment", + "responses": { + "204": { + "description": "Indicates the comment was deleted by this action or a previous delete." + }, + "404": { + "description": "If the comment doesn't exist" + } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } + ] + }, "get": { "tags": ["Commits"], "description": "Returns the specified commit comment.", @@ -1993,6 +2440,63 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } + ] + }, + "put": { + "tags": ["Commits"], + "description": "Used to update the contents of a comment. Only the content of the comment can be updated.\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/prlinks/commit/7f71b5/comments/5728901 \\\n -X PUT -u evzijst \\\n -H 'Content-Type: application/json' \\\n -d '{\"content\": {\"raw\": \"One more thing!\"}'\n```", + "summary": "Update a commit comment", + "responses": { + "201": { + "description": "The newly updated comment.", + "headers": { + "Location": { + "description": "The location of the newly updated comment.", + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "If the comment update was detected as spam" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/commit_comment" + } + } + }, + "description": "The updated comment.", + "required": true + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -2094,7 +2598,18 @@ "requestBody": { "$ref": "#/components/requestBodies/application_property" }, - "tags": ["properties"] + "tags": ["properties"], + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "responses": { @@ -2152,7 +2667,18 @@ } } ], - "tags": ["properties"] + "tags": ["properties"], + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "get": { "responses": { @@ -2217,7 +2743,18 @@ } } ], - "tags": ["properties"] + "tags": ["properties"], + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/pullrequests": { @@ -2308,7 +2845,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports": { @@ -2357,7 +2912,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports/{reportId}": { @@ -2436,7 +3002,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "get": { "tags": ["Reports", "Commits"], @@ -2502,7 +3079,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "tags": ["Reports", "Commits"], @@ -2551,7 +3139,18 @@ "204": { "description": "No content" } - } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports/{reportId}/annotations": { @@ -2609,7 +3208,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "post": { "tags": ["Reports", "Commits"], @@ -2684,7 +3294,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports/{reportId}/annotations/{annotationId}": { @@ -2761,7 +3382,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "put": { "tags": ["Reports", "Commits"], @@ -2847,7 +3479,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "tags": ["Reports", "Commits"], @@ -2905,7 +3548,18 @@ "204": { "description": "No content" } - } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/statuses": { @@ -2968,6 +3622,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -3050,6 +3711,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -3122,6 +3790,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "put": { @@ -3173,6 +3848,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -3251,6 +3933,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "post": { @@ -3289,6 +3978,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -3315,7 +4011,7 @@ "/repositories/{workspace}/{repo_slug}/commits/{revision}": { "get": { "tags": ["Commits"], - "description": "These are the repository's commits. They are paginated and returned\nin reverse chronological order, similar to the output of `git log`.\nLike these tools, the DAG can be filtered.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/master\n\nReturns all commits on rev `master` (similar to `git log master`).\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/dev?include=foo&exclude=master\n\nReturns all commits on ref `dev` or `foo`, except those that are reachable on\n`master` (similar to `git log dev foo ^master`).\n\nAn optional `path` parameter can be specified that will limit the\nresults to commits that affect that path. `path` can either be a file\nor a directory. If a directory is specified, commits are returned that\nhave modified any file in the directory tree rooted by `path`. It is\nimportant to note that if the `path` parameter is specified, the commits\nreturned by this endpoint may no longer be a DAG, parent commits that\ndo not modify the path will be omitted from the response.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/dev?path=README.md&include=foo&include=bar&exclude=master\n\nReturns all commits that are on refs `dev` or `foo` or `bar`, but not on `master`\nthat changed the file README.md.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/dev?path=src/&include=foo&exclude=master\n\nReturns all commits that are on refs `dev` or `foo`, but not on `master`\nthat changed to a file in any file in the directory src or its children.\n\nBecause the response could include a very large number of commits, it\nis paginated. Follow the 'next' link in the response to navigate to the\nnext page of commits. As with other paginated resources, do not\nconstruct your own links.\n\nWhen the include and exclude parameters are more than can fit in a\nquery string, clients can use a `x-www-form-urlencoded` POST instead.", + "description": "These are the repository's commits. They are paginated and returned\nin reverse chronological order, similar to the output of `git log`.\nLike these tools, the DAG can be filtered.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/master\n\nReturns all commits on ref `master` (similar to `git log master`).\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/dev?include=foo&exclude=master\n\nReturns all commits on ref `dev` or `foo`, except those that are reachable on\n`master` (similar to `git log dev foo ^master`).\n\nAn optional `path` parameter can be specified that will limit the\nresults to commits that affect that path. `path` can either be a file\nor a directory. If a directory is specified, commits are returned that\nhave modified any file in the directory tree rooted by `path`. It is\nimportant to note that if the `path` parameter is specified, the commits\nreturned by this endpoint may no longer be a DAG, parent commits that\ndo not modify the path will be omitted from the response.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/dev?path=README.md&include=foo&include=bar&exclude=master\n\nReturns all commits that are on refs `dev` or `foo` or `bar`, but not on `master`\nthat changed the file README.md.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/dev?path=src/&include=foo&exclude=master\n\nReturns all commits that are on refs `dev` or `foo`, but not on `master`\nthat changed to a file in any file in the directory src or its children.\n\nBecause the response could include a very large number of commits, it\nis paginated. Follow the 'next' link in the response to navigate to the\nnext page of commits. As with other paginated resources, do not\nconstruct your own links.\n\nWhen the include and exclude parameters are more than can fit in a\nquery string, clients can use a `x-www-form-urlencoded` POST instead.", "summary": "List commits for revision", "responses": { "200": { @@ -3349,6 +4045,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "post": { @@ -3387,6 +4090,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -3402,7 +4112,7 @@ { "name": "revision", "in": "path", - "description": "The commit's SHA1.", + "description": "A commit SHA1 or ref name.", "required": true, "schema": { "type": "string" @@ -3585,6 +4295,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -3648,6 +4365,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "get": { @@ -3696,6 +4420,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "put": { @@ -3754,6 +4485,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "parameters": [ @@ -3789,7 +4527,7 @@ "/repositories/{workspace}/{repo_slug}/deploy-keys": { "get": { "tags": ["Deployments"], - "description": "Returns all deploy-keys belonging to a repository.\n\nExample:\n```\n$ curl -H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys\n\nOutput:\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"id\": 123,\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"label\": \"mykey\",\n \"type\": \"deploy_key\",\n \"created_on\": \"2018-08-15T23:50:59.993890+00:00\",\n \"repository\": {\n \"full_name\": \"mleu/test\",\n \"name\": \"test\",\n \"type\": \"repository\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"links\":{\n \"self\":{\n \"href\": \"https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123\"\n }\n }\n \"last_used\": null,\n \"comment\": \"mleu@C02W454JHTD8\"\n }\n ],\n \"page\": 1,\n \"size\": 1\n}\n```", + "description": "Returns all deploy-keys belonging to a repository.", "summary": "List repository deploy keys", "responses": { "200": { @@ -3798,6 +4536,37 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_deploy_keys" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "values": [ + { + "id": 123, + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5", + "label": "mykey", + "type": "deploy_key", + "created_on": "2018-08-15T23:50:59.993890+00:00", + "repository": { + "full_name": "mleu/test", + "name": "test", + "type": "repository", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123" + } + }, + "last_used": null, + "comment": "mleu@C02W454JHTD8" + } + ], + "page": 1, + "size": 1 + } + } } } } @@ -3830,7 +4599,7 @@ }, "post": { "tags": ["Deployments"], - "description": "Create a new deploy key in a repository. Note: If authenticating a deploy key\nwith an OAuth consumer, any changes to the OAuth consumer will subsequently\ninvalidate the deploy key.\n\n\nExample:\n```\n$ curl -XPOST \\\n-H \"Authorization \" \\\n-H \"Content-type: application/json\" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys -d \\\n'{\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5 mleu@C02W454JHTD8\",\n \"label\": \"mydeploykey\"\n}'\n\nOutput:\n{\n \"id\": 123,\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"label\": \"mydeploykey\",\n \"type\": \"deploy_key\",\n \"created_on\": \"2018-08-15T23:50:59.993890+00:00\",\n \"repository\": {\n \"full_name\": \"mleu/test\",\n \"name\": \"test\",\n \"type\": \"repository\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"links\":{\n \"self\":{\n \"href\": \"https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123\"\n }\n }\n \"last_used\": null,\n \"comment\": \"mleu@C02W454JHTD8\"\n}\n```", + "description": "Create a new deploy key in a repository. Note: If authenticating a deploy key\nwith an OAuth consumer, any changes to the OAuth consumer will subsequently\ninvalidate the deploy key.\n\n\nExample:\n```\n$ curl -X POST \\\n-H \"Authorization \" \\\n-H \"Content-type: application/json\" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys -d \\\n'{\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5 mleu@C02W454JHTD8\",\n \"label\": \"mydeploykey\"\n}'\n```", "summary": "Add a repository deploy key", "responses": { "200": { @@ -3839,6 +4608,30 @@ "application/json": { "schema": { "$ref": "#/components/schemas/deploy_key" + }, + "examples": { + "response": { + "value": { + "id": 123, + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5", + "label": "mydeploykey", + "type": "deploy_key", + "created_on": "2018-08-15T23:50:59.993890+00:00", + "repository": { + "full_name": "mleu/test", + "name": "test", + "type": "repository", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123" + } + }, + "last_used": null, + "comment": "mleu@C02W454JHTD8" + } + } } } } @@ -3896,7 +4689,7 @@ "/repositories/{workspace}/{repo_slug}/deploy-keys/{key_id}": { "delete": { "tags": ["Deployments"], - "description": "This deletes a deploy key from a repository.\n\nExample:\n```\n$ curl -XDELETE \\\n-H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/1234\n```", + "description": "This deletes a deploy key from a repository.", "summary": "Delete a repository deploy key", "responses": { "204": { @@ -3930,7 +4723,7 @@ }, "get": { "tags": ["Deployments"], - "description": "Returns the deploy key belonging to a specific key.\n\nExample:\n```\n$ curl -H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-key/1234\n\nOutput:\n{\n \"comment\": \"mleu@C02W454JHTD8\",\n \"last_used\": null,\n \"links\": {\n \"self\": {\n \"href\": https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-key/1234\"\n }\n },\n \"repository\": {\n \"full_name\": \"mleu/test\",\n \"name\": \"test\",\n \"type\": \"repository\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"label\": \"mykey\",\n \"created_on\": \"2018-08-15T23:50:59.993890+00:00\",\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"id\": 1234,\n \"type\": \"deploy_key\"\n}\n```", + "description": "Returns the deploy key belonging to a specific key.", "summary": "Get a repository deploy key", "responses": { "200": { @@ -3939,6 +4732,30 @@ "application/json": { "schema": { "$ref": "#/components/schemas/deploy_key" + }, + "examples": { + "response": { + "value": { + "comment": "mleu@C02W454JHTD8", + "last_used": null, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-key/1234" + } + }, + "repository": { + "full_name": "mleu/test", + "name": "test", + "type": "repository", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "label": "mykey", + "created_on": "2018-08-15T23:50:59.993890+00:00", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5", + "id": 1234, + "type": "deploy_key" + } + } } } } @@ -3971,7 +4788,7 @@ }, "put": { "tags": ["Deployments"], - "description": "Create a new deploy key in a repository.\n\nThe same key needs to be passed in but the comment and label can change.\n\nExample:\n```\n$ curl -XPUT \\\n-H \"Authorization \" \\\n-H \"Content-type: application/json\" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/1234 -d \\\n'{\n \"label\": \"newlabel\",\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5 newcomment\",\n}'\n\nOutput:\n{\n \"comment\": \"newcomment\",\n \"last_used\": null,\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/1234\"\n }\n },\n \"repository\": {\n \"full_name\": \"mleu/test\",\n \"name\": \"test\",\n \"type\": \"repository\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"label\": \"newlabel\",\n \"created_on\": \"2018-08-15T23:50:59.993890+00:00\",\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"id\": 1234,\n \"type\": \"deploy_key\"\n}\n```", + "description": "Create a new deploy key in a repository.\n\nThe same key needs to be passed in but the comment and label can change.\n\nExample:\n```\n$ curl -X PUT \\\n-H \"Authorization \" \\\n-H \"Content-type: application/json\" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/1234 -d \\\n'{\n \"label\": \"newlabel\",\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5 newcomment\",\n}'\n```", "summary": "Update a repository deploy key", "responses": { "200": { @@ -3980,6 +4797,30 @@ "application/json": { "schema": { "$ref": "#/components/schemas/deploy_key" + }, + "examples": { + "response": { + "value": { + "comment": "newcomment", + "last_used": null, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/1234" + } + }, + "repository": { + "full_name": "mleu/test", + "name": "test", + "type": "repository", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "label": "newlabel", + "created_on": "2018-08-15T23:50:59.993890+00:00", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5", + "id": 1234, + "type": "deploy_key" + } + } } } } @@ -4050,7 +4891,7 @@ } ] }, - "/repositories/{workspace}/{repo_slug}/deployments/": { + "/repositories/{workspace}/{repo_slug}/deployments": { "get": { "tags": ["Deployments"], "description": "Find deployments", @@ -4087,7 +4928,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/deployments/{deployment_uuid}": { @@ -4146,7 +5005,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/deployments_config/environments/{environment_uuid}/variables": { @@ -4195,7 +5072,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "post": { "tags": ["Pipelines"], @@ -4281,7 +5169,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/deployments_config/environments/{environment_uuid}/variables/{variable_uuid}": { @@ -4360,7 +5259,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "tags": ["Pipelines"], @@ -4419,13 +5329,24 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/diff/{spec}": { "get": { "tags": ["Commits"], - "description": "Produces a raw git-style diff.\n\n#### Single commit spec\n\nIf the `spec` argument to this API is a single commit, the diff is\nproduced against the first parent of the specified commit.\n\n#### Two commit spec\n\nTwo commits separated by `..` may be provided as the `spec`, e.g.,\n`3a8b42..9ff173`. When two commits are provided and the `topic` query\nparameter is true or absent, this API produces a 2-way three dot diff.\nThis is the diff between source commit and the merge base of the source\ncommit and the destination commit. When the `topic` query param is false,\na simple git-style diff is produced.\n\nThe two commits are interpreted as follows:\n\n* First commit: the commit containing the changes we wish to preview\n* Second commit: the commit representing the state to which we want to\n compare the first commit\n* **Note**: This is the opposite of the order used in `git diff`.\n\n#### Comparison to patches\n\nWhile similar to patches, diffs:\n\n* Don't have a commit header (username, commit message, etc)\n* Support the optional `path=foo/bar.py` query param to filter\n the diff to just that one file diff\n\n#### Response\n\nThe raw diff is returned as-is, in whatever encoding the files in the\nrepository use. It is not decoded into unicode. As such, the\ncontent-type is `text/plain`.", + "description": "Produces a raw git-style diff.\n\n#### Single commit spec\n\nIf the `spec` argument to this API is a single commit, the diff is\nproduced against the first parent of the specified commit.\n\n#### Two commit spec\n\nTwo commits separated by `..` may be provided as the `spec`, e.g.,\n`3a8b42..9ff173`. When two commits are provided and the `topic` query\nparameter is true, this API produces a 2-way three dot diff.\nThis is the diff between source commit and the merge base of the source\ncommit and the destination commit. When the `topic` query param is false,\na simple git-style diff is produced.\n\nThe two commits are interpreted as follows:\n\n* First commit: the commit containing the changes we wish to preview\n* Second commit: the commit representing the state to which we want to\n compare the first commit\n* **Note**: This is the opposite of the order used in `git diff`.\n\n#### Comparison to patches\n\nWhile similar to patches, diffs:\n\n* Don't have a commit header (username, commit message, etc)\n* Support the optional `path=foo/bar.py` query param to filter\n the diff to just that one file diff\n\n#### Response\n\nThe raw diff is returned as-is, in whatever encoding the files in the\nrepository use. It is not decoded into unicode. As such, the\ncontent-type is `text/plain`.", "summary": "Compare two commits", "responses": { "200": { @@ -4491,7 +5412,7 @@ { "name": "merge", "in": "query", - "description": "This parameter is deprecated and will be removed at the end\nof 2022. The 'topic' parameter should be used instead. The\n'merge' and 'topic' parameters cannot be both used at the same\ntime.\n\nIf true, the source commit is merged into the\ndestination commit, and then a diff from the\ndestination to the merge result is returned. If false,\na simple 'two dot' diff between the source and\ndestination is returned. True if omitted.", + "description": "This parameter is deprecated. The 'topic' parameter should be used\ninstead. The 'merge' and 'topic' parameters cannot be both used at\nthe same time.\n\nIf true, the source commit is merged into the\ndestination commit, and then a diff from the\ndestination to the merge result is returned. If false,\na simple 'two dot' diff between the source and\ndestination is returned. True if omitted.", "required": false, "schema": { "type": "boolean" @@ -4517,6 +5438,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -4552,7 +5480,7 @@ "/repositories/{workspace}/{repo_slug}/diffstat/{spec}": { "get": { "tags": ["Commits"], - "description": "Produces a response in JSON format with a record for every path\nmodified, including information on the type of the change and the\nnumber of lines added and removed.\n\n#### Single commit spec\n\nIf the `spec` argument to this API is a single commit, the diff is\nproduced against the first parent of the specified commit.\n\n#### Two commit spec\n\nTwo commits separated by `..` may be provided as the `spec`, e.g.,\n`3a8b42..9ff173`. When two commits are provided and the `topic` query\nparameter is true or absent, this API produces a 2-way three dot diff.\nThis is the diff between source commit and the merge base of the source\ncommit and the destination commit. When the `topic` query param is false,\na simple git-style diff is produced.\n\nThe two commits are interpreted as follows:\n\n* First commit: the commit containing the changes we wish to preview\n* Second commit: the commit representing the state to which we want to\n compare the first commit\n* **Note**: This is the opposite of the order used in `git diff`.\n\n#### Sample output\n```\ncurl https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/diffstat/d222fa2..e174964\n{\n \"pagelen\": 500,\n \"values\": [\n {\n \"type\": \"diffstat\",\n \"status\": \"modified\",\n \"lines_removed\": 1,\n \"lines_added\": 2,\n \"old\": {\n \"path\": \"setup.py\",\n \"escaped_path\": \"setup.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/src/e1749643d655d7c7014001a6c0f58abaf42ad850/setup.py\"\n }\n }\n },\n \"new\": {\n \"path\": \"setup.py\",\n \"escaped_path\": \"setup.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/src/d222fa235229c55dad20b190b0b571adf737d5a6/setup.py\"\n }\n }\n }\n }\n ],\n \"page\": 1,\n \"size\": 1\n}\n```", + "description": "Produces a response in JSON format with a record for every path\nmodified, including information on the type of the change and the\nnumber of lines added and removed.\n\n#### Single commit spec\n\nIf the `spec` argument to this API is a single commit, the diff is\nproduced against the first parent of the specified commit.\n\n#### Two commit spec\n\nTwo commits separated by `..` may be provided as the `spec`, e.g.,\n`3a8b42..9ff173`. When two commits are provided and the `topic` query\nparameter is true, this API produces a 2-way three dot diff.\nThis is the diff between source commit and the merge base of the source\ncommit and the destination commit. When the `topic` query param is false,\na simple git-style diff is produced.\n\nThe two commits are interpreted as follows:\n\n* First commit: the commit containing the changes we wish to preview\n* Second commit: the commit representing the state to which we want to\n compare the first commit\n* **Note**: This is the opposite of the order used in `git diff`.", "summary": "Compare two commit diff stats", "responses": { "200": { @@ -4561,6 +5489,43 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_diffstats" + }, + "examples": { + "response": { + "value": { + "pagelen": 500, + "values": [ + { + "type": "diffstat", + "status": "modified", + "lines_removed": 1, + "lines_added": 2, + "old": { + "path": "setup.py", + "escaped_path": "setup.py", + "type": "commit_file", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/src/e1749643d655d7c7014001a6c0f58abaf42ad850/setup.py" + } + } + }, + "new": { + "path": "setup.py", + "escaped_path": "setup.py", + "type": "commit_file", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/src/d222fa235229c55dad20b190b0b571adf737d5a6/setup.py" + } + } + } + } + ], + "page": 1, + "size": 1 + } + } } } } @@ -4586,6 +5551,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -4601,7 +5573,7 @@ { "name": "merge", "in": "query", - "description": "This parameter is deprecated and will be removed at the end\nof 2022. The 'topic' parameter should be used instead. The\n'merge' and 'topic' parameters cannot be both used at the same\ntime.\n\nIf true, the source commit is merged into the\ndestination commit, and then a diffstat from the\ndestination to the merge result is returned. If false,\na simple 'two dot' diffstat between the source and\ndestination is returned. True if omitted.", + "description": "This parameter is deprecated. The 'topic' parameter should be used\ninstead. The 'merge' and 'topic' parameters cannot be both used at\nthe same time.\n\nIf true, the source commit is merged into the\ndestination commit, and then a diffstat from the\ndestination to the merge result is returned. If false,\na simple 'two dot' diffstat between the source and\ndestination is returned. True if omitted.", "required": false, "schema": { "type": "boolean" @@ -4693,6 +5665,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "post": { @@ -4807,6 +5786,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["write:repository:bitbucket"] + } ] }, "get": { @@ -4937,6 +5923,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -4963,7 +5956,7 @@ "/repositories/{workspace}/{repo_slug}/effective-default-reviewers": { "get": { "tags": ["Pullrequests"], - "description": "Returns the repository's effective default reviewers. This includes both default\nreviewers defined at the repository level as well as those inherited from its project.\n\nThese are the users that are automatically added as reviewers on every\nnew pull request that is created.\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/{workspace_slug}/{repo_slug}/effective-default-reviewers?page=1&pagelen=20\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"user\": {\n \"display_name\": \"Patrick Wolf\",\n \"uuid\": \"{9565301a-a3cf-4b5d-88f4-dd6af8078d7e}\"\n },\n \"reviewer_type\": \"project\",\n \"type\": \"default_reviewer\",\n },\n {\n \"user\": {\n \"display_name\": \"Davis Lee\",\n \"uuid\": \"{f0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6}\"\n },\n \"reviewer_type\": \"repository\",\n \"type\": \"default_reviewer\",\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```", + "description": "Returns the repository's effective default reviewers. This includes both default\nreviewers defined at the repository level as well as those inherited from its project.\n\nThese are the users that are automatically added as reviewers on every\nnew pull request that is created.", "summary": "List effective default reviewers", "responses": { "200": { @@ -4972,6 +5965,33 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_default_reviewer_and_type" + }, + "examples": { + "response": { + "value": { + "pagelen": 20, + "values": [ + { + "user": { + "display_name": "Patrick Wolf", + "uuid": "{9565301a-a3cf-4b5d-88f4-dd6af8078d7e}" + }, + "reviewer_type": "project", + "type": "default_reviewer" + }, + { + "user": { + "display_name": "Davis Lee", + "uuid": "{f0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6}" + }, + "reviewer_type": "repository", + "type": "default_reviewer" + } + ], + "page": 1, + "size": 2 + } + } } } } @@ -4997,6 +6017,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -5020,7 +6047,7 @@ } ] }, - "/repositories/{workspace}/{repo_slug}/environments/": { + "/repositories/{workspace}/{repo_slug}/environments": { "get": { "tags": ["Deployments"], "description": "Find environments", @@ -5057,7 +6084,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "post": { "tags": ["Deployments"], @@ -5134,7 +6179,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/environments/{environment_uuid}": { @@ -5193,7 +6249,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "tags": ["Deployments"], @@ -5243,10 +6317,21 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, - "/repositories/{workspace}/{repo_slug}/environments/{environment_uuid}/changes/": { + "/repositories/{workspace}/{repo_slug}/environments/{environment_uuid}/changes": { "post": { "tags": ["Deployments"], "description": "Update an environment", @@ -5295,13 +6380,24 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/filehistory/{commit}/{path}": { "get": { "tags": ["Source", "Repositories"], - "description": "Returns a paginated list of commits that modified the specified file.\n\nCommits are returned in reverse chronological order. This is roughly\nequivalent to the following commands:\n\n $ git log --follow --date-order \n\nBy default, Bitbucket will follow renames and the path name in the\nreturned entries reflects that. This can be turned off using the\n`?renames=false` query parameter.\n\nResults are returned in descending chronological order by default, and\nlike most endpoints you can\n[filter and sort](/cloud/bitbucket/rest/intro/#filtering) the response to\nonly provide exactly the data you want.\n\nFor example, if you wanted to find commits made before 2011-05-18\nagainst a file named `README.rst`, but you only wanted the path and\ndate, your query would look like this:\n\n```\n$ curl 'https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/filehistory/master/README.rst'\\\n '?fields=values.next,values.path,values.commit.date&q=commit.date<=2011-05-18'\n{\n \"values\": [\n {\n \"commit\": {\n \"date\": \"2011-05-17T07:32:09+00:00\"\n },\n \"path\": \"README.rst\"\n },\n {\n \"commit\": {\n \"date\": \"2011-05-16T06:33:28+00:00\"\n },\n \"path\": \"README.txt\"\n },\n {\n \"commit\": {\n \"date\": \"2011-05-16T06:15:39+00:00\"\n },\n \"path\": \"README.txt\"\n }\n ]\n}\n```\n\nIn the response you can see that the file was renamed to `README.rst`\nby the commit made on 2011-05-16, and was previously named `README.txt`.", + "description": "Returns a paginated list of commits that modified the specified file.\n\nCommits are returned in reverse chronological order. This is roughly\nequivalent to the following commands:\n\n $ git log --follow --date-order \n\nBy default, Bitbucket will follow renames and the path name in the\nreturned entries reflects that. This can be turned off using the\n`?renames=false` query parameter.\n\nResults are returned in descending chronological order by default, and\nlike most endpoints you can\n[filter and sort](/cloud/bitbucket/rest/intro/#filtering) the response to\nonly provide exactly the data you want.\n\nThe example response returns commits made before 2011-05-18 against a file\nnamed `README.rst`. The results are filtered to only return the path and\ndate. This request can be made using:\n\n```\n$ curl 'https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/filehistory/master/README.rst'\\\n '?fields=values.next,values.path,values.commit.date&q=commit.date<=2011-05-18'\n```\n\nIn the response you can see that the file was renamed to `README.rst`\nby the commit made on 2011-05-16, and was previously named `README.txt`.", "summary": "List commits that modified a file", "responses": { "200": { @@ -5310,6 +6406,32 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_files" + }, + "examples": { + "response": { + "value": { + "values": [ + { + "commit": { + "date": "2011-05-17T07:32:09+00:00" + }, + "path": "README.rst" + }, + { + "commit": { + "date": "2011-05-16T06:33:28+00:00" + }, + "path": "README.txt" + }, + { + "commit": { + "date": "2011-05-16T06:15:39+00:00" + }, + "path": "README.txt" + } + ] + } + } } } } @@ -5364,6 +6486,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -5462,6 +6591,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "post": { @@ -5508,6 +6644,16 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": [ + "read:repository:bitbucket", + "write:repository:bitbucket" + ] + } ] }, "parameters": [ @@ -5582,7 +6728,7 @@ }, "post": { "tags": ["Repositories", "Webhooks"], - "description": "Creates a new webhook on the specified repository.\n\nExample:\n\n```\n$ curl -X POST -u credentials -H 'Content-Type: application/json'\n https://api.bitbucket.org/2.0/repositories/my-workspace/my-repo-slug/hooks\n -d '\n {\n \"description\": \"Webhook Description\",\n \"url\": \"https://example.com/\",\n \"active\": true,\n \"events\": [\n \"repo:push\",\n \"issue:created\",\n \"issue:updated\"\n ]\n }'\n```\n\nNote that this call requires the webhook scope, as well as any scope\nthat applies to the events that the webhook subscribes to. In the\nexample above that means: `webhook`, `repository` and `issue`.\n\nAlso note that the `url` must properly resolve and cannot be an\ninternal, non-routed address.", + "description": "Creates a new webhook on the specified repository.\n\nExample:\n\n```\n$ curl -X POST -u credentials -H 'Content-Type: application/json'\n https://api.bitbucket.org/2.0/repositories/my-workspace/my-repo-slug/hooks\n -d '\n {\n \"description\": \"Webhook Description\",\n \"url\": \"https://example.com/\",\n \"active\": true,\n \"secret\": \"this is a really bad secret\",\n \"events\": [\n \"repo:push\",\n \"issue:created\",\n \"issue:updated\"\n ]\n }'\n```\n\nWhen the `secret` is provided it will be used as the key to generate a HMAC\ndigest value sent in the `X-Hub-Signature` header at delivery time. Passing\na `null` or empty `secret` or not passing a `secret` will leave the webhook's\nsecret unset. Bitbucket only generates the `X-Hub-Signature` when the webhook's\nsecret is set.\n\nNote that this call requires the webhook scope, as well as any scope\nthat applies to the events that the webhook subscribes to. In the\nexample above that means: `webhook`, `repository` and `issue`.\n\nAlso note that the `url` must properly resolve and cannot be an\ninternal, non-routed address.", "summary": "Create a webhook for a repository", "responses": { "201": { @@ -5739,7 +6885,7 @@ }, "put": { "tags": ["Repositories", "Webhooks"], - "description": "Updates the specified webhook subscription.\n\nThe following properties can be mutated:\n\n* `description`\n* `url`\n* `active`\n* `events`", + "description": "Updates the specified webhook subscription.\n\nThe following properties can be mutated:\n\n* `description`\n* `url`\n* `secret`\n* `active`\n* `events`\n\nThe hook's secret is used as a key to generate the HMAC hex digest sent in the\n`X-Hub-Signature` header at delivery time. This signature is only generated\nwhen the hook has a secret.\n\nSet the hook's secret by passing the new value in the `secret` field. Passing a\n`null` value in the `secret` field will remove the secret from the hook. The\nhook's secret can be left unchanged by not passing the `secret` field in the\nrequest.", "summary": "Update a webhook for a repository", "responses": { "200": { @@ -5955,7 +7101,7 @@ "/repositories/{workspace}/{repo_slug}/issues/export": { "post": { "tags": ["Issue tracker"], - "description": "A POST request to this endpoint initiates a new background celery task that archives the repo's issues.\n\nFor example, you can run:\n\ncurl -u -X POST http://api.bitbucket.org/2.0/repositories///\nissues/export\n\nWhen the job has been accepted, it will return a 202 (Accepted) along with a unique url to this job in the\n'Location' response header. This url is the endpoint for where the user can obtain their zip files.\"", + "description": "A POST request to this endpoint initiates a new background celery task that archives the repo's issues.\n\nWhen the job has been accepted, it will return a 202 (Accepted) along with a unique url to this job in the\n'Location' response header. This url is the endpoint for where the user can obtain their zip files.\"", "summary": "Export issues", "responses": { "202": { @@ -6038,7 +7184,7 @@ "/repositories/{workspace}/{repo_slug}/issues/export/{repo_name}-issues-{task_id}.zip": { "get": { "tags": ["Issue tracker"], - "description": "This endpoint is used to poll for the progress of an issue export\njob and return the zip file after the job is complete.\nAs long as the job is running, this will return a 200 response\nwith in the response body a description of the current status.\n\nAfter the job has been scheduled, but before it starts executing, this\nendpoint's response is:\n\n{\n \"type\": \"issue_job_status\",\n \"status\": \"ACCEPTED\",\n \"phase\": \"Initializing\",\n \"total\": 0,\n \"count\": 0,\n \"pct\": 0\n}\n\n\nThen once it starts running, it becomes:\n\n{\n \"type\": \"issue_job_status\",\n \"status\": \"STARTED\",\n \"phase\": \"Attachments\",\n \"total\": 15,\n \"count\": 11,\n \"pct\": 73\n}\n\nOnce the job has successfully completed, it returns a stream of the zip file.", + "description": "This endpoint is used to poll for the progress of an issue export\njob and return the zip file after the job is complete.\nAs long as the job is running, this will return a 202 response\nwith in the response body a description of the current status.\n\nAfter the job has been scheduled, but before it starts executing, the endpoint\nreturns a 202 response with status `ACCEPTED`.\n\nOnce it starts running, it is a 202 response with status `STARTED` and progress filled.\n\nAfter it is finished, it becomes a 200 response with status `SUCCESS` or `FAILURE`.", "summary": "Check issue export status", "responses": { "202": { @@ -6047,6 +7193,18 @@ "application/json": { "schema": { "$ref": "#/components/schemas/issue_job_status" + }, + "examples": { + "response": { + "value": { + "type": "issue_job_status", + "status": "ACCEPTED", + "phase": "Initializing", + "total": 0, + "count": 0, + "pct": 0 + } + } } } } @@ -6136,7 +7294,7 @@ "/repositories/{workspace}/{repo_slug}/issues/import": { "get": { "tags": ["Issue tracker"], - "description": "When using GET, this endpoint reports the status of the current import task. Request example:\n\n```\n$ curl -u -X GET https://api.bitbucket.org/2.0/repositories///issues/import\n```\n\nAfter the job has been scheduled, but before it starts executing, this endpoint's response is:\n\n```\n< HTTP/1.1 202 Accepted\n{\n \"type\": \"issue_job_status\",\n \"status\": \"PENDING\",\n \"phase\": \"Attachments\",\n \"total\": 15,\n \"count\": 0,\n \"percent\": 0\n}\n```\n\nOnce it starts running, it is a 202 response with status STARTED and progress filled.\n\nAfter it is finished, it becomes a 200 response with status SUCCESS or FAILURE.", + "description": "When using GET, this endpoint reports the status of the current import task.\n\nAfter the job has been scheduled, but before it starts executing, the endpoint\nreturns a 202 response with status `ACCEPTED`.\n\nOnce it starts running, it is a 202 response with status `STARTED` and progress filled.\n\nAfter it is finished, it becomes a 200 response with status `SUCCESS` or `FAILURE`.", "summary": "Check issue import status", "responses": { "200": { @@ -6155,6 +7313,18 @@ "application/json": { "schema": { "$ref": "#/components/schemas/issue_job_status" + }, + "examples": { + "response": { + "value": { + "type": "issue_job_status", + "status": "ACCEPTED", + "phase": "Attachments", + "total": 15, + "count": 0, + "percent": 0 + } + } } } } @@ -6204,7 +7374,7 @@ }, "post": { "tags": ["Issue tracker"], - "description": "A POST request to this endpoint will import the zip file given by the archive parameter into the repository. All\nexisting issues will be deleted and replaced by the contents of the imported zip file.\n\nImports are done through a multipart/form-data POST. There is one valid and required form field, with the name\n\"archive,\" which needs to be a file field:\n\n```\n$ curl -u -X POST -F archive=@/path/to/file.zip https://api.bitbucket.org/2.0/repositories///issues/import\n```\n\nWhen the import job is accepted, here is example output:\n\n```\n< HTTP/1.1 202 Accepted\n\n{\n \"type\": \"issue_job_status\",\n \"status\": \"ACCEPTED\",\n \"phase\": \"Attachments\",\n \"total\": 15,\n \"count\": 0,\n \"percent\": 0\n}\n```", + "description": "A POST request to this endpoint will import the zip file given by the archive parameter into the repository. All\nexisting issues will be deleted and replaced by the contents of the imported zip file.\n\nImports are done through a multipart/form-data POST. There is one valid and required form field, with the name\n\"archive,\" which needs to be a file field:\n\n```\n$ curl -u -X POST -F archive=@/path/to/file.zip https://api.bitbucket.org/2.0/repositories///issues/import\n```", "summary": "Import issues", "responses": { "202": { @@ -6213,6 +7383,18 @@ "application/json": { "schema": { "$ref": "#/components/schemas/issue_job_status" + }, + "examples": { + "response": { + "value": { + "type": "issue_job_status", + "status": "ACCEPTED", + "phase": "Attachments", + "total": 15, + "count": 0, + "percent": 0 + } + } } } } @@ -6711,7 +7893,7 @@ "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/changes": { "get": { "tags": ["Issue tracker"], - "description": "Returns the list of all changes that have been made to the specified\nissue. Changes are returned in chronological order with the oldest\nchange first.\n\nEach time an issue is edited in the UI or through the API, an immutable\nchange record is created under the `/issues/123/changes` endpoint. It\nalso has a comment associated with the change.\n\nNote that this operation is changing significantly, due to privacy changes.\nSee the [announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-changes-gdpr/#changes-to-the-issue-changes-api)\nfor details.\n\n```\n$ curl -s https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/issues/1/changes - | jq .\n\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"changes\": {\n \"priority\": {\n \"new\": \"trivial\",\n \"old\": \"major\"\n },\n \"assignee\": {\n \"new\": \"\",\n \"old\": \"evzijst\"\n },\n \"assignee_account_id\": {\n \"new\": \"\",\n \"old\": \"557058:c0b72ad0-1cb5-4018-9cdc-0cde8492c443\"\n },\n \"kind\": {\n \"new\": \"enhancement\",\n \"old\": \"bug\"\n }\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/issues/1/changes/2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst/dogslow/issues/1#comment-2\"\n }\n },\n \"issue\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/issues/1\"\n }\n },\n \"type\": \"issue\",\n \"id\": 1,\n \"repository\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/evzijst/dogslow\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst/dogslow\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/evzijst/dogslow/avatar/32/\"\n }\n },\n \"type\": \"repository\",\n \"name\": \"dogslow\",\n \"full_name\": \"evzijst/dogslow\",\n \"uuid\": \"{988b17c6-1a47-4e70-84ee-854d5f012bf6}\"\n },\n \"title\": \"Updated title\"\n },\n \"created_on\": \"2018-03-03T00:35:28.353630+00:00\",\n \"user\": {\n \"username\": \"evzijst\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"evzijst\",\n \"type\": \"user\",\n \"uuid\": \"{aaa7972b-38af-4fb1-802d-6e3854c95778}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/evzijst\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst/\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/evzijst/avatar/32/\"\n }\n }\n },\n \"message\": {\n \"raw\": \"Removed assignee, changed kind and priority.\",\n \"markup\": \"markdown\",\n \"html\": \"

    Removed assignee, changed kind and priority.

    \",\n \"type\": \"rendered\"\n },\n \"type\": \"issue_change\",\n \"id\": 2\n }\n ],\n \"page\": 1\n}\n```\n\nChanges support [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) that\ncan be used to search for specific changes. For instance, to see\nwhen an issue transitioned to \"resolved\":\n\n```\n$ curl -s https://api.bitbucket.org/2.0/repositories/site/master/issues/1/changes \\\n -G --data-urlencode='q=changes.state.new = \"resolved\"'\n```\n\nThis resource is only available on repositories that have the issue\ntracker enabled.\n\nN.B.\n\nThe `changes.assignee` and `changes.assignee_account_id` fields are not\na `user` object. Instead, they contain the raw `username` and\n`account_id` of the user. This is to protect the integrity of the audit\nlog even after a user account gets deleted.\n\nThe `changes.assignee` field is deprecated will disappear in the\nfuture. Use `changes.assignee_account_id` instead.", + "description": "Returns the list of all changes that have been made to the specified\nissue. Changes are returned in chronological order with the oldest\nchange first.\n\nEach time an issue is edited in the UI or through the API, an immutable\nchange record is created under the `/issues/123/changes` endpoint. It\nalso has a comment associated with the change.\n\nNote that this operation is changing significantly, due to privacy changes.\nSee the [announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-changes-gdpr/#changes-to-the-issue-changes-api)\nfor details.\n\nChanges support [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) that\ncan be used to search for specific changes. For instance, to see\nwhen an issue transitioned to \"resolved\":\n\n```\n$ curl -s https://api.bitbucket.org/2.0/repositories/site/master/issues/1/changes \\\n -G --data-urlencode='q=changes.state.new = \"resolved\"'\n```\n\nThis resource is only available on repositories that have the issue\ntracker enabled.\n\nN.B.\n\nThe `changes.assignee` and `changes.assignee_account_id` fields are not\na `user` object. Instead, they contain the raw `username` and\n`account_id` of the user. This is to protect the integrity of the audit\nlog even after a user account gets deleted.\n\nThe `changes.assignee` field is deprecated will disappear in the\nfuture. Use `changes.assignee_account_id` instead.", "summary": "List changes on an issue", "responses": { "200": { @@ -6720,6 +7902,98 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_log_entries" + }, + "examples": { + "response": { + "value": { + "pagelen": 20, + "values": [ + { + "changes": { + "priority": { + "new": "trivial", + "old": "major" + }, + "assignee": { + "new": "", + "old": "evzijst" + }, + "assignee_account_id": { + "new": "", + "old": "557058:c0b72ad0-1cb5-4018-9cdc-0cde8492c443" + }, + "kind": { + "new": "enhancement", + "old": "bug" + } + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/issues/1/changes/2" + }, + "html": { + "href": "https://bitbucket.org/evzijst/dogslow/issues/1#comment-2" + } + }, + "issue": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/issues/1" + } + }, + "type": "issue", + "id": 1, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/evzijst/dogslow" + }, + "html": { + "href": "https://bitbucket.org/evzijst/dogslow" + }, + "avatar": { + "href": "https://bitbucket.org/evzijst/dogslow/avatar/32/" + } + }, + "type": "repository", + "name": "dogslow", + "full_name": "evzijst/dogslow", + "uuid": "{988b17c6-1a47-4e70-84ee-854d5f012bf6}" + }, + "title": "Updated title" + }, + "created_on": "2018-03-03T00:35:28.353630+00:00", + "user": { + "username": "evzijst", + "nickname": "evzijst", + "display_name": "evzijst", + "type": "user", + "uuid": "{aaa7972b-38af-4fb1-802d-6e3854c95778}", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/evzijst" + }, + "html": { + "href": "https://bitbucket.org/evzijst/" + }, + "avatar": { + "href": "https://bitbucket.org/account/evzijst/avatar/32/" + } + } + }, + "message": { + "raw": "Removed assignee, changed kind and priority.", + "markup": "markdown", + "html": "

    Removed assignee, changed kind and priority.

    ", + "type": "rendered" + }, + "type": "issue_change", + "id": 2 + } + ], + "page": 1 + } + } } } } @@ -7597,6 +8871,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -7795,6 +9076,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "put": { @@ -7826,6 +9114,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:repository:bitbucket"] + } ] }, "parameters": [ @@ -7879,6 +9174,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -7914,7 +9216,7 @@ "/repositories/{workspace}/{repo_slug}/permissions-config/groups": { "get": { "tags": ["Repositories"], - "description": "Returns a paginated list of explicit group permissions for the given repository.\nThis endpoint does not support BBQL features.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups\n\nHTTP/1.1 200\nLocation: https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_group_permission\",\n \"group\": {\n \"type\": \"group\",\n \"name\": \"Administrators\",\n \"slug\": \"administrators\"\n },\n \"permission\": \"admin\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/\n geordi/permissions-config/groups/administrators\"\n }\n }\n },\n {\n \"type\": \"repository_group_permission\",\n \"group\": {\n \"type\": \"group\",\n \"name\": \"Developers\",\n \"slug\": \"developers\"\n },\n \"permission\": \"read\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/\n geordi/permissions-config/groups/developers\"\n }\n }\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```", + "description": "Returns a paginated list of explicit group permissions for the given repository.\nThis endpoint does not support BBQL features.", "summary": "List explicit group permissions for a repository", "responses": { "200": { @@ -7923,6 +9225,45 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_repository_group_permissions" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "values": [ + { + "type": "repository_group_permission", + "group": { + "type": "group", + "name": "Administrators", + "slug": "administrators" + }, + "permission": "admin", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/administrators" + } + } + }, + { + "type": "repository_group_permission", + "group": { + "type": "group", + "name": "Developers", + "slug": "developers" + }, + "permission": "read", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers" + } + } + } + ], + "page": 1, + "size": 2 + } + } } } } @@ -7968,6 +9309,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket", "read:user:bitbucket"] + } ] }, "parameters": [ @@ -7994,7 +9342,7 @@ "/repositories/{workspace}/{repo_slug}/permissions-config/groups/{group_slug}": { "delete": { "tags": ["Repositories"], - "description": "Deletes the repository group permission between the requested repository and group, if one exists.\n\nOnly users with admin permission for the repository may access this resource.\n\nExample:\n\n$ curl -X DELETE https://api.bitbucket.org/2.0/repositories/atlassian_tutorial\n/geordi/permissions-config/groups/developers\n\n\nHTTP/1.1 204", + "description": "Deletes the repository group permission between the requested repository and group, if one exists.\n\nOnly users with admin permission for the repository may access this resource.", "summary": "Delete an explicit group permission for a repository", "responses": { "204": { @@ -8021,7 +9369,7 @@ } }, "404": { - "description": "The workspace does not exist, the repository does not exist,or the group does not exist.", + "description": "The workspace does not exist, the repository does not exist, or the group does not exist.", "content": { "application/json": { "schema": { @@ -8045,7 +9393,7 @@ }, "get": { "tags": ["Repositories"], - "description": "Returns the group permission for a given group slug and repository\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n* `none`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\n\nHTTP/1.1 200\nLocation:\nhttps://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\n\n{\n \"type\": \"repository_group_permission\",\n \"group\": {\n \"type\": \"group\",\n \"name\": \"Developers\",\n \"slug\": \"developers\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"read\",\n \"links\": {\n \"self\": {\n \"href\":\n \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\"\n }\n }\n}\n```", + "description": "Returns the group permission for a given group slug and repository\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n* `none`", "summary": "Get an explicit group permission for a repository", "responses": { "200": { @@ -8054,6 +9402,25 @@ "application/json": { "schema": { "$ref": "#/components/schemas/repository_group_permission" + }, + "examples": { + "response": { + "value": { + "type": "repository_group_permission", + "group": { + "type": "group", + "name": "Developers", + "slug": "developers", + "full_slug": "atlassian_tutorial:developers" + }, + "permission": "read", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers" + } + } + } + } } } } @@ -8099,11 +9466,18 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket", "read:user:bitbucket"] + } ] }, "put": { "tags": ["Repositories"], - "description": "Updates the group permission if it exists.\n\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method supported for this endpoint is via app passwords.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n```\n$ curl -X PUT -H \"Content-Type: application/json\"\nhttps://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\n-d\n'{\n \"permission\": \"write\"\n}'\n\nHTTP/1.1 200\nLocation:\nhttps://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\n\n{\n \"type\": \"repository_group_permission\",\n \"group\": {\n \"type\": \"group\",\n \"name\": \"Developers\",\n \"slug\": \"developers\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\",\n \"links\": {\n \"self\": {\n \"href\":\n \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\"\n }\n }\n}\n```", + "description": "Updates the group permission, or grants a new permission if one does not already exist.\n\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method supported for this endpoint is via app passwords.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`", "summary": "Update an explicit group permission for a repository", "responses": { "200": { @@ -8112,6 +9486,25 @@ "application/json": { "schema": { "$ref": "#/components/schemas/repository_group_permission" + }, + "examples": { + "response": { + "value": { + "type": "repository_group_permission", + "group": { + "type": "group", + "name": "Developers", + "slug": "developers", + "full_slug": "atlassian_tutorial:developers" + }, + "permission": "write", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers" + } + } + } + } } } } @@ -8157,7 +9550,7 @@ } }, "404": { - "description": "The workspace does not exist, the repository does not exist,or the group does not exist.", + "description": "The workspace does not exist, the repository does not exist, or the group does not exist.", "content": { "application/json": { "schema": { @@ -8167,6 +9560,9 @@ } } }, + "requestBody": { + "$ref": "#/components/requestBodies/bitbucket.apps.permissions.serializers.RepoPermissionUpdateSchema" + }, "security": [ { "oauth2": ["repository:admin"] @@ -8212,7 +9608,7 @@ "/repositories/{workspace}/{repo_slug}/permissions-config/users": { "get": { "tags": ["Repositories"], - "description": "Returns a paginated list of explicit user permissions for the given repository.\nThis endpoint does not support BBQL features.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/users\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_user_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Colin Cameron\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"account_id\": \"557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n },\n \"permission\": \"admin\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n }\n }\n },\n {\n \"type\": \"repository_user_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\",\n \"account_id\": \"557058:ba8948b2-49da-43a9-9e8b-e7249b8e324c\"\n },\n \"permission\": \"write\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0//repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324c\"\n }\n }\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```", + "description": "Returns a paginated list of explicit user permissions for the given repository.\nThis endpoint does not support BBQL features.", "summary": "List explicit user permissions for a repository", "responses": { "200": { @@ -8221,6 +9617,47 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_repository_user_permissions" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "values": [ + { + "type": "repository_user_permission", + "user": { + "type": "user", + "display_name": "Colin Cameron", + "uuid": "{d301aafa-d676-4ee0-88be-962be7417567}", + "account_id": "557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a" + }, + "permission": "admin", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a" + } + } + }, + { + "type": "repository_user_permission", + "user": { + "type": "user", + "display_name": "Sean Conaty", + "uuid": "{504c3b62-8120-4f0c-a7bc-87800b9d6f70}", + "account_id": "557058:ba8948b2-49da-43a9-9e8b-e7249b8e324c" + }, + "permission": "write", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0//repositories/atlassian_tutorial/geordi/permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324c" + } + } + } + ], + "page": 1, + "size": 2 + } + } } } } @@ -8266,6 +9703,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket", "read:user:bitbucket"] + } ] }, "parameters": [ @@ -8292,7 +9736,7 @@ "/repositories/{workspace}/{repo_slug}/permissions-config/users/{selected_user_id}": { "delete": { "tags": ["Repositories"], - "description": "Deletes the repository user permission between the requested repository and user, if one exists.\n\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method for this endpoint is via app passwords.\n\n```\n$ curl -X DELETE https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\npermissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\n\n\nHTTP/1.1 204\n```", + "description": "Deletes the repository user permission between the requested repository and user, if one exists.\n\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method for this endpoint is via app passwords.", "summary": "Delete an explicit user permission for a repository", "responses": { "204": { @@ -8343,7 +9787,7 @@ }, "get": { "tags": ["Repositories"], - "description": "Returns the explicit user permission for a given user and repository.\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n* `none`\n\nExample:\n\n```\n$ curl 'https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a'\n\nHTTP/1.1 200\nLocation: 'https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a'\n\n{\n \"type\": \"repository_user_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Colin Cameron\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"account_id\": \"557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n }\n }\n}\n```", + "description": "Returns the explicit user permission for a given user and repository.\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n* `none`", "summary": "Get an explicit user permission for a repository", "responses": { "200": { @@ -8352,6 +9796,26 @@ "application/json": { "schema": { "$ref": "#/components/schemas/repository_user_permission" + }, + "examples": { + "response": { + "value": { + "type": "repository_user_permission", + "user": { + "type": "user", + "display_name": "Colin Cameron", + "uuid": "{d301aafa-d676-4ee0-88be-962be7417567}", + "account_id": "557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a", + "nickname": "Colin Cameron" + }, + "permission": "admin", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a" + } + } + } + } } } } @@ -8397,11 +9861,18 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket", "read:user:bitbucket"] + } ] }, "put": { "tags": ["Repositories"], - "description": "Updates the explicit user permission for a given user and repository. The selected user must be a member of\nthe workspace, and cannot be the workspace owner.\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method for this endpoint is via app passwords.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl -X PUT -H \"Content-Type: application/json\" 'https://api.bitbucket.org/2.0/repositories/\natlassian_tutorial/geordi/permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a'\n-d '{\n \"permission\": \"write\"\n}'\n\nHTTP/1.1 200\nLocation: 'https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\npermissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a'\n\n\n{\n \"type\": \"repository_user_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Colin Cameron\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"account_id\": \"557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n }\n }\n}\n```", + "description": "Updates the explicit user permission for a given user and repository. The selected user must be a member of\nthe workspace, and cannot be the workspace owner.\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method for this endpoint is via app passwords.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`", "summary": "Update an explicit user permission for a repository", "responses": { "200": { @@ -8410,6 +9881,26 @@ "application/json": { "schema": { "$ref": "#/components/schemas/repository_user_permission" + }, + "examples": { + "response": { + "value": { + "type": "repository_user_permission", + "user": { + "type": "user", + "display_name": "Colin Cameron", + "uuid": "{d301aafa-d676-4ee0-88be-962be7417567}", + "account_id": "557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a", + "nickname": "Colin Cameron" + }, + "permission": "write", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a" + } + } + } + } } } } @@ -8465,6 +9956,9 @@ } } }, + "requestBody": { + "$ref": "#/components/requestBodies/bitbucket.apps.permissions.serializers.RepoPermissionUpdateSchema" + }, "security": [ { "oauth2": ["repository:admin"] @@ -8507,168 +10001,7 @@ } ] }, - "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/": { - "get": { - "tags": ["Pipelines"], - "summary": "List caches", - "description": "Retrieve the repository pipelines caches.", - "operationId": "getRepositoryPipelineCaches", - "parameters": [ - { - "name": "workspace", - "description": "The account.", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "repo_slug", - "description": "The repository.", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "The list of caches for the given repository.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_pipeline_caches" - } - } - } - }, - "404": { - "description": "The account or repository was not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - } - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/{cache_uuid}": { - "delete": { - "tags": ["Pipelines"], - "summary": "Delete a cache", - "description": "Delete a repository cache.", - "operationId": "deleteRepositoryPipelineCache", - "parameters": [ - { - "name": "workspace", - "description": "The account.", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "repo_slug", - "description": "The repository.", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "cache_uuid", - "description": "The UUID of the cache to delete.", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "The cache was deleted." - }, - "404": { - "description": "The workspace, repository or cache_uuid with given UUID was not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - } - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/{cache_uuid}/content-uri": { - "get": { - "tags": ["Pipelines"], - "summary": "Get cache content URI", - "description": "Retrieve the URI of the content of the specified cache.", - "operationId": "getRepositoryPipelineCacheContentURI", - "parameters": [ - { - "name": "workspace", - "description": "The account.", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "repo_slug", - "description": "The repository.", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - }, - { - "name": "cache_uuid", - "description": "The UUID of the cache.", - "required": true, - "in": "path", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "The cache content uri.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_cache_content_uri" - } - } - } - }, - "404": { - "description": "The workspace, repository or cache_uuid with given UUID was not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - } - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines/": { + "/repositories/{workspace}/{repo_slug}/pipelines": { "get": { "tags": ["Pipelines"], "summary": "List pipelines", @@ -8705,12 +10038,30 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "post": { "tags": ["Pipelines"], "summary": "Run a pipeline", - "description": "Endpoint to create and initiate a pipeline.\nThere are a couple of different options to initiate a pipeline, where the payload of the request will determine which type of pipeline will be instantiated.\n# Trigger a Pipeline for a branch\nOne way to trigger pipelines is by specifying the branch for which you want to trigger a pipeline.\nThe specified branch will be used to determine which pipeline definition from the `bitbucket-pipelines.yml` file will be applied to initiate the pipeline. The pipeline will then do a clone of the repository and checkout the latest revision of the specified branch.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"ref_type\": \"branch\",\n \"type\": \"pipeline_ref_target\",\n \"ref_name\": \"master\"\n }\n }'\n```\n# Trigger a Pipeline for a commit on a branch or tag\nYou can initiate a pipeline for a specific commit and in the context of a specified reference (e.g. a branch, tag or bookmark).\nThe specified reference will be used to determine which pipeline definition from the bitbucket-pipelines.yml file will be applied to initiate the pipeline. The pipeline will clone the repository and then do a checkout the specified reference.\n\nThe following reference types are supported:\n\n* `branch`\n* `named_branch`\n* `bookmark`\n * `tag`\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ce5b7431602f7cbba007062eeb55225c6e18e956\"\n },\n \"ref_type\": \"branch\",\n \"type\": \"pipeline_ref_target\",\n \"ref_name\": \"master\"\n }\n }'\n```\n# Trigger a specific pipeline definition for a commit\nYou can trigger a specific pipeline that is defined in your `bitbucket-pipelines.yml` file for a specific commit.\nIn addition to the commit revision, you specify the type and pattern of the selector that identifies the pipeline definition. The resulting pipeline will then clone the repository and checkout the specified revision.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"commit\": {\n \"hash\":\"a3c4e02c9a3755eccdc3764e6ea13facdf30f923\",\n \"type\":\"commit\"\n },\n \"selector\": {\n \"type\":\"custom\",\n \"pattern\":\"Deploy to production\"\n },\n \"type\":\"pipeline_commit_target\"\n }\n }'\n```\n# Trigger a specific pipeline definition for a commit on a branch or tag\nYou can trigger a specific pipeline that is defined in your `bitbucket-pipelines.yml` file for a specific commit in the context of a specified reference.\nIn addition to the commit revision, you specify the type and pattern of the selector that identifies the pipeline definition, as well as the reference information. The resulting pipeline will then clone the repository a checkout the specified reference.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"commit\": {\n \"hash\":\"a3c4e02c9a3755eccdc3764e6ea13facdf30f923\",\n \"type\":\"commit\"\n },\n \"selector\": {\n \"type\": \"custom\",\n \"pattern\": \"Deploy to production\"\n },\n \"type\": \"pipeline_ref_target\",\n \"ref_name\": \"master\",\n \"ref_type\": \"branch\"\n }\n }'\n```\n\n\n# Trigger a custom pipeline with variables\nIn addition to triggering a custom pipeline that is defined in your `bitbucket-pipelines.yml` file as shown in the examples above, you can specify variables that will be available for your build. In the request, provide a list of variables, specifying the following for each variable: key, value, and whether it should be secured or not (this field is optional and defaults to not secured).\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/{workspace}/{repo_slug}/pipelines/ \\\n -d '\n {\n \"target\": {\n \"type\": \"pipeline_ref_target\",\n \"ref_type\": \"branch\",\n \"ref_name\": \"master\",\n \"selector\": {\n \"type\": \"custom\",\n \"pattern\": \"Deploy to production\"\n }\n },\n \"variables\": [\n {\n \"key\": \"var1key\",\n \"value\": \"var1value\",\n \"secured\": true\n },\n {\n \"key\": \"var2key\",\n \"value\": \"var2value\"\n }\n ]\n }'\n```\n\n# Trigger a pull request pipeline\n\nYou can also initiate a pipeline for a specific pull request.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/{workspace}/{repo_slug}/pipelines/ \\\n -d '\n {\n\t\"target\": {\n \"type\": \"pipeline_pullrequest_target\",\n\t \"source\": \"pull-request-branch\",\n \"destination\": \"master\",\n \"destination_commit\": {\n \t \"hash\" : \"9f848b7\"\n },\n \"commit\": {\n \t\"hash\" : \"1a372fc\"\n },\n \"pullrequest\" : {\n \t\"id\" : \"3\"\n },\n\t \"selector\": {\n \"type\": \"pull-requests\",\n \"pattern\": \"**\"\n }\n }\n }'\n```\n", + "description": "Endpoint to create and initiate a pipeline.\nThere are a couple of different options to initiate a pipeline, where the payload of the request will determine which type of pipeline will be instantiated.\n# Trigger a Pipeline for a branch\nOne way to trigger pipelines is by specifying the branch for which you want to trigger a pipeline.\nThe specified branch will be used to determine which pipeline definition from the `bitbucket-pipelines.yml` file will be applied to initiate the pipeline. The pipeline will then do a clone of the repository and checkout the latest revision of the specified branch.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"ref_type\": \"branch\",\n \"type\": \"pipeline_ref_target\",\n \"ref_name\": \"master\"\n }\n }'\n```\n# Trigger a Pipeline for a commit on a branch or tag\nYou can initiate a pipeline for a specific commit and in the context of a specified reference (e.g. a branch, tag or bookmark).\nThe specified reference will be used to determine which pipeline definition from the bitbucket-pipelines.yml file will be applied to initiate the pipeline. The pipeline will clone the repository and then do a checkout the specified reference.\n\nThe following reference types are supported:\n\n* `branch`\n* `named_branch`\n* `bookmark`\n * `tag`\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ce5b7431602f7cbba007062eeb55225c6e18e956\"\n },\n \"ref_type\": \"branch\",\n \"type\": \"pipeline_ref_target\",\n \"ref_name\": \"master\"\n }\n }'\n```\n# Trigger a specific pipeline definition for a commit\nYou can trigger a specific pipeline that is defined in your `bitbucket-pipelines.yml` file for a specific commit.\nIn addition to the commit revision, you specify the type and pattern of the selector that identifies the pipeline definition. The resulting pipeline will then clone the repository and checkout the specified revision.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"commit\": {\n \"hash\":\"a3c4e02c9a3755eccdc3764e6ea13facdf30f923\",\n \"type\":\"commit\"\n },\n \"selector\": {\n \"type\":\"custom\",\n \"pattern\":\"Deploy to production\"\n },\n \"type\":\"pipeline_commit_target\"\n }\n }'\n```\n# Trigger a specific pipeline definition for a commit on a branch or tag\nYou can trigger a specific pipeline that is defined in your `bitbucket-pipelines.yml` file for a specific commit in the context of a specified reference.\nIn addition to the commit revision, you specify the type and pattern of the selector that identifies the pipeline definition, as well as the reference information. The resulting pipeline will then clone the repository a checkout the specified reference.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"commit\": {\n \"hash\":\"a3c4e02c9a3755eccdc3764e6ea13facdf30f923\",\n \"type\":\"commit\"\n },\n \"selector\": {\n \"type\": \"custom\",\n \"pattern\": \"Deploy to production\"\n },\n \"type\": \"pipeline_ref_target\",\n \"ref_name\": \"master\",\n \"ref_type\": \"branch\"\n }\n }'\n```\n\n\n# Trigger a custom pipeline with variables\nIn addition to triggering a custom pipeline that is defined in your `bitbucket-pipelines.yml` file as shown in the examples above, you can specify variables that will be available for your build. In the request, provide a list of variables, specifying the following for each variable: key, value, and whether it should be secured or not (this field is optional and defaults to not secured).\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/{workspace}/{repo_slug}/pipelines/ \\\n -d '\n {\n \"target\": {\n \"type\": \"pipeline_ref_target\",\n \"ref_type\": \"branch\",\n \"ref_name\": \"master\",\n \"selector\": {\n \"type\": \"custom\",\n \"pattern\": \"Deploy to production\"\n }\n },\n \"variables\": [\n {\n \"key\": \"var1key\",\n \"value\": \"var1value\",\n \"secured\": true\n },\n {\n \"key\": \"var2key\",\n \"value\": \"var2value\"\n }\n ]\n }'\n```\n\n# Trigger a pull request pipeline\n\nYou can also initiate a pipeline for a specific pull request.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/{workspace}/{repo_slug}/pipelines/ \\\n -d '\n {\n \"target\": {\n \"type\": \"pipeline_pullrequest_target\",\n \"source\": \"pull-request-branch\",\n \"destination\": \"master\",\n \"destination_commit\": {\n \"hash\": \"9f848b7\"\n },\n \"commit\": {\n \"hash\": \"1a372fc\"\n },\n \"pullrequest\": {\n \"id\": \"3\"\n },\n \"selector\": {\n \"type\": \"pull-requests\",\n \"pattern\": \"**\"\n }\n }\n }'\n```\n", "operationId": "createPipelineForRepository", "parameters": [ { @@ -8782,7 +10133,308 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket", "write:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines-config/caches": { + "get": { + "tags": ["Pipelines"], + "summary": "List caches", + "description": "Retrieve the repository pipelines caches.", + "operationId": "getRepositoryPipelineCaches", + "parameters": [ + { + "name": "workspace", + "description": "The account.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The list of caches for the given repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pipeline_caches" + } + } + } + }, + "404": { + "description": "The account or repository was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "delete": { + "tags": ["Pipelines"], + "summary": "Delete caches", + "description": "Delete repository cache versions by name.", + "operationId": "deleteRepositoryPipelineCaches", + "parameters": [ + { + "name": "workspace", + "description": "The account.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "name", + "description": "The cache name.", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The caches were deleted." + }, + "404": { + "description": "The workspace, repository or cache name was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["write:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/{cache_uuid}": { + "delete": { + "tags": ["Pipelines"], + "summary": "Delete a cache", + "description": "Delete a repository cache.", + "operationId": "deleteRepositoryPipelineCache", + "parameters": [ + { + "name": "workspace", + "description": "The account.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "cache_uuid", + "description": "The UUID of the cache to delete.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "The cache was deleted." + }, + "404": { + "description": "The workspace, repository or cache_uuid with given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["write:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/{cache_uuid}/content-uri": { + "get": { + "tags": ["Pipelines"], + "summary": "Get cache content URI", + "description": "Retrieve the URI of the content of the specified cache.", + "operationId": "getRepositoryPipelineCacheContentURI", + "parameters": [ + { + "name": "workspace", + "description": "The account.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "cache_uuid", + "description": "The UUID of the cache.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The cache content uri.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_cache_content_uri" + } + } + } + }, + "404": { + "description": "The workspace, repository or cache_uuid with given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}": { @@ -8841,10 +10493,28 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, - "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/": { + "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps": { "get": { "tags": ["Pipelines"], "summary": "List steps for a pipeline", @@ -8890,7 +10560,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}": { @@ -8958,7 +10646,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/log": { @@ -9039,7 +10745,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/logs/{log_uuid}": { @@ -9109,7 +10833,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/test_reports": { @@ -9169,7 +10911,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/test_reports/test_cases": { @@ -9229,7 +10989,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/test_reports/test_cases/{test_case_uuid}/test_case_reasons": { @@ -9298,7 +11076,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/stopPipeline": { @@ -9360,7 +11156,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["write:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines_config": { @@ -9400,7 +11214,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "put": { "tags": ["Pipelines"], @@ -9449,7 +11274,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines_config/build_number": { @@ -9520,10 +11356,21 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, - "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules/": { + "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules": { "post": { "tags": ["Pipelines"], "summary": "Create a schedule", @@ -9553,7 +11400,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/pipeline_schedule" + "$ref": "#/components/schemas/pipeline_schedule_post_request_body" } } }, @@ -9601,7 +11448,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket", "write:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "get": { "tags": ["Pipelines"], @@ -9649,7 +11514,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules/{schedule_uuid}": { @@ -9708,7 +11591,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "put": { "tags": ["Pipelines"], @@ -9748,7 +11649,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/pipeline_schedule" + "$ref": "#/components/schemas/pipeline_schedule_put_request_body" } } }, @@ -9776,7 +11677,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket", "write:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "tags": ["Pipelines"], @@ -9826,10 +11745,28 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["write:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, - "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules/{schedule_uuid}/executions/": { + "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules/{schedule_uuid}/executions": { "get": { "tags": ["Pipelines"], "summary": "List executions of a schedule", @@ -9885,7 +11822,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/key_pair": { @@ -9935,7 +11890,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "put": { "tags": ["Pipelines"], @@ -9994,7 +11967,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "tags": ["Pipelines"], @@ -10035,10 +12019,28 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, - "/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/known_hosts/": { + "/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/known_hosts": { "get": { "tags": ["Pipelines"], "summary": "List known hosts", @@ -10075,7 +12077,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "post": { "tags": ["Pipelines"], @@ -10152,7 +12172,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/known_hosts/{known_host_uuid}": { @@ -10211,7 +12242,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "put": { "tags": ["Pipelines"], @@ -10279,7 +12328,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "tags": ["Pipelines"], @@ -10329,10 +12389,28 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, - "/repositories/{workspace}/{repo_slug}/pipelines_config/variables/": { + "/repositories/{workspace}/{repo_slug}/pipelines_config/variables": { "get": { "tags": ["Pipelines"], "summary": "List variables for a repository", @@ -10369,7 +12447,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "post": { "tags": ["Pipelines"], @@ -10446,7 +12542,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pipelines_config/variables/{variable_uuid}": { @@ -10505,7 +12612,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "put": { "tags": ["Pipelines"], @@ -10573,7 +12698,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "tags": ["Pipelines"], @@ -10623,7 +12759,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/properties/{app_key}/{property_name}": { @@ -10677,7 +12831,18 @@ "requestBody": { "$ref": "#/components/requestBodies/application_property" }, - "tags": ["properties"] + "tags": ["properties"], + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "responses": { @@ -10726,7 +12891,18 @@ } } ], - "tags": ["properties"] + "tags": ["properties"], + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "get": { "responses": { @@ -10782,7 +12958,18 @@ } } ], - "tags": ["properties"] + "tags": ["properties"], + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/pullrequests": { @@ -10836,6 +13023,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "post": { @@ -10902,6 +13096,16 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": [ + "read:pullrequest:bitbucket", + "write:pullrequest:bitbucket" + ] + } ] }, "parameters": [ @@ -10958,6 +13162,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -11021,6 +13232,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "put": { @@ -11089,6 +13307,16 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": [ + "read:pullrequest:bitbucket", + "write:pullrequest:bitbucket" + ] + } ] }, "parameters": [ @@ -11154,6 +13382,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -11195,6 +13430,16 @@ "204": { "description": "An empty response indicating the authenticated user's approval has been withdrawn." }, + "400": { + "description": "Pull request cannot be unapproved because the pull request has already been merged.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, "401": { "description": "The request wasn't authenticated.", "content": { @@ -11226,6 +13471,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["write:pullrequest:bitbucket"] + } ] }, "post": { @@ -11274,6 +13526,16 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": [ + "read:pullrequest:bitbucket", + "write:pullrequest:bitbucket" + ] + } ] }, "parameters": [ @@ -11353,6 +13615,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "post": { @@ -11420,6 +13689,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -11492,6 +13768,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "get": { @@ -11540,6 +13823,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "put": { @@ -11599,6 +13889,166 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } + ] + }, + "parameters": [ + { + "name": "comment_id", + "in": "path", + "description": "The id of the comment.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "repo_slug", + "in": "path", + "description": "This can either be the repository slug or the UUID of the repository,\nsurrounded by curly-braces, for example: `{repository UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/comments/{comment_id}/resolve": { + "delete": { + "tags": ["Pullrequests"], + "description": "", + "summary": "Reopen a comment thread", + "responses": { + "204": { + "description": "The comment is reopened." + }, + "403": { + "description": "If the authenticated user does not have access to the pull request,\nor if the provided comment is not a top-level comment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the comment does not exist, or if the comment has not been resolved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } + ] + }, + "post": { + "tags": ["Pullrequests"], + "description": "", + "summary": "Resolve a comment thread", + "responses": { + "200": { + "description": "The comment resolution details.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/comment_resolution" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the pull request,\nif the provided comment is not a top-level comment,\nor if the comment is not on the diff.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the comment does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "409": { + "description": "If the comment has already been resolved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -11680,6 +14130,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -11749,6 +14206,16 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": [ + "read:pullrequest:bitbucket", + "write:pullrequest:bitbucket" + ] + } ] }, "parameters": [ @@ -11801,6 +14268,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -11853,6 +14327,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -11945,6 +14426,16 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": [ + "read:pullrequest:bitbucket", + "write:pullrequest:bitbucket" + ] + } ] }, "parameters": [ @@ -12003,6 +14494,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -12064,6 +14562,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -12105,6 +14610,16 @@ "204": { "description": "An empty response indicating the authenticated user's request for change has been withdrawn." }, + "400": { + "description": "Pull request requested changes cannot be removed because the pull request has already been merged.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, "401": { "description": "The request wasn't authenticated.", "content": { @@ -12136,6 +14651,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["write:pullrequest:bitbucket"] + } ] }, "post": { @@ -12153,6 +14675,16 @@ } } }, + "400": { + "description": "Pull request changes cannot be requested because the pull request has already been merged.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, "401": { "description": "The request wasn't authenticated.", "content": { @@ -12184,6 +14716,16 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": [ + "read:pullrequest:bitbucket", + "write:pullrequest:bitbucket" + ] + } ] }, "parameters": [ @@ -12276,6 +14818,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -12308,6 +14857,434 @@ } ] }, + "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/tasks": { + "get": { + "tags": ["Pullrequests"], + "description": "Returns a paginated list of the pull request's tasks.\n\nThis endpoint supports filtering and sorting of the results by the 'task' field.\nSee [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details.", + "summary": "List tasks on a pull request", + "responses": { + "200": { + "description": "A paginated list of pull request tasks for the given pull request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_tasks" + } + } + } + }, + "400": { + "description": "If the user provides an invalid filter, sort, or fields query parameter.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the pull request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the pull request does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "q", + "in": "query", + "description": "\nQuery string to narrow down the response. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for details.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "\nField by which the results should be sorted as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\nDefaults to `created_on`.\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "pagelen", + "in": "query", + "description": "\nCurrent number of objects on the existing page.\nThe default value is 10 with 100 being the maximum allowed value.\nIndividual APIs may enforce different values.\n", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } + ] + }, + "post": { + "tags": ["Pullrequests"], + "description": "Creates a new pull request task.\n\nReturns the newly created pull request task.\n\nTasks can optionally be created in relation to a comment specified by the comment's ID which\nwill cause the task to appear below the comment on a pull request when viewed in Bitbucket.", + "summary": "Create a task on a pull request", + "responses": { + "201": { + "description": "The newly created task.", + "headers": { + "Location": { + "description": "The URL of the new task", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/A_pullrequest_comment_task" + } + } + } + }, + "400": { + "description": "There is a missing required field in the request or the task content is blank.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the pull request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the pull request does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/A_pullrequest_task_create" + } + } + }, + "description": "The contents of the task", + "required": true + }, + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } + ] + }, + "parameters": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "repo_slug", + "in": "path", + "description": "This can either be the repository slug or the UUID of the repository,\nsurrounded by curly-braces, for example: `{repository UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/tasks/{task_id}": { + "delete": { + "tags": ["Pullrequests"], + "description": "Deletes a specific pull request task.", + "summary": "Delete a task on a pull request", + "responses": { + "204": { + "description": "Successful deletion." + }, + "403": { + "description": "If the authenticated user does not have access to delete the task.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the task does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } + ] + }, + "get": { + "tags": ["Pullrequests"], + "description": "Returns a specific pull request task.", + "summary": "Get a task on a pull request", + "responses": { + "200": { + "description": "The task.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/A_pullrequest_comment_task" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the pull request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the task does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } + ] + }, + "put": { + "tags": ["Pullrequests"], + "description": "Updates a specific pull request task.", + "summary": "Update a task on a pull request", + "responses": { + "200": { + "description": "The updated task.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/A_pullrequest_comment_task" + } + } + } + }, + "400": { + "description": "There is a missing required field in the request or the task content is blank.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the pull request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the task does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/A_pullrequest_task_update" + } + } + }, + "description": "The updated state and content of the task.", + "required": true + }, + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } + ] + }, + "parameters": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "repo_slug", + "in": "path", + "description": "This can either be the repository slug or the UUID of the repository,\nsurrounded by curly-braces, for example: `{repository UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "task_id", + "in": "path", + "description": "The ID of the task.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pullrequest_id}/properties/{app_key}/{property_name}": { "put": { "responses": { @@ -12368,7 +15345,18 @@ "requestBody": { "$ref": "#/components/requestBodies/application_property" }, - "tags": ["properties"] + "tags": ["properties"], + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "responses": { @@ -12426,7 +15414,18 @@ } } ], - "tags": ["properties"] + "tags": ["properties"], + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "get": { "responses": { @@ -12491,7 +15490,18 @@ } } ], - "tags": ["properties"] + "tags": ["properties"], + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/repositories/{workspace}/{repo_slug}/refs": { @@ -12559,6 +15569,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -12585,7 +15602,7 @@ "/repositories/{workspace}/{repo_slug}/refs/branches": { "get": { "tags": ["Refs"], - "description": "Returns a list of all open branches within the specified repository.\n Results will be in the order the source control manager returns them.\n\n ```\n $ curl -s https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches?pagelen=1 | jq .\n {\n \"pagelen\": 1,\n \"size\": 187,\n \"values\": [\n {\n \"name\": \"issue-9.3/AUI-5343-assistive-class\",\n \"links\": {\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commits/issue-9.3/AUI-5343-assistive-class\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches/issue-9.3/AUI-5343-assistive-class\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/branch/issue-9.3/AUI-5343-assistive-class\"\n }\n },\n \"default_merge_strategy\": \"squash\",\n \"merge_strategies\": [\n \"merge_commit\",\n \"squash\",\n \"fast_forward\"\n ],\n \"type\": \"branch\",\n \"target\": {\n \"hash\": \"e5d1cde9069fcb9f0af90403a4de2150c125a148\",\n \"repository\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B585074de-7b60-4fd1-81ed-e0bc7fafbda5%7D?ts=86317\"\n }\n },\n \"type\": \"repository\",\n \"name\": \"aui\",\n \"full_name\": \"atlassian/aui\",\n \"uuid\": \"{585074de-7b60-4fd1-81ed-e0bc7fafbda5}\"\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148/comments\"\n },\n \"patch\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/patch/e5d1cde9069fcb9f0af90403a4de2150c125a148\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/commits/e5d1cde9069fcb9f0af90403a4de2150c125a148\"\n },\n \"diff\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/diff/e5d1cde9069fcb9f0af90403a4de2150c125a148\"\n },\n \"approve\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148/approve\"\n },\n \"statuses\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148/statuses\"\n }\n },\n \"author\": {\n \"raw\": \"Marcin Konopka \",\n \"type\": \"author\",\n \"user\": {\n \"display_name\": \"Marcin Konopka\",\n \"uuid\": \"{47cc24f4-2a05-4420-88fe-0417535a110a}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/%7B47cc24f4-2a05-4420-88fe-0417535a110a%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B47cc24f4-2a05-4420-88fe-0417535a110a%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/initials/MK-1.png\"\n }\n },\n \"nickname\": \"Marcin Konopka\",\n \"type\": \"user\",\n \"account_id\": \"60113d2b47a9540069f4de03\"\n }\n },\n \"parents\": [\n {\n \"hash\": \"87f7fc92b00464ae47b13ef65c91884e4ac9be51\",\n \"type\": \"commit\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/87f7fc92b00464ae47b13ef65c91884e4ac9be51\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/commits/87f7fc92b00464ae47b13ef65c91884e4ac9be51\"\n }\n }\n }\n ],\n \"date\": \"2021-04-13T13:44:49+00:00\",\n \"message\": \"wip\n\",\n \"type\": \"commit\"\n }\n }\n ],\n \"page\": 1,\n \"next\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches?pagelen=1&page=2\"\n }\n ```\n\n Branches support [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering)\n that can be used to search for specific branches. For instance, to find\n all branches that have \"stab\" in their name:\n\n ```\n curl -s https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches -G --data-urlencode 'q=name ~ \"stab\"'\n ```\n\n By default, results will be in the order the underlying source control system returns them and identical to\n the ordering one sees when running \"$ git branch --list\". Note that this follows simple\n lexical ordering of the ref names.\n\n This can be undesirable as it does apply any natural sorting semantics, meaning for instance that tags are\n sorted [\"v10\", \"v11\", \"v9\"] instead of [\"v9\", \"v10\", \"v11\"].\n\n Sorting can be changed using the ?q= query parameter. When using ?q=name to explicitly sort on ref name,\n Bitbucket will apply natural sorting and interpret numerical values as numbers instead of strings.", + "description": "Returns a list of all open branches within the specified repository.\nResults will be in the order the source control manager returns them.\n\nBranches support [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering)\nthat can be used to search for specific branches. For instance, to find\nall branches that have \"stab\" in their name:\n\n```\ncurl -s https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches -G --data-urlencode 'q=name ~ \"stab\"'\n```\n\nBy default, results will be in the order the underlying source control system returns them and identical to\nthe ordering one sees when running \"$ git branch --list\". Note that this follows simple\nlexical ordering of the ref names.\n\nThis can be undesirable as it does apply any natural sorting semantics, meaning for instance that tags are\nsorted [\"v10\", \"v11\", \"v9\"] instead of [\"v9\", \"v10\", \"v11\"].\n\nSorting can be changed using the ?q= query parameter. When using ?q=name to explicitly sort on ref name,\nBitbucket will apply natural sorting and interpret numerical values as numbers instead of strings.", "summary": "List open branches", "responses": { "200": { @@ -12594,6 +15611,121 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_branches" + }, + "examples": { + "response": { + "value": { + "pagelen": 1, + "size": 187, + "values": [ + { + "name": "issue-9.3/AUI-5343-assistive-class", + "links": { + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/commits/issue-9.3/AUI-5343-assistive-class" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches/issue-9.3/AUI-5343-assistive-class" + }, + "html": { + "href": "https://bitbucket.org/atlassian/aui/branch/issue-9.3/AUI-5343-assistive-class" + } + }, + "default_merge_strategy": "squash", + "merge_strategies": [ + "merge_commit", + "squash", + "fast_forward" + ], + "type": "branch", + "target": { + "hash": "e5d1cde9069fcb9f0af90403a4de2150c125a148", + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui" + }, + "html": { + "href": "https://bitbucket.org/atlassian/aui" + }, + "avatar": { + "href": "https://bytebucket.org/ravatar/%7B585074de-7b60-4fd1-81ed-e0bc7fafbda5%7D?ts=86317" + } + }, + "type": "repository", + "name": "aui", + "full_name": "atlassian/aui", + "uuid": "{585074de-7b60-4fd1-81ed-e0bc7fafbda5}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148" + }, + "comments": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148/comments" + }, + "patch": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/patch/e5d1cde9069fcb9f0af90403a4de2150c125a148" + }, + "html": { + "href": "https://bitbucket.org/atlassian/aui/commits/e5d1cde9069fcb9f0af90403a4de2150c125a148" + }, + "diff": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/diff/e5d1cde9069fcb9f0af90403a4de2150c125a148" + }, + "approve": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148/approve" + }, + "statuses": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148/statuses" + } + }, + "author": { + "raw": "Marcin Konopka ", + "type": "author", + "user": { + "display_name": "Marcin Konopka", + "uuid": "{47cc24f4-2a05-4420-88fe-0417535a110a}", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7B47cc24f4-2a05-4420-88fe-0417535a110a%7D" + }, + "html": { + "href": "https://bitbucket.org/%7B47cc24f4-2a05-4420-88fe-0417535a110a%7D/" + }, + "avatar": { + "href": "https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/initials/MK-1.png" + } + }, + "nickname": "Marcin Konopka", + "type": "user", + "account_id": "60113d2b47a9540069f4de03" + } + }, + "parents": [ + { + "hash": "87f7fc92b00464ae47b13ef65c91884e4ac9be51", + "type": "commit", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/87f7fc92b00464ae47b13ef65c91884e4ac9be51" + }, + "html": { + "href": "https://bitbucket.org/atlassian/aui/commits/87f7fc92b00464ae47b13ef65c91884e4ac9be51" + } + } + } + ], + "date": "2021-04-13T13:44:49+00:00", + "message": "wip\n", + "type": "commit" + } + } + ], + "page": 1, + "next": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches?pagelen=1&page=2" + } + } } } } @@ -12647,6 +15779,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "post": { @@ -12695,6 +15834,16 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": [ + "write:repository:bitbucket", + "read:repository:bitbucket" + ] + } ] }, "parameters": [ @@ -12758,11 +15907,18 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["write:repository:bitbucket"] + } ] }, "get": { "tags": ["Refs"], - "description": "Returns a branch object within the specified repository.\n\n ```\n $ curl -s https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches/master | jq .\n {\n \"name\": \"master\",\n \"links\": {\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commits/master\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches/master\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/branch/master\"\n }\n },\n \"default_merge_strategy\": \"squash\",\n \"merge_strategies\": [\n \"merge_commit\",\n \"squash\",\n \"fast_forward\"\n ],\n \"type\": \"branch\",\n \"target\": {\n \"hash\": \"e7d158ff7ed5538c28f94cd97a9ad569680fc94e\",\n \"repository\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B585074de-7b60-4fd1-81ed-e0bc7fafbda5%7D?ts=86317\"\n }\n },\n \"type\": \"repository\",\n \"name\": \"aui\",\n \"full_name\": \"atlassian/aui\",\n \"uuid\": \"{585074de-7b60-4fd1-81ed-e0bc7fafbda5}\"\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e/comments\"\n },\n \"patch\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/patch/e7d158ff7ed5538c28f94cd97a9ad569680fc94e\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/commits/e7d158ff7ed5538c28f94cd97a9ad569680fc94e\"\n },\n \"diff\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/diff/e7d158ff7ed5538c28f94cd97a9ad569680fc94e\"\n },\n \"approve\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e/approve\"\n },\n \"statuses\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e/statuses\"\n }\n },\n \"author\": {\n \"raw\": \"psre-renovate-bot \",\n \"type\": \"author\",\n \"user\": {\n \"display_name\": \"psre-renovate-bot\",\n \"uuid\": \"{250a442a-3ab3-4fcb-87c3-3c8f3df65ec7}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/%7B250a442a-3ab3-4fcb-87c3-3c8f3df65ec7%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B250a442a-3ab3-4fcb-87c3-3c8f3df65ec7%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://secure.gravatar.com/avatar/6972ee037c9f36360170a86f544071a2?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FP-3.png\"\n }\n },\n \"nickname\": \"Renovate Bot\",\n \"type\": \"user\",\n \"account_id\": \"5d5355e8c6b9320d9ea5b28d\"\n }\n },\n \"parents\": [\n {\n \"hash\": \"eab868a309e75733de80969a7bed1ec6d4651e06\",\n \"type\": \"commit\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/eab868a309e75733de80969a7bed1ec6d4651e06\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/commits/eab868a309e75733de80969a7bed1ec6d4651e06\"\n }\n }\n }\n ],\n \"date\": \"2021-04-12T06:44:38+00:00\",\n \"message\": \"Merged in issue/NONE-renovate-master-babel-monorepo (pull request #2883)\n\nchore(deps): update babel monorepo to v7.13.15 (master)\n\nApproved-by: Chris \"Daz\" Darroch\n\",\n \"type\": \"commit\"\n }\n }\n ```\n\n This call requires authentication. Private repositories require the\n caller to authenticate with an account that has appropriate\n authorization.\n\n For Git, the branch name should not include any prefixes (e.g.\n refs/heads).", + "description": "Returns a branch object within the specified repository.\n\nThis call requires authentication. Private repositories require the\ncaller to authenticate with an account that has appropriate\nauthorization.\n\nFor Git, the branch name should not include any prefixes (e.g.\nrefs/heads).", "summary": "Get a branch", "responses": { "200": { @@ -12771,6 +15927,113 @@ "application/json": { "schema": { "$ref": "#/components/schemas/branch" + }, + "examples": { + "response": { + "value": { + "name": "master", + "links": { + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/commits/master" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches/master" + }, + "html": { + "href": "https://bitbucket.org/atlassian/aui/branch/master" + } + }, + "default_merge_strategy": "squash", + "merge_strategies": [ + "merge_commit", + "squash", + "fast_forward" + ], + "type": "branch", + "target": { + "hash": "e7d158ff7ed5538c28f94cd97a9ad569680fc94e", + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui" + }, + "html": { + "href": "https://bitbucket.org/atlassian/aui" + }, + "avatar": { + "href": "https://bytebucket.org/ravatar/%7B585074de-7b60-4fd1-81ed-e0bc7fafbda5%7D?ts=86317" + } + }, + "type": "repository", + "name": "aui", + "full_name": "atlassian/aui", + "uuid": "{585074de-7b60-4fd1-81ed-e0bc7fafbda5}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e" + }, + "comments": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e/comments" + }, + "patch": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/patch/e7d158ff7ed5538c28f94cd97a9ad569680fc94e" + }, + "html": { + "href": "https://bitbucket.org/atlassian/aui/commits/e7d158ff7ed5538c28f94cd97a9ad569680fc94e" + }, + "diff": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/diff/e7d158ff7ed5538c28f94cd97a9ad569680fc94e" + }, + "approve": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e/approve" + }, + "statuses": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e/statuses" + } + }, + "author": { + "raw": "psre-renovate-bot ", + "type": "author", + "user": { + "display_name": "psre-renovate-bot", + "uuid": "{250a442a-3ab3-4fcb-87c3-3c8f3df65ec7}", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7B250a442a-3ab3-4fcb-87c3-3c8f3df65ec7%7D" + }, + "html": { + "href": "https://bitbucket.org/%7B250a442a-3ab3-4fcb-87c3-3c8f3df65ec7%7D/" + }, + "avatar": { + "href": "https://secure.gravatar.com/avatar/6972ee037c9f36360170a86f544071a2?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FP-3.png" + } + }, + "nickname": "Renovate Bot", + "type": "user", + "account_id": "5d5355e8c6b9320d9ea5b28d" + } + }, + "parents": [ + { + "hash": "eab868a309e75733de80969a7bed1ec6d4651e06", + "type": "commit", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/eab868a309e75733de80969a7bed1ec6d4651e06" + }, + "html": { + "href": "https://bitbucket.org/atlassian/aui/commits/eab868a309e75733de80969a7bed1ec6d4651e06" + } + } + } + ], + "date": "2021-04-12T06:44:38+00:00", + "message": "Merged in issue/NONE-renovate-master-babel-monorepo (pull request #2883)\n\nchore(deps): update babel monorepo to v7.13.15 (master)\n\nApproved-by: Chris \"Daz\" Darroch\n", + "type": "commit" + } + } + } } } } @@ -12806,6 +16069,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -12903,6 +16173,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "post": { @@ -12951,6 +16228,16 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": [ + "write:repository:bitbucket", + "read:repository:bitbucket" + ] + } ] }, "parameters": [ @@ -13014,6 +16301,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["write:repository:bitbucket"] + } ] }, "get": { @@ -13062,6 +16356,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -13143,11 +16444,18 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "post": { "tags": ["Source", "Repositories"], - "description": "This endpoint is used to create new commits in the repository by\nuploading files.\n\nTo add a new file to a repository:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/username/slug/src \\\n -F /repo/path/to/image.png=@image.png\n```\n\nThis will create a new commit on top of the main branch, inheriting the\ncontents of the main branch, but adding (or overwriting) the\n`image.png` file to the repository in the `/repo/path/to` directory.\n\nTo create a commit that deletes files, use the `files` parameter:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/username/slug/src \\\n -F files=/file/to/delete/1.txt \\\n -F files=/file/to/delete/2.txt\n```\n\nYou can add/modify/delete multiple files in a request. Rename/move a\nfile by deleting the old path and adding the content at the new path.\n\nThis endpoint accepts `multipart/form-data` (as in the examples above),\nas well as `application/x-www-form-urlencoded`.\n\n#### multipart/form-data\n\nA `multipart/form-data` post contains a series of \"form fields\" that\nidentify both the individual files that are being uploaded, as well as\nadditional, optional meta data.\n\nFiles are uploaded in file form fields (those that have a\n`Content-Disposition` parameter) whose field names point to the remote\npath in the repository where the file should be stored. Path field\nnames are always interpreted to be absolute from the root of the\nrepository, regardless whether the client uses a leading slash (as the\nabove `curl` example did).\n\nFile contents are treated as bytes and are not decoded as text.\n\nThe commit message, as well as other non-file meta data for the\nrequest, is sent along as normal form field elements. Meta data fields\nshare the same namespace as the file objects. For `multipart/form-data`\nbodies that should not lead to any ambiguity, as the\n`Content-Disposition` header will contain the `filename` parameter to\ndistinguish between a file named \"message\" and the commit message field.\n\n#### application/x-www-form-urlencoded\n\nIt is also possible to upload new files using a simple\n`application/x-www-form-urlencoded` POST. This can be convenient when\nuploading pure text files:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src \\\n --data-urlencode \"/path/to/me.txt=Lorem ipsum.\" \\\n --data-urlencode \"message=Initial commit\" \\\n --data-urlencode \"author=Erik van Zijst \"\n```\n\nThere could be a field name clash if a client were to upload a file\nnamed \"message\", as this filename clashes with the meta data property\nfor the commit message. To avoid this and to upload files whose names\nclash with the meta data properties, use a leading slash for the files,\ne.g. `curl --data-urlencode \"/message=file contents\"`.\n\nWhen an explicit slash is omitted for a file whose path matches that of\na meta data parameter, then it is interpreted as meta data, not as a\nfile.\n\n#### Executables and links\n\nWhile this API aims to facilitate the most common use cases, it is\npossible to perform some more advanced operations like creating a new\nsymlink in the repository, or creating an executable file.\n\nFiles can be supplied with a `x-attributes` value in the\n`Content-Disposition` header. For example, to upload an executable\nfile, as well as create a symlink from `README.txt` to `README`:\n\n```\n--===============1438169132528273974==\nContent-Type: text/plain; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-ID: \"bin/shutdown.sh\"\nContent-Disposition: attachment; filename=\"shutdown.sh\"; x-attributes:\"executable\"\n\n#!/bin/sh\nhalt\n\n--===============1438169132528273974==\nContent-Type: text/plain; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-ID: \"/README.txt\"\nContent-Disposition: attachment; filename=\"README.txt\"; x-attributes:\"link\"\n\nREADME\n--===============1438169132528273974==--\n```\n\nLinks are files that contain the target path and have\n`x-attributes:\"link\"` set.\n\nWhen overwriting links with files, or vice versa, the newly uploaded\nfile determines both the new contents, as well as the attributes. That\nmeans uploading a file without specifying `x-attributes=\"link\"` will\ncreate a regular file, even if the parent commit hosted a symlink at\nthe same path.\n\nThe same applies to executables. When modifying an existing executable\nfile, the form-data file element must include\n`x-attributes=\"executable\"` in order to preserve the executable status\nof the file.\n\nNote that this API does not support the creation or manipulation of\nsubrepos / submodules.", + "description": "This endpoint is used to create new commits in the repository by\nuploading files.\n\nTo add a new file to a repository:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/username/slug/src \\\n -F /repo/path/to/image.png=@image.png\n```\n\nThis will create a new commit on top of the main branch, inheriting the\ncontents of the main branch, but adding (or overwriting) the\n`image.png` file to the repository in the `/repo/path/to` directory.\n\nTo create a commit that deletes files, use the `files` parameter:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/username/slug/src \\\n -F files=/file/to/delete/1.txt \\\n -F files=/file/to/delete/2.txt\n```\n\nYou can add/modify/delete multiple files in a request. Rename/move a\nfile by deleting the old path and adding the content at the new path.\n\nThis endpoint accepts `multipart/form-data` (as in the examples above),\nas well as `application/x-www-form-urlencoded`.\n\nNote: `multipart/form-data` is currently not supported by Forge apps\nfor this API.\n\n#### multipart/form-data\n\nA `multipart/form-data` post contains a series of \"form fields\" that\nidentify both the individual files that are being uploaded, as well as\nadditional, optional meta data.\n\nFiles are uploaded in file form fields (those that have a\n`Content-Disposition` parameter) whose field names point to the remote\npath in the repository where the file should be stored. Path field\nnames are always interpreted to be absolute from the root of the\nrepository, regardless whether the client uses a leading slash (as the\nabove `curl` example did).\n\nFile contents are treated as bytes and are not decoded as text.\n\nThe commit message, as well as other non-file meta data for the\nrequest, is sent along as normal form field elements. Meta data fields\nshare the same namespace as the file objects. For `multipart/form-data`\nbodies that should not lead to any ambiguity, as the\n`Content-Disposition` header will contain the `filename` parameter to\ndistinguish between a file named \"message\" and the commit message field.\n\n#### application/x-www-form-urlencoded\n\nIt is also possible to upload new files using a simple\n`application/x-www-form-urlencoded` POST. This can be convenient when\nuploading pure text files:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src \\\n --data-urlencode \"/path/to/me.txt=Lorem ipsum.\" \\\n --data-urlencode \"message=Initial commit\" \\\n --data-urlencode \"author=Erik van Zijst \"\n```\n\nThere could be a field name clash if a client were to upload a file\nnamed \"message\", as this filename clashes with the meta data property\nfor the commit message. To avoid this and to upload files whose names\nclash with the meta data properties, use a leading slash for the files,\ne.g. `curl --data-urlencode \"/message=file contents\"`.\n\nWhen an explicit slash is omitted for a file whose path matches that of\na meta data parameter, then it is interpreted as meta data, not as a\nfile.\n\n#### Executables and links\n\nWhile this API aims to facilitate the most common use cases, it is\npossible to perform some more advanced operations like creating a new\nsymlink in the repository, or creating an executable file.\n\nFiles can be supplied with a `x-attributes` value in the\n`Content-Disposition` header. For example, to upload an executable\nfile, as well as create a symlink from `README.txt` to `README`:\n\n```\n--===============1438169132528273974==\nContent-Type: text/plain; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-ID: \"bin/shutdown.sh\"\nContent-Disposition: attachment; filename=\"shutdown.sh\"; x-attributes:\"executable\"\n\n#!/bin/sh\nhalt\n\n--===============1438169132528273974==\nContent-Type: text/plain; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-ID: \"/README.txt\"\nContent-Disposition: attachment; filename=\"README.txt\"; x-attributes:\"link\"\n\nREADME\n--===============1438169132528273974==--\n```\n\nLinks are files that contain the target path and have\n`x-attributes:\"link\"` set.\n\nWhen overwriting links with files, or vice versa, the newly uploaded\nfile determines both the new contents, as well as the attributes. That\nmeans uploading a file without specifying `x-attributes=\"link\"` will\ncreate a regular file, even if the parent commit hosted a symlink at\nthe same path.\n\nThe same applies to executables. When modifying an existing executable\nfile, the form-data file element must include\n`x-attributes=\"executable\"` in order to preserve the executable status\nof the file.\n\nNote that this API does not support the creation or manipulation of\nsubrepos / submodules.", "summary": "Create a commit by uploading a file", "responses": { "201": { @@ -13231,6 +16539,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["write:repository:bitbucket"] + } ] }, "parameters": [ @@ -13281,7 +16596,7 @@ } }, "555": { - "description": "If the call times out, possibly because the specifiedrecursion depth is too large.", + "description": "If the call times out, possibly because the specified recursion depth is too large.", "content": { "application/json": { "schema": { @@ -13295,7 +16610,7 @@ { "name": "format", "in": "query", - "description": "If 'meta' is provided, returns the (json) meta data for the contents of the file. If 'rendered' is provided, returns the contents of a non-binary file in HTML-formatted rendered markup. Since Git does not generally track what text encoding scheme is used, this endpoint attempts to detect the most appropriate character encoding. While usually correct, determining the character encoding can be ambiguous which in exceptional cases can lead to misinterpretation of the characters. As such, the raw element in the response object should not be treated as equivalent to the file's actual contents.", + "description": "If 'meta' is provided, returns the (json) meta data for the contents of the file. If 'rendered' is provided, returns the contents of a non-binary file in HTML-formatted rendered markup. The 'rendered' option only supports these filetypes: `.md`, `.markdown`, `.mkd`, `.mkdn`, `.mdown`, `.text`, `.rst`, and `.textile`. Since Git does not generally track what text encoding scheme is used, this endpoint attempts to detect the most appropriate character encoding. While usually correct, determining the character encoding can be ambiguous which in exceptional cases can lead to misinterpretation of the characters. As such, the raw element in the response object should not be treated as equivalent to the file's actual contents.", "required": false, "schema": { "type": "string", @@ -13340,6 +16655,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -13537,6 +16859,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -15371,7 +18700,7 @@ } ] }, - "/teams/{username}/pipelines_config/variables/": { + "/teams/{username}/pipelines_config/variables": { "get": { "tags": ["Pipelines"], "summary": "List variables for an account", @@ -15400,7 +18729,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "post": { "tags": ["Pipelines"], @@ -15461,7 +18801,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/teams/{username}/pipelines_config/variables/{variable_uuid}": { @@ -15512,7 +18863,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "put": { "tags": ["Pipelines"], @@ -15564,7 +18926,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "tags": ["Pipelines"], @@ -15606,14 +18979,25 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/teams/{username}/search/code": { "get": { "tags": ["Search"], "summary": "Search for code in a team's repositories", - "description": "Search for code in the repositories of the specified team.\n\nSearching across all repositories:\n\n```\ncurl 'https://api.bitbucket.org/2.0/teams/team_name/search/code?search_query=foo'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 2,\n \"content_matches\": [\n {\n \"lines\": [\n {\n \"line\": 2,\n \"segments\": []\n },\n {\n \"line\": 3,\n \"segments\": [\n {\n \"text\": \"def \"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \"():\"\n }\n ]\n },\n {\n \"line\": 4,\n \"segments\": [\n {\n \"text\": \" print(\\\"snek\\\")\"\n }\n ]\n },\n {\n \"line\": 5,\n \"segments\": []\n }\n ]\n }\n ],\n \"path_matches\": [\n {\n \"text\": \"src/\"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \".py\"\n }\n ],\n \"file\": {\n \"path\": \"src/foo.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n }\n }\n }\n ]\n}\n```\n\nNote that searches can match in the file's text (`content_matches`),\nthe path (`path_matches`), or both as in the example above.\n\nYou can use the same syntax for the search query as in the UI, e.g.\nto only search within a specific repository:\n\n```\ncurl 'https://api.bitbucket.org/2.0/teams/team_name/search/code?search_query=foo+repo:demo'\n# results from the \"demo\" repository\n```\n\nSimilar to other APIs, you can request more fields using a\n`fields` query parameter. E.g. to get some more information about\nthe repository of matched files (the `%2B` is a URL-encoded `+`):\n\n```\ncurl 'https://api.bitbucket.org/2.0/teams/team_name/search/code'\\\n '?search_query=foo&fields=%2Bvalues.file.commit.repository'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 1,\n \"content_matches\": [...],\n \"path_matches\": [...],\n \"file\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/commit/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo/commits/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n }\n },\n \"repository\": {\n \"name\": \"demo\",\n \"type\": \"repository\",\n \"full_name\": \"my-workspace/demo\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B850e1749-781a-4115-9316-df39d0600e7a%7D?ts=default\"\n }\n },\n \"uuid\": \"{850e1749-781a-4115-9316-df39d0600e7a}\"\n }\n },\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n },\n \"path\": \"src/foo.py\"\n }\n }\n ]\n}\n```\n\nTry `fields=%2Bvalues.*.*.*.*` to get an idea what's possible.\n", + "description": "Search for code in the repositories of the specified team.\n\nNote that searches can match in the file's text (`content_matches`),\nthe path (`path_matches`), or both.\n\nYou can use the same syntax for the search query as in the UI.\nE.g. to search for \"foo\" only within the repository \"demo\",\nuse the query parameter `search_query=foo+repo:demo`.\n\nSimilar to other APIs, you can request more fields using a\n`fields` query parameter. E.g. to get some more information about\nthe repository of matched files, use the query parameter\n`search_query=foo&fields=%2Bvalues.file.commit.repository`\n(the `%2B` is a URL-encoded `+`).\n\nTry `fields=%2Bvalues.*.*.*.*` to get an idea what's possible.\n", "operationId": "searchTeam", "parameters": [ { @@ -15664,6 +19048,80 @@ "application/json": { "schema": { "$ref": "#/components/schemas/search_result_page" + }, + "examples": { + "response": { + "value": { + "size": 1, + "page": 1, + "pagelen": 10, + "query_substituted": false, + "values": [ + { + "type": "code_search_result", + "content_match_count": 2, + "content_matches": [ + { + "lines": [ + { + "line": 2, + "segments": [] + }, + { + "line": 3, + "segments": [ + { + "text": "def " + }, + { + "text": "foo", + "match": true + }, + { + "text": "():" + } + ] + }, + { + "line": 4, + "segments": [ + { + "text": " print(\"snek\")" + } + ] + }, + { + "line": 5, + "segments": [] + } + ] + } + ], + "path_matches": [ + { + "text": "src/" + }, + { + "text": "foo", + "match": true + }, + { + "text": ".py" + } + ], + "file": { + "path": "src/foo.py", + "type": "commit_file", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py" + } + } + } + } + ] + } + } } } } @@ -15698,7 +19156,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/user": { @@ -15817,7 +19286,7 @@ "/user/permissions/repositories": { "get": { "tags": ["Repositories"], - "description": "Returns an object for each repository the caller has explicit access\nto and their effective permission — the highest level of permission the\ncaller has. This does not return public repositories that the user was\nnot granted any specific permission in, and does not distinguish between\nexplicit and implicit privileges.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/user/permissions/repositories\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"bitbucket/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\"\n }\n ],\n \"page\": 1,\n \"size\": 1\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nrepository or permission by adding the following query string\nparameters:\n\n* `q=repository.name=\"geordi\"` or `q=permission>\"read\"`\n* `sort=repository.name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", + "description": "Returns an object for each repository the caller has explicit access\nto and their effective permission — the highest level of permission the\ncaller has. This does not return public repositories that the user was\nnot granted any specific permission in, and does not distinguish between\nexplicit and implicit privileges.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nrepository or permission by adding the following query string\nparameters:\n\n* `q=repository.name=\"geordi\"` or `q=permission>\"read\"`\n* `sort=repository.name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", "summary": "List repository permissions for a user", "responses": { "200": { @@ -15826,6 +19295,33 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_repository_permissions" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "values": [ + { + "type": "repository_permission", + "user": { + "type": "user", + "nickname": "evzijst", + "display_name": "Erik van Zijst", + "uuid": "{d301aafa-d676-4ee0-88be-962be7417567}" + }, + "repository": { + "type": "repository", + "name": "geordi", + "full_name": "bitbucket/geordi", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "permission": "admin" + } + ], + "page": 1, + "size": 1 + } + } } } } @@ -15868,7 +19364,7 @@ "/user/permissions/workspaces": { "get": { "tags": ["Workspaces"], - "description": "Returns an object for each workspace the caller is a member of, and\ntheir effective role - the highest level of privilege the caller has.\nIf a user is a member of multiple groups with distinct roles, only the\nhighest level is returned.\n\nPermissions can be:\n\n* `owner`\n* `collaborator`\n* `member`\n\n**The `collaborator` role is being removed from the Bitbucket Cloud API. For more information,\nsee the [deprecation announcement](/cloud/bitbucket/deprecation-notice-collaborator-role/).**\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/user/permissions/workspaces\n\n{\n \"pagelen\": 10,\n \"page\": 1,\n \"size\": 1,\n \"values\": [\n {\n \"type\": \"workspace_membership\",\n \"permission\": \"owner\",\n \"last_accessed\": \"2019-03-07T12:35:02.900024+00:00\",\n \"added_on\": \"2018-10-11T17:42:02.961424+00:00\",\n \"user\": {\n \"type\": \"user\",\n \"uuid\": \"{470c176d-3574-44ea-bb41-89e8638bcca4}\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n },\n \"workspace\": {\n \"type\": \"workspace\",\n \"uuid\": \"{a15fb181-db1f-48f7-b41f-e1eff06929d6}\",\n \"slug\": \"bbworkspace1\",\n \"name\": \"Atlassian Bitbucket\",\n }\n }\n ]\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nworkspace or permission by adding the following query string parameters:\n\n* `q=workspace.slug=\"bbworkspace1\"` or `q=permission=\"owner\"`\n* `sort=workspace.slug`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", + "description": "Returns an object for each workspace the caller is a member of, and\ntheir effective role - the highest level of privilege the caller has.\nIf a user is a member of multiple groups with distinct roles, only the\nhighest level is returned.\n\nPermissions can be:\n\n* `owner`\n* `collaborator`\n* `member`\n\n**The `collaborator` role is being removed from the Bitbucket Cloud API. For more information,\nsee the [deprecation announcement](/cloud/bitbucket/deprecation-notice-collaborator-role/).**\n\n**When you move your administration from Bitbucket Cloud to admin.atlassian.com, the following fields on\n`workspace_membership` will no longer be present: `last_accessed` and `added_on`. See the\n[deprecation announcement](/cloud/bitbucket/announcement-breaking-change-workspace-membership/).**\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nworkspace or permission by adding the following query string parameters:\n\n* `q=workspace.slug=\"bbworkspace1\"` or `q=permission=\"owner\"`\n* `sort=workspace.slug`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", "summary": "List workspaces for the current user", "responses": { "200": { @@ -15877,6 +19373,35 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_workspace_memberships" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "page": 1, + "size": 1, + "values": [ + { + "type": "workspace_membership", + "permission": "owner", + "last_accessed": "2019-03-07T12:35:02.900024+00:00", + "added_on": "2018-10-11T17:42:02.961424+00:00", + "user": { + "type": "user", + "uuid": "{470c176d-3574-44ea-bb41-89e8638bcca4}", + "nickname": "evzijst", + "display_name": "Erik van Zijst" + }, + "workspace": { + "type": "workspace", + "uuid": "{a15fb181-db1f-48f7-b41f-e1eff06929d6}", + "slug": "bbworkspace1", + "name": "Atlassian Bitbucket" + } + } + ] + } + } } } } @@ -15969,7 +19494,7 @@ { "name": "selected_user", "in": "path", - "description": "This can either be the UUID of the account, surrounded by curly-braces, for\nexample: `{account UUID}`, OR an Atlassian Account ID.\n", + "description": "This can either be an Atlassian Account ID OR the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`.\n", "required": true, "schema": { "type": "string" @@ -15977,7 +19502,7 @@ } ] }, - "/users/{selected_user}/pipelines_config/variables/": { + "/users/{selected_user}/pipelines_config/variables": { "get": { "tags": ["Pipelines"], "deprecated": true, @@ -16006,7 +19531,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "post": { "tags": ["Pipelines"], @@ -16067,7 +19603,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/users/{selected_user}/pipelines_config/variables/{variable_uuid}": { @@ -16118,7 +19665,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "put": { "tags": ["Pipelines"], @@ -16170,7 +19728,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "tags": ["Pipelines"], @@ -16212,7 +19781,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/users/{selected_user}/properties/{app_key}/{property_name}": { @@ -16257,7 +19837,18 @@ "requestBody": { "$ref": "#/components/requestBodies/application_property" }, - "tags": ["properties"] + "tags": ["properties"], + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "responses": { @@ -16297,7 +19888,18 @@ } } ], - "tags": ["properties"] + "tags": ["properties"], + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "get": { "responses": { @@ -16344,14 +19946,25 @@ } } ], - "tags": ["properties"] + "tags": ["properties"], + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/users/{selected_user}/search/code": { "get": { "tags": ["Search"], "summary": "Search for code in a user's repositories", - "description": "Search for code in the repositories of the specified user.\n\nSearching across all repositories:\n\n```\ncurl 'https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/search/code?search_query=foo'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 2,\n \"content_matches\": [\n {\n \"lines\": [\n {\n \"line\": 2,\n \"segments\": []\n },\n {\n \"line\": 3,\n \"segments\": [\n {\n \"text\": \"def \"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \"():\"\n }\n ]\n },\n {\n \"line\": 4,\n \"segments\": [\n {\n \"text\": \" print(\\\"snek\\\")\"\n }\n ]\n },\n {\n \"line\": 5,\n \"segments\": []\n }\n ]\n }\n ],\n \"path_matches\": [\n {\n \"text\": \"src/\"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \".py\"\n }\n ],\n \"file\": {\n \"path\": \"src/foo.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n }\n }\n }\n ]\n}\n```\n\nNote that searches can match in the file's text (`content_matches`),\nthe path (`path_matches`), or both as in the example above.\n\nYou can use the same syntax for the search query as in the UI, e.g.\nto only search within a specific repository:\n\n```\ncurl 'https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/search/code?search_query=foo+repo:demo'\n# results from the \"demo\" repository\n```\n\nSimilar to other APIs, you can request more fields using a\n`fields` query parameter. E.g. to get some more information about\nthe repository of matched files (the `%2B` is a URL-encoded `+`):\n\n```\ncurl 'https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/search/code'\\\n '?search_query=foo&fields=%2Bvalues.file.commit.repository'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 1,\n \"content_matches\": [...],\n \"path_matches\": [...],\n \"file\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/commit/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo/commits/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n }\n },\n \"repository\": {\n \"name\": \"demo\",\n \"type\": \"repository\",\n \"full_name\": \"my-workspace/demo\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B850e1749-781a-4115-9316-df39d0600e7a%7D?ts=default\"\n }\n },\n \"uuid\": \"{850e1749-781a-4115-9316-df39d0600e7a}\"\n }\n },\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n },\n \"path\": \"src/foo.py\"\n }\n }\n ]\n}\n```\n\nTry `fields=%2Bvalues.*.*.*.*` to get an idea what's possible.\n", + "description": "Search for code in the repositories of the specified user.\n\nNote that searches can match in the file's text (`content_matches`),\nthe path (`path_matches`), or both.\n\nYou can use the same syntax for the search query as in the UI.\nE.g. to search for \"foo\" only within the repository \"demo\",\nuse the query parameter `search_query=foo+repo:demo`.\n\nSimilar to other APIs, you can request more fields using a\n`fields` query parameter. E.g. to get some more information about\nthe repository of matched files, use the query parameter\n`search_query=foo&fields=%2Bvalues.file.commit.repository`\n(the `%2B` is a URL-encoded `+`).\n", "operationId": "searchAccount", "parameters": [ { @@ -16402,6 +20015,80 @@ "application/json": { "schema": { "$ref": "#/components/schemas/search_result_page" + }, + "examples": { + "response": { + "value": { + "size": 1, + "page": 1, + "pagelen": 10, + "query_substituted": false, + "values": [ + { + "type": "code_search_result", + "content_match_count": 2, + "content_matches": [ + { + "lines": [ + { + "line": 2, + "segments": [] + }, + { + "line": 3, + "segments": [ + { + "text": "def " + }, + { + "text": "foo", + "match": true + }, + { + "text": "():" + } + ] + }, + { + "line": 4, + "segments": [ + { + "text": " print(\"snek\")" + } + ] + }, + { + "line": 5, + "segments": [] + } + ] + } + ], + "path_matches": [ + { + "text": "src/" + }, + { + "text": "foo", + "match": true + }, + { + "text": ".py" + } + ], + "file": { + "path": "src/foo.py", + "type": "commit_file", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py" + } + } + } + } + ] + } + } } } } @@ -16436,13 +20123,24 @@ } } } - } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/users/{selected_user}/ssh-keys": { "get": { "tags": ["Ssh"], - "description": "Returns a paginated list of the user's SSH public keys.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys\n{\n \"page\": 1,\n \"pagelen\": 10,\n \"size\": 1,\n \"values\": [\n {\n \"comment\": \"user@myhost\",\n \"created_on\": \"2018-03-14T13:17:05.196003+00:00\",\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY\",\n \"label\": \"\",\n \"last_used\": \"2018-03-20T13:18:05.196003+00:00\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a\"\n }\n },\n \"owner\": {\n \"display_name\": \"Mark Adams\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/markadams-atl/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/markadams-atl/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}\"\n }\n },\n \"type\": \"user\",\n \"username\": \"markadams-atl\",\n \"nickname\": \"markadams-atl\",\n \"uuid\": \"{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}\"\n },\n \"type\": \"ssh_key\",\n \"uuid\": \"{b15b6026-9c02-4626-b4ad-b905f99f763a}\"\n }\n ]\n}\n```", + "description": "Returns a paginated list of the user's SSH public keys.", "summary": "List SSH keys", "responses": { "200": { @@ -16451,6 +20149,49 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_ssh_user_keys" + }, + "examples": { + "response": { + "value": { + "page": 1, + "pagelen": 10, + "size": 1, + "values": [ + { + "comment": "user@myhost", + "created_on": "2018-03-14T13:17:05.196003+00:00", + "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY", + "label": "", + "last_used": "2018-03-20T13:18:05.196003+00:00", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a" + } + }, + "owner": { + "display_name": "Mark Adams", + "links": { + "avatar": { + "href": "https://bitbucket.org/account/markadams-atl/avatar/32/" + }, + "html": { + "href": "https://bitbucket.org/markadams-atl/" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}" + } + }, + "type": "user", + "username": "markadams-atl", + "nickname": "markadams-atl", + "uuid": "{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}" + }, + "type": "ssh_key", + "uuid": "{b15b6026-9c02-4626-b4ad-b905f99f763a}" + } + ] + } + } } } } @@ -16483,7 +20224,7 @@ }, "post": { "tags": ["Ssh"], - "description": "Adds a new SSH public key to the specified user account and returns the resulting key.\n\nExample:\n```\n$ curl -X POST -H \"Content-Type: application/json\" -d '{\"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY user@myhost\"}' https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys\n\n{\n \"comment\": \"user@myhost\",\n \"created_on\": \"2018-03-14T13:17:05.196003+00:00\",\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY\",\n \"label\": \"\",\n \"last_used\": \"2018-03-20T13:18:05.196003+00:00\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a\"\n }\n },\n \"owner\": {\n \"display_name\": \"Mark Adams\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/markadams-atl/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/markadams-atl/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}\"\n }\n },\n \"type\": \"user\",\n \"username\": \"markadams-atl\",\n \"nickname\": \"markadams-atl\",\n \"uuid\": \"{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}\"\n },\n \"type\": \"ssh_key\",\n \"uuid\": \"{b15b6026-9c02-4626-b4ad-b905f99f763a}\"\n}\n```", + "description": "Adds a new SSH public key to the specified user account and returns the resulting key.\n\nExample:\n\n```\n$ curl -X POST -H \"Content-Type: application/json\" -d '{\"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY user@myhost\"}' https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys\n```", "summary": "Add a new SSH key", "responses": { "201": { @@ -16492,6 +20233,42 @@ "application/json": { "schema": { "$ref": "#/components/schemas/ssh_account_key" + }, + "examples": { + "response": { + "value": { + "comment": "user@myhost", + "created_on": "2018-03-14T13:17:05.196003+00:00", + "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY", + "label": "", + "last_used": "2018-03-20T13:18:05.196003+00:00", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a" + } + }, + "owner": { + "display_name": "Mark Adams", + "links": { + "avatar": { + "href": "https://bitbucket.org/account/markadams-atl/avatar/32/" + }, + "html": { + "href": "https://bitbucket.org/markadams-atl/" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}" + } + }, + "type": "user", + "username": "markadams-atl", + "nickname": "markadams-atl", + "uuid": "{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}" + }, + "type": "ssh_key", + "uuid": "{b15b6026-9c02-4626-b4ad-b905f99f763a}" + } + } } } } @@ -16546,7 +20323,7 @@ { "name": "selected_user", "in": "path", - "description": "This can either be the UUID of the account, surrounded by curly-braces, for\nexample: `{account UUID}`, OR an Atlassian Account ID.\n", + "description": "This can either be an Atlassian Account ID OR the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`.\n", "required": true, "schema": { "type": "string" @@ -16557,7 +20334,7 @@ "/users/{selected_user}/ssh-keys/{key_id}": { "delete": { "tags": ["Ssh"], - "description": "Deletes a specific SSH public key from a user's account\n\nExample:\n```\n$ curl -X DELETE https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/{b15b6026-9c02-4626-b4ad-b905f99f763a}\n```", + "description": "Deletes a specific SSH public key from a user's account.", "summary": "Delete a SSH key", "responses": { "204": { @@ -16601,7 +20378,7 @@ }, "get": { "tags": ["Ssh"], - "description": "Returns a specific SSH public key belonging to a user.\n\nExample:\n```\n$ curl https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/{fbe4bbab-f6f7-4dde-956b-5c58323c54b3}\n\n{\n \"comment\": \"user@myhost\",\n \"created_on\": \"2018-03-14T13:17:05.196003+00:00\",\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY\",\n \"label\": \"\",\n \"last_used\": \"2018-03-20T13:18:05.196003+00:00\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a\"\n }\n },\n \"owner\": {\n \"display_name\": \"Mark Adams\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/markadams-atl/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/markadams-atl/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}\"\n }\n },\n \"type\": \"user\",\n \"username\": \"markadams-atl\",\n \"nickname\": \"markadams-atl\",\n \"uuid\": \"{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}\"\n },\n \"type\": \"ssh_key\",\n \"uuid\": \"{b15b6026-9c02-4626-b4ad-b905f99f763a}\"\n}\n```", + "description": "Returns a specific SSH public key belonging to a user.", "summary": "Get a SSH key", "responses": { "200": { @@ -16610,6 +20387,42 @@ "application/json": { "schema": { "$ref": "#/components/schemas/ssh_account_key" + }, + "examples": { + "response": { + "value": { + "comment": "user@myhost", + "created_on": "2018-03-14T13:17:05.196003+00:00", + "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY", + "label": "", + "last_used": "2018-03-20T13:18:05.196003+00:00", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a" + } + }, + "owner": { + "display_name": "Mark Adams", + "links": { + "avatar": { + "href": "https://bitbucket.org/account/markadams-atl/avatar/32/" + }, + "html": { + "href": "https://bitbucket.org/markadams-atl/" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}" + } + }, + "type": "user", + "username": "markadams-atl", + "nickname": "markadams-atl", + "uuid": "{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}" + }, + "type": "ssh_key", + "uuid": "{b15b6026-9c02-4626-b4ad-b905f99f763a}" + } + } } } } @@ -16642,7 +20455,7 @@ }, "put": { "tags": ["Ssh"], - "description": "Updates a specific SSH public key on a user's account\n\nNote: Only the 'comment' field can be updated using this API. To modify the key or comment values, you must delete and add the key again.\n\nExample:\n```\n$ curl -X PUT -H \"Content-Type: application/json\" -d '{\"label\": \"Work key\"}' https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/{b15b6026-9c02-4626-b4ad-b905f99f763a}\n\n{\n \"comment\": \"\",\n \"created_on\": \"2018-03-14T13:17:05.196003+00:00\",\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY\",\n \"label\": \"Work key\",\n \"last_used\": \"2018-03-20T13:18:05.196003+00:00\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a\"\n }\n },\n \"owner\": {\n \"display_name\": \"Mark Adams\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/markadams-atl/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/markadams-atl/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}\"\n }\n },\n \"type\": \"user\",\n \"username\": \"markadams-atl\",\n \"nickname\": \"markadams-atl\",\n \"uuid\": \"{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}\"\n },\n \"type\": \"ssh_key\",\n \"uuid\": \"{b15b6026-9c02-4626-b4ad-b905f99f763a}\"\n}\n```", + "description": "Updates a specific SSH public key on a user's account\n\nNote: Only the 'comment' field can be updated using this API. To modify the key or comment values, you must delete and add the key again.\n\nExample:\n\n```\n$ curl -X PUT -H \"Content-Type: application/json\" -d '{\"label\": \"Work key\"}' https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/{b15b6026-9c02-4626-b4ad-b905f99f763a}\n```", "summary": "Update a SSH key", "responses": { "200": { @@ -16651,6 +20464,42 @@ "application/json": { "schema": { "$ref": "#/components/schemas/ssh_account_key" + }, + "examples": { + "response": { + "value": { + "comment": "", + "created_on": "2018-03-14T13:17:05.196003+00:00", + "key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY", + "label": "Work key", + "last_used": "2018-03-20T13:18:05.196003+00:00", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a" + } + }, + "owner": { + "display_name": "Mark Adams", + "links": { + "avatar": { + "href": "https://bitbucket.org/account/markadams-atl/avatar/32/" + }, + "html": { + "href": "https://bitbucket.org/markadams-atl/" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}" + } + }, + "type": "user", + "username": "markadams-atl", + "nickname": "markadams-atl", + "uuid": "{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}" + }, + "type": "ssh_key", + "uuid": "{b15b6026-9c02-4626-b4ad-b905f99f763a}" + } + } } } } @@ -16714,7 +20563,7 @@ { "name": "selected_user", "in": "path", - "description": "This can either be the UUID of the account, surrounded by curly-braces, for\nexample: `{account UUID}`, OR an Atlassian Account ID.\n", + "description": "This can either be an Atlassian Account ID OR the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`.\n", "required": true, "schema": { "type": "string" @@ -16725,7 +20574,7 @@ "/workspaces": { "get": { "tags": ["Workspaces"], - "description": "Returns a list of workspaces accessible by the authenticated user.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/workspaces\n\n{\n \"pagelen\": 10,\n \"page\": 1,\n \"size\": 1,\n \"values\": [\n {\n \"uuid\": \"{a15fb181-db1f-48f7-b41f-e1eff06929d6}\",\n \"links\": {\n \"owners\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/bbworkspace1/members?q=permission%3D%22owner%22\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/bbworkspace1\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bbworkspace1\"\n },\n \"snippets\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/bbworkspace1\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/bbworkspace1/\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/workspaces/bbworkspace1/avatar/?ts=1543465801\"\n },\n \"members\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/bbworkspace1/members\"\n },\n \"projects\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/bbworkspace1/projects\"\n }\n },\n \"created_on\": \"2018-11-14T19:15:05.058566+00:00\",\n \"type\": \"workspace\",\n \"slug\": \"bbworkspace1\",\n \"is_private\": true,\n \"name\": \"Atlassian Bitbucket\"\n }\n ]\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nworkspace or permission by adding the following query string parameters:\n\n* `q=slug=\"bbworkspace1\"` or `q=is_private=true`\n* `sort=created_on`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.\n\n**The `collaborator` role is being removed from the Bitbucket Cloud API. For more information,\nsee the [deprecation announcement](/cloud/bitbucket/deprecation-notice-collaborator-role/).**", + "description": "Returns a list of workspaces accessible by the authenticated user.\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nworkspace or permission by adding the following query string parameters:\n\n* `q=slug=\"bbworkspace1\"` or `q=is_private=true`\n* `sort=created_on`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.\n\n**The `collaborator` role is being removed from the Bitbucket Cloud API. For more information,\nsee the [deprecation announcement](/cloud/bitbucket/deprecation-notice-collaborator-role/).**", "summary": "List workspaces for user", "responses": { "200": { @@ -16734,6 +20583,51 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_workspaces" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "page": 1, + "size": 1, + "values": [ + { + "uuid": "{a15fb181-db1f-48f7-b41f-e1eff06929d6}", + "links": { + "owners": { + "href": "https://api.bitbucket.org/2.0/workspaces/bbworkspace1/members?q=permission%3D%22owner%22" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/bbworkspace1" + }, + "repositories": { + "href": "https://api.bitbucket.org/2.0/repositories/bbworkspace1" + }, + "snippets": { + "href": "https://api.bitbucket.org/2.0/snippets/bbworkspace1" + }, + "html": { + "href": "https://bitbucket.org/bbworkspace1/" + }, + "avatar": { + "href": "https://bitbucket.org/workspaces/bbworkspace1/avatar/?ts=1543465801" + }, + "members": { + "href": "https://api.bitbucket.org/2.0/workspaces/bbworkspace1/members" + }, + "projects": { + "href": "https://api.bitbucket.org/2.0/workspaces/bbworkspace1/projects" + } + }, + "created_on": "2018-11-14T19:15:05.058566+00:00", + "type": "workspace", + "slug": "bbworkspace1", + "is_private": true, + "name": "Atlassian Bitbucket" + } + ] + } + } } } } @@ -16895,7 +20789,7 @@ }, "post": { "tags": ["Workspaces", "Webhooks"], - "description": "Creates a new webhook on the specified workspace.\n\nWorkspace webhooks are fired for events from all repositories contained\nby that workspace.\n\nExample:\n\n```\n$ curl -X POST -u credentials -H 'Content-Type: application/json'\n https://api.bitbucket.org/2.0/workspaces/my-workspace/hooks\n -d '\n {\n \"description\": \"Webhook Description\",\n \"url\": \"https://example.com/\",\n \"active\": true,\n \"events\": [\n \"repo:push\",\n \"issue:created\",\n \"issue:updated\"\n ]\n }'\n```\n\nThis call requires the webhook scope, as well as any scope\nthat applies to the events that the webhook subscribes to. In the\nexample above that means: `webhook`, `repository` and `issue`.\n\nThe `url` must properly resolve and cannot be an internal, non-routed address.\n\nOnly workspace owners can install webhooks on workspaces.", + "description": "Creates a new webhook on the specified workspace.\n\nWorkspace webhooks are fired for events from all repositories contained\nby that workspace.\n\nExample:\n\n```\n$ curl -X POST -u credentials -H 'Content-Type: application/json'\n https://api.bitbucket.org/2.0/workspaces/my-workspace/hooks\n -d '\n {\n \"description\": \"Webhook Description\",\n \"url\": \"https://example.com/\",\n \"active\": true,\n \"secret\": \"this is a really bad secret\",\n \"events\": [\n \"repo:push\",\n \"issue:created\",\n \"issue:updated\"\n ]\n }'\n```\n\nWhen the `secret` is provided it will be used as the key to generate a HMAC\ndigest value sent in the `X-Hub-Signature` header at delivery time. Passing\na `null` or empty `secret` or not passing a `secret` will leave the webhook's\nsecret unset. Bitbucket only generates the `X-Hub-Signature` when the webhook's\nsecret is set.\n\nThis call requires the webhook scope, as well as any scope\nthat applies to the events that the webhook subscribes to. In the\nexample above that means: `webhook`, `repository` and `issue`.\n\nThe `url` must properly resolve and cannot be an internal, non-routed address.\n\nOnly workspace owners can install webhooks on workspaces.", "summary": "Create a webhook for a workspace", "responses": { "201": { @@ -17043,7 +20937,7 @@ }, "put": { "tags": ["Workspaces", "Webhooks"], - "description": "Updates the specified webhook subscription.\n\nThe following properties can be mutated:\n\n* `description`\n* `url`\n* `active`\n* `events`", + "description": "Updates the specified webhook subscription.\n\nThe following properties can be mutated:\n\n* `description`\n* `url`\n* `secret`\n* `active`\n* `events`\n\nThe hook's secret is used as a key to generate the HMAC hex digest sent in the\n`X-Hub-Signature` header at delivery time. This signature is only generated\nwhen the hook has a secret.\n\nSet the hook's secret by passing the new value in the `secret` field. Passing a\n`null` value in the `secret` field will remove the secret from the hook. The\nhook's secret can be left unchanged by not passing the `secret` field in the\nrequest.", "summary": "Update a webhook for a workspace", "responses": { "200": { @@ -17147,6 +21041,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:workspace:bitbucket", "read:user:bitbucket"] + } ] }, "parameters": [ @@ -17208,6 +21109,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:workspace:bitbucket", "read:user:bitbucket"] + } ] }, "parameters": [ @@ -17234,7 +21142,7 @@ "/workspaces/{workspace}/permissions": { "get": { "tags": ["Workspaces"], - "description": "Returns the list of members in a workspace\nand their permission levels.\nPermission can be:\n* `owner`\n* `collaborator`\n* `member`\n\n**The `collaborator` role is being removed from the Bitbucket Cloud API. For more information,\nsee the [deprecation announcement](/cloud/bitbucket/deprecation-notice-collaborator-role/).**\n\nExample:\n\n```\n$ curl -X https://api.bitbucket.org/2.0/workspaces/bbworkspace1/permissions\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"permission\": \"owner\",\n \"type\": \"workspace_membership\",\n \"user\": {\n \"type\": \"user\",\n \"uuid\": \"{470c176d-3574-44ea-bb41-89e8638bcca4}\",\n \"display_name\": \"Erik van Zijst\",\n },\n \"workspace\": {\n \"type\": \"workspace\",\n \"uuid\": \"{a15fb181-db1f-48f7-b41f-e1eff06929d6}\",\n \"slug\": \"bbworkspace1\",\n \"name\": \"Atlassian Bitbucket\",\n }\n },\n {\n \"permission\": \"member\",\n \"type\": \"workspace_membership\",\n \"user\": {\n \"type\": \"user\",\n \"nickname\": \"seanaty\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n },\n \"workspace\": {\n \"type\": \"workspace\",\n \"uuid\": \"{a15fb181-db1f-48f7-b41f-e1eff06929d6}\",\n \"slug\": \"bbworkspace1\",\n \"name\": \"Atlassian Bitbucket\",\n }\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```\n\nResults may be further [filtered](/cloud/bitbucket/rest/intro/#filtering) by\npermission by adding the following query string parameters:\n\n* `q=permission=\"owner\"`", + "description": "Returns the list of members in a workspace\nand their permission levels.\nPermission can be:\n* `owner`\n* `collaborator`\n* `member`\n\n**The `collaborator` role is being removed from the Bitbucket Cloud API. For more information,\nsee the [deprecation announcement](/cloud/bitbucket/deprecation-notice-collaborator-role/).**\n\n**When you move your administration from Bitbucket Cloud to admin.atlassian.com, the following fields on\n`workspace_membership` will no longer be present: `last_accessed` and `added_on`. See the\n[deprecation announcement](/cloud/bitbucket/announcement-breaking-change-workspace-membership/).**\n\nResults may be further [filtered](/cloud/bitbucket/rest/intro/#filtering) by\npermission by adding the following query string parameters:\n\n* `q=permission=\"owner\"`", "summary": "List user permissions in a workspace", "responses": { "200": { @@ -17243,6 +21151,48 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_workspace_memberships" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "values": [ + { + "permission": "owner", + "type": "workspace_membership", + "user": { + "type": "user", + "uuid": "{470c176d-3574-44ea-bb41-89e8638bcca4}", + "display_name": "Erik van Zijst" + }, + "workspace": { + "type": "workspace", + "uuid": "{a15fb181-db1f-48f7-b41f-e1eff06929d6}", + "slug": "bbworkspace1", + "name": "Atlassian Bitbucket" + } + }, + { + "permission": "member", + "type": "workspace_membership", + "user": { + "type": "user", + "nickname": "seanaty", + "display_name": "Sean Conaty", + "uuid": "{504c3b62-8120-4f0c-a7bc-87800b9d6f70}" + }, + "workspace": { + "type": "workspace", + "uuid": "{a15fb181-db1f-48f7-b41f-e1eff06929d6}", + "slug": "bbworkspace1", + "name": "Atlassian Bitbucket" + } + } + ], + "page": 1, + "size": 2 + } + } } } } @@ -17279,6 +21229,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:workspace:bitbucket", "read:user:bitbucket"] + } ] }, "parameters": [ @@ -17296,7 +21253,7 @@ "/workspaces/{workspace}/permissions/repositories": { "get": { "tags": ["Workspaces"], - "description": "Returns an object for each repository permission for all of a\nworkspace's repositories.\n\nPermissions returned are effective permissions: the highest level of\npermission the user has. This does not distinguish between direct and\nindirect (group) privileges.\n\nOnly users with admin permission for the team may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/permissions/repositories\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\"\n },\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\"\n },\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Jeff Zeng\",\n \"uuid\": \"{47f92a9a-c3a3-4d0b-bc4e-782a969c5c72}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"whee\",\n \"full_name\": \"atlassian_tutorial/whee\",\n \"uuid\": \"{30ba25e9-51ff-4555-8dd0-fc7ee2fa0895}\"\n },\n \"permission\": \"admin\"\n }\n ],\n \"page\": 1,\n \"size\": 3\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering)\nby repository, user, or permission by adding the following query string\nparameters:\n\n* `q=repository.name=\"geordi\"` or `q=permission>\"read\"`\n* `sort=user.display_name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", + "description": "Returns an object for each repository permission for all of a\nworkspace's repositories.\n\nPermissions returned are effective permissions: the highest level of\npermission the user has. This does not distinguish between direct and\nindirect (group) privileges.\n\nOnly users with admin permission for the team may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering)\nby repository, user, or permission by adding the following query string\nparameters:\n\n* `q=repository.name=\"geordi\"` or `q=permission>\"read\"`\n* `sort=user.display_name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", "summary": "List all repository permissions for a workspace", "responses": { "200": { @@ -17305,6 +21262,62 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_repository_permissions" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "values": [ + { + "type": "repository_permission", + "user": { + "type": "user", + "display_name": "Erik van Zijst", + "uuid": "{d301aafa-d676-4ee0-88be-962be7417567}" + }, + "repository": { + "type": "repository", + "name": "geordi", + "full_name": "atlassian_tutorial/geordi", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "permission": "admin" + }, + { + "type": "repository_permission", + "user": { + "type": "user", + "display_name": "Sean Conaty", + "uuid": "{504c3b62-8120-4f0c-a7bc-87800b9d6f70}" + }, + "repository": { + "type": "repository", + "name": "geordi", + "full_name": "atlassian_tutorial/geordi", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "permission": "write" + }, + { + "type": "repository_permission", + "user": { + "type": "user", + "display_name": "Jeff Zeng", + "uuid": "{47f92a9a-c3a3-4d0b-bc4e-782a969c5c72}" + }, + "repository": { + "type": "repository", + "name": "whee", + "full_name": "atlassian_tutorial/whee", + "uuid": "{30ba25e9-51ff-4555-8dd0-fc7ee2fa0895}" + }, + "permission": "admin" + } + ], + "page": 1, + "size": 3 + } + } } } } @@ -17350,6 +21363,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket", "read:user:bitbucket"] + } ] }, "parameters": [ @@ -17367,7 +21387,7 @@ "/workspaces/{workspace}/permissions/repositories/{repo_slug}": { "get": { "tags": ["Workspaces"], - "description": "Returns an object for the repository permission of each user in the\nrequested repository.\n\nPermissions returned are effective permissions: the highest level of\npermission the user has. This does not distinguish between direct and\nindirect (group) privileges.\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/permissions/repositories/geordi\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\"\n },\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\"\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering)\nby user, or permission by adding the following query string parameters:\n\n* `q=permission>\"read\"`\n* `sort=user.display_name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", + "description": "Returns an object for the repository permission of each user in the\nrequested repository.\n\nPermissions returned are effective permissions: the highest level of\npermission the user has. This does not distinguish between direct and\nindirect (group) privileges.\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering)\nby user, or permission by adding the following query string parameters:\n\n* `q=permission>\"read\"`\n* `sort=user.display_name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", "summary": "List a repository permissions for a workspace", "responses": { "200": { @@ -17376,6 +21396,47 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_repository_permissions" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "values": [ + { + "type": "repository_permission", + "user": { + "type": "user", + "display_name": "Erik van Zijst", + "uuid": "{d301aafa-d676-4ee0-88be-962be7417567}" + }, + "repository": { + "type": "repository", + "name": "geordi", + "full_name": "atlassian_tutorial/geordi", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "permission": "admin" + }, + { + "type": "repository_permission", + "user": { + "type": "user", + "display_name": "Sean Conaty", + "uuid": "{504c3b62-8120-4f0c-a7bc-87800b9d6f70}" + }, + "repository": { + "type": "repository", + "name": "geordi", + "full_name": "atlassian_tutorial/geordi", + "uuid": "{85d08b4e-571d-44e9-a507-fa476535aa98}" + }, + "permission": "write" + } + ], + "page": 1, + "size": 2 + } + } } } } @@ -17421,6 +21482,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket", "read:user:bitbucket"] + } ] }, "parameters": [ @@ -17475,7 +21543,18 @@ } } } - } + }, + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/workspaces/{workspace}/pipelines-config/identity/oidc/keys.json": { @@ -17509,7 +21588,18 @@ } } } - } + }, + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/workspaces/{workspace}/pipelines-config/variables": { @@ -17540,7 +21630,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "post": { "tags": ["Pipelines"], @@ -17600,7 +21708,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/workspaces/{workspace}/pipelines-config/variables/{variable_uuid}": { @@ -17650,7 +21769,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "put": { "tags": ["Pipelines"], @@ -17701,7 +21838,18 @@ } } } - } + }, + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] }, "delete": { "tags": ["Pipelines"], @@ -17742,7 +21890,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:pipeline:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["pipeline:variable"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } }, "/workspaces/{workspace}/projects": { @@ -17782,11 +21948,18 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:project:bitbucket"] + } ] }, "post": { "tags": ["Projects"], - "description": "Creates a new project.\n\nNote that the avatar has to be embedded as either a data-url\nor a URL to an external image as shown in the examples below:\n\n```\n$ body=$(cat << EOF\n{\n \"name\": \"Mars Project\",\n \"key\": \"MARS\",\n \"description\": \"Software for colonizing mars.\",\n \"links\": {\n \"avatar\": {\n \"href\": \"data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/...\"\n }\n },\n \"is_private\": false\n}\nEOF\n)\n$ curl -H \"Content-Type: application/json\" \\\n -X POST \\\n -d \"$body\" \\\n https://api.bitbucket.org/2.0/teams/teams-in-space/projects/ | jq .\n{\n // Serialized project document\n}\n```\n\nor even:\n\n```\n$ body=$(cat << EOF\n{\n \"name\": \"Mars Project\",\n \"key\": \"MARS\",\n \"description\": \"Software for colonizing mars.\",\n \"links\": {\n \"avatar\": {\n \"href\": \"http://i.imgur.com/72tRx4w.gif\"\n }\n },\n \"is_private\": false\n}\nEOF\n)\n$ curl -H \"Content-Type: application/json\" \\\n -X POST \\\n -d \"$body\" \\\n https://api.bitbucket.org/2.0/teams/teams-in-space/projects/ | jq .\n{\n // Serialized project document\n}\n```", + "description": "Creates a new project.\n\nNote that the avatar has to be embedded as either a data-url\nor a URL to an external image as shown in the examples below:\n\n```\n$ body=$(cat << EOF\n{\n \"name\": \"Mars Project\",\n \"key\": \"MARS\",\n \"description\": \"Software for colonizing mars.\",\n \"links\": {\n \"avatar\": {\n \"href\": \"data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/...\"\n }\n },\n \"is_private\": false\n}\nEOF\n)\n$ curl -H \"Content-Type: application/json\" \\\n -X POST \\\n -d \"$body\" \\\n https://api.bitbucket.org/2.0/workspaces/teams-in-space/projects/ | jq .\n{\n // Serialized project document\n}\n```\n\nor even:\n\n```\n$ body=$(cat << EOF\n{\n \"name\": \"Mars Project\",\n \"key\": \"MARS\",\n \"description\": \"Software for colonizing mars.\",\n \"links\": {\n \"avatar\": {\n \"href\": \"http://i.imgur.com/72tRx4w.gif\"\n }\n },\n \"is_private\": false\n}\nEOF\n)\n$ curl -H \"Content-Type: application/json\" \\\n -X POST \\\n -d \"$body\" \\\n https://api.bitbucket.org/2.0/workspaces/teams-in-space/projects/ | jq .\n{\n // Serialized project document\n}\n```", "summary": "Create a project in a workspace", "responses": { "201": { @@ -17841,6 +22014,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:project:bitbucket"] + } ] }, "parameters": [ @@ -17858,7 +22038,7 @@ "/workspaces/{workspace}/projects/{project_key}": { "delete": { "tags": ["Projects"], - "description": "Deletes this project. This is an irreversible operation.\n\nYou cannot delete a project that still contains repositories.\nTo delete the project, [delete](/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-repo-slug-delete)\nor transfer the repositories first.\n\nExample:\n```\n$ curl -X DELETE https://api.bitbucket.org/2.0/bbworkspace1/PROJ\n```", + "description": "Deletes this project. This is an irreversible operation.\n\nYou cannot delete a project that still contains repositories.\nTo delete the project, [delete](/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-repo-slug-delete)\nor transfer the repositories first.\n\nExample:\n```\n$ curl -X DELETE https://api.bitbucket.org/2.0/workspaces/bbworkspace1/projects/PROJ\n```", "summary": "Delete a project for a workspace", "responses": { "204": { @@ -17895,6 +22075,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:project:bitbucket"] + } ] }, "get": { @@ -17953,6 +22140,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:project:bitbucket"] + } ] }, "put": { @@ -18030,6 +22224,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:project:bitbucket"] + } ] }, "parameters": [ @@ -18056,7 +22257,7 @@ "/workspaces/{workspace}/projects/{project_key}/branching-model": { "get": { "tags": ["Branching model"], - "description": "Return the branching model set at the project level. This view is\nread-only. The branching model settings can be changed using the\n[settings](#api-workspaces-workspace-projects-project-key-branching-model-settings-get)\nAPI.\n\nThe returned object:\n\n1. Always has a `development` property. `development.name` is\n the user-specified branch that can be inherited by an individual repository's\n branching model.\n2. Might have a `production` property. `production` will not\n be present when `production` is disabled.\n `production.name` is the user-specified branch that can be\n inherited by an individual repository's branching model.\n3. Always has a `branch_types` array which contains all enabled branch\n types.\n\nExample body:\n\n```\n{\n \"development\": {\n \"name\": \"master\",\n \"use_mainbranch\": true\n },\n \"production\": {\n \"name\": \"production\",\n \"use_mainbranch\": false\n },\n \"branch_types\": [\n {\n \"kind\": \"release\",\n \"prefix\": \"release/\"\n },\n {\n \"kind\": \"hotfix\",\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"feature\",\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"bugfix\",\n \"prefix\": \"bugfix/\"\n }\n ],\n \"type\": \"project_branching_model\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/.../branching-model\"\n }\n }\n}\n```", + "description": "Return the branching model set at the project level. This view is\nread-only. The branching model settings can be changed using the\n[settings](#api-workspaces-workspace-projects-project-key-branching-model-settings-get)\nAPI.\n\nThe returned object:\n\n1. Always has a `development` property. `development.name` is\n the user-specified branch that can be inherited by an individual repository's\n branching model.\n2. Might have a `production` property. `production` will not\n be present when `production` is disabled.\n `production.name` is the user-specified branch that can be\n inherited by an individual repository's branching model.\n3. Always has a `branch_types` array which contains all enabled branch\n types.", "summary": "Get the branching model for a project", "responses": { "200": { @@ -18065,6 +22266,44 @@ "application/json": { "schema": { "$ref": "#/components/schemas/project_branching_model" + }, + "examples": { + "response": { + "value": { + "development": { + "name": "master", + "use_mainbranch": true + }, + "production": { + "name": "production", + "use_mainbranch": false + }, + "branch_types": [ + { + "kind": "release", + "prefix": "release/" + }, + { + "kind": "hotfix", + "prefix": "hotfix/" + }, + { + "kind": "feature", + "prefix": "feature/" + }, + { + "kind": "bugfix", + "prefix": "bugfix/" + } + ], + "type": "project_branching_model", + "links": { + "self": { + "href": "https://api.bitbucket.org/.../branching-model" + } + } + } + } } } } @@ -18110,6 +22349,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } ] }, "parameters": [ @@ -18136,7 +22382,7 @@ "/workspaces/{workspace}/projects/{project_key}/branching-model/settings": { "get": { "tags": ["Branching model"], - "description": "Return the branching model configuration for a project. The returned\nobject:\n\n1. Always has a `development` property for the development branch.\n2. Always a `production` property for the production branch. The\n production branch can be disabled.\n3. The `branch_types` contains all the branch types.\n\n\nThis is the raw configuration for the branching model. A client\nwishing to see the branching model with its actual current branches may find the\n[active model API](#api-workspaces-workspace-projects-project-key-branching-model-get)\nmore useful.\n\nExample body:\n\n```\n{\n \"development\": {\n \"name\": null,\n \"use_mainbranch\": true\n },\n \"production\": {\n \"name\": \"production\",\n \"use_mainbranch\": false,\n \"enabled\": false\n },\n \"branch_types\": [\n {\n \"kind\": \"release\",\n \"enabled\": true,\n \"prefix\": \"release/\"\n },\n {\n \"kind\": \"hotfix\",\n \"enabled\": true,\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"feature\",\n \"enabled\": true,\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"bugfix\",\n \"enabled\": false,\n \"prefix\": \"bugfix/\"\n }\n ],\n \"type\": \"branching_model_settings\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/.../branching-model/settings\"\n }\n }\n}\n```", + "description": "Return the branching model configuration for a project. The returned\nobject:\n\n1. Always has a `development` property for the development branch.\n2. Always a `production` property for the production branch. The\n production branch can be disabled.\n3. The `branch_types` contains all the branch types.\n\n\nThis is the raw configuration for the branching model. A client\nwishing to see the branching model with its actual current branches may find the\n[active model API](#api-workspaces-workspace-projects-project-key-branching-model-get)\nmore useful.", "summary": "Get the branching model config for a project", "responses": { "200": { @@ -18145,6 +22391,49 @@ "application/json": { "schema": { "$ref": "#/components/schemas/branching_model_settings" + }, + "examples": { + "response": { + "value": { + "development": { + "name": "null", + "use_mainbranch": true + }, + "production": { + "name": "production", + "use_mainbranch": false, + "enabled": false + }, + "branch_types": [ + { + "kind": "release", + "enabled": true, + "prefix": "release/" + }, + { + "kind": "hotfix", + "enabled": true, + "prefix": "hotfix/" + }, + { + "kind": "feature", + "enabled": true, + "prefix": "feature/" + }, + { + "kind": "bugfix", + "enabled": false, + "prefix": "bugfix/" + } + ], + "type": "branching_model_settings", + "links": { + "self": { + "href": "https://api.bitbucket.org/.../branching-model/settings" + } + } + } + } } } } @@ -18190,11 +22479,18 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:project:bitbucket"] + } ] }, "put": { "tags": ["Branching model"], - "description": "Update the branching model configuration for a project.\n\nThe `development` branch can be configured to a specific branch or to\ntrack the main branch. Any branch name can be supplied, but will only\nsuccessfully be applied to a repository via inheritance if that branch\nexists for that repository. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`development` property will leave the development branch unchanged.\n\nThe `production` branch can be a specific branch, the main\nbranch or disabled. Any branch name can be supplied, but will only\nsuccessfully be applied to a repository via inheritance if that branch\nexists for that repository. The `enabled` property can be used to enable (`true`)\nor disable (`false`) it. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`production` property will leave the production branch unchanged.\n\nThe `branch_types` property contains the branch types to be updated.\nOnly the branch types passed will be updated. All updates will be\nrejected if it would leave the branching model in an invalid state.\nFor branch types this means that:\n\n1. The prefixes for all enabled branch types are valid. For example,\n it is not possible to use '*' inside a Git prefix.\n2. A prefix of an enabled branch type must not be a prefix of another\n enabled branch type. This is to ensure that a branch can be easily\n classified by its prefix unambiguously.\n\nIt is possible to store an invalid prefix if that branch type would be\nleft disabled. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. Each branch type must\nhave a `kind` property to identify it.\n\nExample Body:\n\n```\n {\n \"development\": {\n \"use_mainbranch\": true\n },\n \"production\": {\n \"enabled\": true,\n \"use_mainbranch\": false,\n \"name\": \"production\"\n },\n \"branch_types\": [\n {\n \"kind\": \"bugfix\",\n \"enabled\": true,\n \"prefix\": \"bugfix/\"\n },\n {\n \"kind\": \"feature\",\n \"enabled\": true,\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"hotfix\",\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"release\",\n \"enabled\": false,\n }\n ]\n }\n```", + "description": "Update the branching model configuration for a project.\n\nThe `development` branch can be configured to a specific branch or to\ntrack the main branch. Any branch name can be supplied, but will only\nsuccessfully be applied to a repository via inheritance if that branch\nexists for that repository. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`development` property will leave the development branch unchanged.\n\nThe `production` branch can be a specific branch, the main\nbranch or disabled. Any branch name can be supplied, but will only\nsuccessfully be applied to a repository via inheritance if that branch\nexists for that repository. The `enabled` property can be used to enable (`true`)\nor disable (`false`) it. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`production` property will leave the production branch unchanged.\n\nThe `branch_types` property contains the branch types to be updated.\nOnly the branch types passed will be updated. All updates will be\nrejected if it would leave the branching model in an invalid state.\nFor branch types this means that:\n\n1. The prefixes for all enabled branch types are valid. For example,\n it is not possible to use '*' inside a Git prefix.\n2. A prefix of an enabled branch type must not be a prefix of another\n enabled branch type. This is to ensure that a branch can be easily\n classified by its prefix unambiguously.\n\nIt is possible to store an invalid prefix if that branch type would be\nleft disabled. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. Each branch type must\nhave a `kind` property to identify it.", "summary": "Update the branching model config for a project", "responses": { "200": { @@ -18203,6 +22499,40 @@ "application/json": { "schema": { "$ref": "#/components/schemas/branching_model_settings" + }, + "examples": { + "response": { + "value": { + "development": { + "use_mainbranch": true + }, + "production": { + "enabled": true, + "use_mainbranch": false, + "name": "production" + }, + "branch_types": [ + { + "kind": "bugfix", + "enabled": true, + "prefix": "bugfix/" + }, + { + "kind": "feature", + "enabled": true, + "prefix": "feature/" + }, + { + "kind": "hotfix", + "prefix": "hotfix/" + }, + { + "kind": "release", + "enabled": false + } + ] + } + } } } } @@ -18258,6 +22588,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:project:bitbucket"] + } ] }, "parameters": [ @@ -18284,7 +22621,7 @@ "/workspaces/{workspace}/projects/{project_key}/default-reviewers": { "get": { "tags": ["Projects"], - "description": "Return a list of all default reviewers for a project. This is a list of users that will be added as default\nreviewers to pull requests for any repository within the project.\n\nExample:\n```\n$ curl https://api.bitbucket.org/2.0/.../projects/.../default-reviewers | jq .\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"user\": {\n \"display_name\": \"Davis Lee\",\n \"uuid\": \"{f0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6}\"\n },\n \"reviewer_type\": \"project\",\n \"type\": \"default_reviewer\"\n },\n {\n \"user\": {\n \"display_name\": \"Jorge Rodriguez\",\n \"uuid\": \"{1aa43376-260d-4a0b-9660-f62672b9655d}\"\n },\n \"reviewer_type\": \"project\",\n \"type\": \"default_reviewer\"\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```", + "description": "Return a list of all default reviewers for a project. This is a list of users that will be added as default\nreviewers to pull requests for any repository within the project.", "summary": "List the default reviewers in a project", "responses": { "200": { @@ -18293,6 +22630,33 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_default_reviewer_and_type" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "values": [ + { + "user": { + "display_name": "Davis Lee", + "uuid": "{f0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6}" + }, + "reviewer_type": "project", + "type": "default_reviewer" + }, + { + "user": { + "display_name": "Jorge Rodriguez", + "uuid": "{1aa43376-260d-4a0b-9660-f62672b9655d}" + }, + "reviewer_type": "project", + "type": "default_reviewer" + } + ], + "page": 1, + "size": 2 + } + } } } } @@ -18328,6 +22692,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "parameters": [ @@ -18358,7 +22729,7 @@ "summary": "Remove the specific user from the project's default reviewers", "responses": { "204": { - "description": "The specified user was removed from the list of project default reviewers" + "description": "The specified user was removed from the list of project default reviewers" }, "400": { "description": "If the specified user is not a default reviewer for the project", @@ -18401,11 +22772,18 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:project:bitbucket"] + } ] }, "get": { "tags": ["Projects"], - "description": "Returns the specified default reviewer.\n\nExample:\n```\n$ curl https://api.bitbucket.org/2.0/.../default-reviewers/%7Bf0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6%7D\n{\n \"display_name\": \"Davis Lee\",\n \"type\": \"user\",\n \"uuid\": \"{f0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6}\"\n}\n```", + "description": "Returns the specified default reviewer.", "summary": "Get a default reviewer", "responses": { "200": { @@ -18414,6 +22792,15 @@ "application/json": { "schema": { "$ref": "#/components/schemas/user" + }, + "examples": { + "response": { + "value": { + "display_name": "Davis Lee", + "type": "user", + "uuid": "{f0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6}" + } + } } } } @@ -18459,19 +22846,48 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:pullrequest:bitbucket"] + } ] }, "put": { "tags": ["Projects"], - "description": "Adds the specified user to the project's list of default reviewers. The method is\nidempotent. Accepts an optional body containing the `uuid` of the user to be added.\n\nExample:\n```\n$ curl -XPUT https://api.bitbucket.org/2.0/.../default-reviewers/%7Bf0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6%7D\n-d { 'uuid': '{f0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6}' }\n\nHTTP/1.1 204\n```", + "description": "Adds the specified user to the project's list of default reviewers. The method is\nidempotent. Accepts an optional body containing the `uuid` of the user to be added.", "summary": "Add the specific user as a default reviewer for the project", "responses": { - "204": { + "200": { "description": "The specified user was added as a project default reviewer", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/user" + }, + "examples": { + "response": { + "value": { + "display_name": "Yaniv Sagy", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7Bdd5db7f2-6389-458d-a62a-716773910b7a%7D" + }, + "avatar": { + "href": "https://secure.gravatar.com/avatar/YS-2.png" + }, + "html": { + "href": "https://api.bitbucket.org/%7Bdd5db7f2-6389-458d-a62a-716773910b7a%7D/" + } + }, + "type": "user", + "uuid": "{dd5db7f2-6389-458d-a62a-716773910b7a}", + "account_id": "712020:4efe52fa-b4b4-475b-9eb0-c0a23b7eb194", + "nickname": "Yaniv Sagy" + } + } } } } @@ -18517,6 +22933,13 @@ { "api_key": [] } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["admin:project:bitbucket"] + } ] }, "parameters": [ @@ -18552,7 +22975,7 @@ "/workspaces/{workspace}/projects/{project_key}/deploy-keys": { "get": { "tags": ["Deployments"], - "description": "Returns all deploy keys belonging to a project.\n\nExample:\n```\n$ curl -H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT/deploy-keys\n\nOutput:\n{\n \"pagelen\":10,\n \"values\":[\n {\n \"comment\":\"thakseth@C02W454JHTD8\",\n \"last_used\":null,\n \"links\":{\n \"self\":{\n \"href\":\"https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT/deploy-keys/1234\"\n }\n },\n \"label\":\"test\",\n \"project\":{\n \"links\":{\n \"self\":{\n \"href\":\"https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT\"\n }\n },\n \"type\":\"project\",\n \"name\":\"cooperative standard\",\n \"key\":\"TEST_PROJECT\",\n \"uuid\":\"{3b3e510b-7f2b-414d-a2b7-76c4e405c1c0}\"\n },\n \"created_on\":\"2021-07-28T21:20:19.491721+00:00\",\n \"key\":\"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDX5yfMOEw6HG9jKTYTisbmDTJ4MCUTSVGr5e4OWvY3UuI2A6F8SdzQqa2f5BABA/4g5Sk5awJrYHlNu3EzV1V2I44tR3A4fnZAG71ZKyDPi1wvdO7UYmFgxV/Vd18H9QZFFjICGDM7W0PT2mI0kON/jN3qNWi+GiB/xgaeQKSqynysdysDp8lnnI/8Sh3ikURP9UP83ShRCpAXszOUNaa+UUlcYQYBDLIGowsg51c4PCkC3DNhAMxppkNRKoSOWwyl+oRVXHSDylkiJSBHW3HH4Q6WHieD54kGrjbhWBKdnnxKX7QAAZBDseY+t01N36m6/ljvXSUEcBWtHxBYye0r\",\n \"type\":\"project_deploy_key\",\n \"id\":1234\n }\n ],\n \"page\":1,\n \"size\":1\n}\n```", + "description": "Returns all deploy keys belonging to a project.", "summary": "List project deploy keys", "responses": { "200": { @@ -18561,6 +22984,42 @@ "application/json": { "schema": { "$ref": "#/components/schemas/paginated_project_deploy_keys" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "values": [ + { + "comment": "thakseth@C02W454JHTD8", + "last_used": null, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT/deploy-keys/1234" + } + }, + "label": "test", + "project": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT" + } + }, + "type": "project", + "name": "cooperative standard", + "key": "TEST_PROJECT", + "uuid": "{3b3e510b-7f2b-414d-a2b7-76c4e405c1c0}" + }, + "created_on": "2021-07-28T21:20:19.491721+00:00", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDX5yfMOEw6HG9jKTYTisbmDTJ4MCUTSVGr5e4OWvY3UuI2A6F8SdzQqa2f5BABA/4g5Sk5awJrYHlNu3EzV1V2I44tR3A4fnZAG71ZKyDPi1wvdO7UYmFgxV/Vd18H9QZFFjICGDM7W0PT2mI0kON/jN3qNWi+GiB/xgaeQKSqynysdysDp8lnnI/8Sh3ikURP9UP83ShRCpAXszOUNaa+UUlcYQYBDLIGowsg51c4PCkC3DNhAMxppkNRKoSOWwyl+oRVXHSDylkiJSBHW3HH4Q6WHieD54kGrjbhWBKdnnxKX7QAAZBDseY+t01N36m6/ljvXSUEcBWtHxBYye0r", + "type": "project_deploy_key", + "id": 1234 + } + ], + "page": 1, + "size": 1 + } + } } } } @@ -18600,7 +23059,7 @@ }, "post": { "tags": ["Deployments"], - "description": "Create a new deploy key in a project.\n\nExample:\n```\n$ curl -XPOST \\\n-H \"Authorization \" \\\n-H \"Content-type: application/json\" \\\nhttps://api.bitbucket.org/2.0/workspaces/jzeng/projects/JZ/deploy-keys/ -d \\\n'{\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5 mleu@C02W454JHTD8\",\n \"label\": \"mydeploykey\"\n}'\n\nOutput:\n{\n \"comment\": \"mleu@C02W454JHTD8\",\n \"last_used\": null,\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/testadfsa/projects/ASDF/deploy-keys/5/\"\n }\n },\n \"label\": \"myprojectkey\",\n \"project\": {\n ...\n },\n \"created_on\": \"2021-08-10T05:28:00.570859+00:00\",\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"type\": \"project_deploy_key\",\n \"id\": 5\n}\n```", + "description": "Create a new deploy key in a project.\n\nExample:\n```\n$ curl -X POST \\\n-H \"Authorization \" \\\n-H \"Content-type: application/json\" \\\nhttps://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT/deploy-keys/ -d \\\n'{\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5 mleu@C02W454JHTD8\",\n \"label\": \"mydeploykey\"\n}'\n```", "summary": "Create a project deploy key", "responses": { "200": { @@ -18609,6 +23068,35 @@ "application/json": { "schema": { "$ref": "#/components/schemas/project_deploy_key" + }, + "examples": { + "response": { + "value": { + "comment": "mleu@C02W454JHTD8", + "last_used": null, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT/deploy-keys/5/" + } + }, + "label": "myprojectkey", + "project": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT" + } + }, + "type": "project", + "name": "cooperative standard", + "key": "TEST_PROJECT", + "uuid": "{3b3e510b-7f2b-414d-a2b7-76c4e405c1c0}" + }, + "created_on": "2021-08-10T05:28:00.570859+00:00", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5", + "type": "project_deploy_key", + "id": 5 + } + } } } } @@ -18680,7 +23168,7 @@ "/workspaces/{workspace}/projects/{project_key}/deploy-keys/{key_id}": { "delete": { "tags": ["Deployments"], - "description": "This deletes a deploy key from a project.\n\nExample:\n```\n$ curl -XDELETE \\\n-H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/workspaces/jzeng/projects/JZ/deploy-keys/1234\n```", + "description": "This deletes a deploy key from a project.", "summary": "Delete a deploy key from a project", "responses": { "204": { @@ -18721,7 +23209,7 @@ }, "get": { "tags": ["Deployments"], - "description": "Returns the deploy key belonging to a specific key ID.\n\nExample:\n```\n$ curl -H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT/deploy-keys/1234\n\nOutput:\n{\n \"pagelen\":10,\n \"values\":[\n {\n \"comment\":\"thakseth@C02W454JHTD8\",\n \"last_used\":null,\n \"links\":{\n \"self\":{\n \"href\":\"https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT/deploy-keys/1234\"\n }\n },\n \"label\":\"test\",\n \"project\":{\n \"links\":{\n \"self\":{\n \"href\":\"https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT\"\n }\n },\n \"type\":\"project\",\n \"name\":\"cooperative standard\",\n \"key\":\"TEST_PROJECT\",\n \"uuid\":\"{3b3e510b-7f2b-414d-a2b7-76c4e405c1c0}\"\n },\n \"created_on\":\"2021-07-28T21:20:19.491721+00:00\",\n \"key\":\"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDX5yfMOEw6HG9jKTYTisbmDTJ4MCUTSVGr5e4OWvY3UuI2A6F8SdzQqa2f5BABA/4g5Sk5awJrYHlNu3EzV1V2I44tR3A4fnZAG71ZKyDPi1wvdO7UYmFgxV/Vd18H9QZFFjICGDM7W0PT2mI0kON/jN3qNWi+GiB/xgaeQKSqynysdysDp8lnnI/8Sh3ikURP9UP83ShRCpAXszOUNaa+UUlcYQYBDLIGowsg51c4PCkC3DNhAMxppkNRKoSOWwyl+oRVXHSDylkiJSBHW3HH4Q6WHieD54kGrjbhWBKdnnxKX7QAAZBDseY+t01N36m6/ljvXSUEcBWtHxBYye0r\",\n \"type\":\"project_deploy_key\",\n \"id\":1234\n }\n ],\n}\n```", + "description": "Returns the deploy key belonging to a specific key ID.", "summary": "Get a project deploy key", "responses": { "200": { @@ -18730,6 +23218,40 @@ "application/json": { "schema": { "$ref": "#/components/schemas/project_deploy_key" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "values": [ + { + "comment": "thakseth@C02W454JHTD8", + "last_used": null, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT/deploy-keys/1234" + } + }, + "label": "test", + "project": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT" + } + }, + "type": "project", + "name": "cooperative standard", + "key": "TEST_PROJECT", + "uuid": "{3b3e510b-7f2b-414d-a2b7-76c4e405c1c0}" + }, + "created_on": "2021-07-28T21:20:19.491721+00:00", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDX5yfMOEw6HG9jKTYTisbmDTJ4MCUTSVGr5e4OWvY3UuI2A6F8SdzQqa2f5BABA/4g5Sk5awJrYHlNu3EzV1V2I44tR3A4fnZAG71ZKyDPi1wvdO7UYmFgxV/Vd18H9QZFFjICGDM7W0PT2mI0kON/jN3qNWi+GiB/xgaeQKSqynysdysDp8lnnI/8Sh3ikURP9UP83ShRCpAXszOUNaa+UUlcYQYBDLIGowsg51c4PCkC3DNhAMxppkNRKoSOWwyl+oRVXHSDylkiJSBHW3HH4Q6WHieD54kGrjbhWBKdnnxKX7QAAZBDseY+t01N36m6/ljvXSUEcBWtHxBYye0r", + "type": "project_deploy_key", + "id": 1234 + } + ] + } + } } } } @@ -18797,11 +23319,795 @@ } ] }, + "/workspaces/{workspace}/projects/{project_key}/permissions-config/groups": { + "get": { + "tags": ["Projects"], + "description": "Returns a paginated list of explicit group permissions for the given project.\nThis endpoint does not support BBQL features.", + "summary": "List explicit group permissions for a project", + "responses": { + "200": { + "description": "Paginated list of project group permissions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_project_group_permissions" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "values": [ + { + "type": "project_group_permission", + "group": { + "type": "group", + "name": "Administrators", + "slug": "administrators" + }, + "permission": "admin", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/projects/PRJ/permissions-config/groups/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a" + } + } + }, + { + "type": "project_group_permission", + "group": { + "type": "group", + "name": "Developers", + "slug": "developers" + }, + "permission": "write", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/projects/PRJ/permissions-config/groups/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324c" + } + } + } + ], + "page": 1, + "size": 2 + } + } + } + } + } + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The user doesn't have admin access to the project.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "One or both of the workspace and project don't exist for the given identifiers or the requesting user is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:project:bitbucket", "read:user:bitbucket"] + } + ] + }, + "parameters": [ + { + "name": "project_key", + "in": "path", + "description": "The project in question. This is the actual key assigned to the project.\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/workspaces/{workspace}/projects/{project_key}/permissions-config/groups/{group_slug}": { + "delete": { + "tags": ["Projects"], + "description": "Deletes the project group permission between the requested project and group, if one exists.\n\nOnly users with admin permission for the project may access this resource.", + "summary": "Delete an explicit group permission for a project", + "responses": { + "204": { + "description": "The project group permission was deleted and no content returned." + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the project, or the authentication method was not via app password.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "One or more of the workspace, project, and group doesn't exist for the given identifiers or the requesting user is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "get": { + "tags": ["Projects"], + "description": "Returns the group permission for a given group and project.\n\nOnly users with admin permission for the project may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `create-repo`\n* `write`\n* `read`\n* `none`", + "summary": "Get an explicit group permission for a project", + "responses": { + "200": { + "description": "Project group permission", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/project_group_permission" + }, + "examples": { + "response": { + "value": { + "type": "project_group_permission", + "group": { + "type": "group", + "name": "Administrators", + "slug": "administrators" + }, + "permission": "admin", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/projects/PRJ/permissions-config/groups/administrators" + } + } + } + } + } + } + } + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The user doesn't have admin access to the project.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "One or more of the workspace, project, and group doesn't exist for the given identifiers or the requesting user is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:project:bitbucket", "read:user:bitbucket"] + } + ] + }, + "put": { + "tags": ["Projects"], + "description": "Updates the group permission, or grants a new permission if one does not already exist.\n\nOnly users with admin permission for the project may access this resource.\n\nDue to security concerns, the JWT and OAuth authentication methods are unsupported.\nThis is to ensure integrations and add-ons are not allowed to change permissions.\n\nPermissions can be:\n\n* `admin`\n* `create-repo`\n* `write`\n* `read`", + "summary": "Update an explicit group permission for a project", + "responses": { + "200": { + "description": "Project group permission updated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/project_group_permission" + }, + "examples": { + "response": { + "value": { + "type": "project_group_permission", + "group": { + "type": "group", + "name": "Administrators", + "slug": "administrators" + }, + "permission": "write", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/projects/PRJ/permissions-config/groups/administrators" + } + } + } + } + } + } + } + }, + "400": { + "description": "No permission value was provided or the value is invalid(not one of read, write, create-repo, or admin).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "402": { + "description": "You have reached your plan's user limit and must upgrade before giving access to additional users.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the project, or the authentication method was not via app password.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "One or more of the workspace, project, and group doesn't exist for the given identifiers or the requesting user is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/bitbucket.apps.permissions.serializers.ProjectPermissionUpdateSchema" + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "parameters": [ + { + "name": "group_slug", + "in": "path", + "description": "Slug of the requested group.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "project_key", + "in": "path", + "description": "The project in question. This is the actual key assigned to the project.\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/workspaces/{workspace}/projects/{project_key}/permissions-config/users": { + "get": { + "tags": ["Projects"], + "description": "Returns a paginated list of explicit user permissions for the given project.\nThis endpoint does not support BBQL features.", + "summary": "List explicit user permissions for a project", + "responses": { + "200": { + "description": "Paginated list of explicit user permissions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_project_user_permissions" + }, + "examples": { + "response": { + "value": { + "pagelen": 10, + "values": [ + { + "type": "project_user_permission", + "user": { + "type": "user", + "display_name": "Colin Cameron", + "uuid": "{d301aafa-d676-4ee0-88be-962be7417567}", + "account_id": "557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a" + }, + "permission": "admin", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/projects/PRJ/permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a" + } + } + }, + { + "type": "project_user_permission", + "user": { + "type": "user", + "display_name": "Sean Conaty", + "uuid": "{504c3b62-8120-4f0c-a7bc-87800b9d6f70}", + "account_id": "557058:ba8948b2-49da-43a9-9e8b-e7249b8e324c" + }, + "permission": "write", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/projects/PRJ/permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324c" + } + } + } + ], + "page": 1, + "size": 2 + } + } + } + } + } + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The user doesn't have admin access to the project.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "One or both of the workspace and project don't exist for the given identifiers or the requesting user is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:project:bitbucket", "read:user:bitbucket"] + } + ] + }, + "parameters": [ + { + "name": "project_key", + "in": "path", + "description": "The project in question. This is the actual key assigned to the project.\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/workspaces/{workspace}/projects/{project_key}/permissions-config/users/{selected_user_id}": { + "delete": { + "tags": ["Projects"], + "description": "Deletes the project user permission between the requested project and user, if one exists.\n\nOnly users with admin permission for the project may access this resource.\n\nDue to security concerns, the JWT and OAuth authentication methods are unsupported.\nThis is to ensure integrations and add-ons are not allowed to change permissions.", + "summary": "Delete an explicit user permission for a project", + "responses": { + "204": { + "description": "The project user permission was deleted and no content returned." + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the project, or the authentication method was not via app password.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "One or more of the workspace, project, and selected user doesn't exist for the given identifiers or the requesting user is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "get": { + "tags": ["Projects"], + "description": "Returns the explicit user permission for a given user and project.\n\nOnly users with admin permission for the project may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `create-repo`\n* `write`\n* `read`\n* `none`", + "summary": "Get an explicit user permission for a project", + "responses": { + "200": { + "description": "Explicit user permission for user and project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/project_user_permission" + }, + "examples": { + "response": { + "value": { + "type": "project_user_permission", + "user": { + "type": "user", + "display_name": "Colin Cameron", + "uuid": "{d301aafa-d676-4ee0-88be-962be7417567}", + "account_id": "557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a" + }, + "permission": "admin", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/projects/PRJ/permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a" + } + } + } + } + } + } + } + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the project.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "One or more of the workspace, project, and selected user doesn't exist for the given identifiers or the requesting user is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:project:bitbucket", "read:user:bitbucket"] + } + ] + }, + "put": { + "tags": ["Projects"], + "description": "Updates the explicit user permission for a given user and project. The selected\nuser must be a member of the workspace, and cannot be the workspace owner.\n\nOnly users with admin permission for the project may access this resource.\n\nDue to security concerns, the JWT and OAuth authentication methods are unsupported.\nThis is to ensure integrations and add-ons are not allowed to change permissions.\n\nPermissions can be:\n\n* `admin`\n* `create-repo`\n* `write`\n* `read`", + "summary": "Update an explicit user permission for a project", + "responses": { + "200": { + "description": "Explicit user permission updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/project_user_permission" + }, + "examples": { + "response": { + "value": { + "type": "project_user_permission", + "user": { + "type": "user", + "display_name": "Colin Cameron", + "uuid": "{d301aafa-d676-4ee0-88be-962be7417567}", + "account_id": "557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a" + }, + "permission": "write", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/projects/PRJ/permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a" + } + } + } + } + } + } + } + }, + "400": { + "description": "No permission value was provided or the value is invalid (not one of read, write, create-repo, or admin)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "402": { + "description": "You have reached your plan's user limit and must upgrade before giving access to additional users.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the project, or the authentication method was not via app password.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "One or more of the workspace, project, and selected user doesn't exist for the given identifiers or the requesting user is not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/bitbucket.apps.permissions.serializers.ProjectPermissionUpdateSchema" + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "parameters": [ + { + "name": "project_key", + "in": "path", + "description": "The project in question. This is the actual key assigned to the project.\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "selected_user_id", + "in": "path", + "description": "This can either be the username, the user's UUID surrounded by curly-braces,\nfor example: {account UUID}, or the user's Atlassian ID.\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, "/workspaces/{workspace}/search/code": { "get": { "tags": ["Search"], "summary": "Search for code in a workspace", - "description": "Search for code in the repositories of the specified workspace.\n\nSearching across all repositories:\n\n```\ncurl 'https://api.bitbucket.org/2.0/workspaces/workspace_slug_or_uuid/search/code?search_query=foo'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 2,\n \"content_matches\": [\n {\n \"lines\": [\n {\n \"line\": 2,\n \"segments\": []\n },\n {\n \"line\": 3,\n \"segments\": [\n {\n \"text\": \"def \"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \"():\"\n }\n ]\n },\n {\n \"line\": 4,\n \"segments\": [\n {\n \"text\": \" print(\\\"snek\\\")\"\n }\n ]\n },\n {\n \"line\": 5,\n \"segments\": []\n }\n ]\n }\n ],\n \"path_matches\": [\n {\n \"text\": \"src/\"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \".py\"\n }\n ],\n \"file\": {\n \"path\": \"src/foo.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n }\n }\n }\n ]\n}\n```\n\nNote that searches can match in the file's text (`content_matches`),\nthe path (`path_matches`), or both as in the example above.\n\nYou can use the same syntax for the search query as in the UI, e.g.\nto only search within a specific repository:\n\n```\ncurl 'https://api.bitbucket.org/2.0/workspaces/my-workspace/search/code?search_query=foo+repo:demo'\n# results from the \"demo\" repository\n```\n\nSimilar to other APIs, you can request more fields using a\n`fields` query parameter. E.g. to get some more information about\nthe repository of matched files (the `%2B` is a URL-encoded `+`):\n\n```\ncurl 'https://api.bitbucket.org/2.0/workspaces/my-workspace/search/code'\\\n '?search_query=foo&fields=%2Bvalues.file.commit.repository'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 1,\n \"content_matches\": [...],\n \"path_matches\": [...],\n \"file\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/commit/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo/commits/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n }\n },\n \"repository\": {\n \"name\": \"demo\",\n \"type\": \"repository\",\n \"full_name\": \"my-workspace/demo\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B850e1749-781a-4115-9316-df39d0600e7a%7D?ts=default\"\n }\n },\n \"uuid\": \"{850e1749-781a-4115-9316-df39d0600e7a}\"\n }\n },\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n },\n \"path\": \"src/foo.py\"\n }\n }\n ]\n}\n```\n\nTry `fields=%2Bvalues.*.*.*.*` to get an idea what's possible.\n", + "description": "Search for code in the repositories of the specified workspace.\n\nNote that searches can match in the file's text (`content_matches`),\nthe path (`path_matches`), or both.\n\nYou can use the same syntax for the search query as in the UI.\nE.g. to search for \"foo\" only within the repository \"demo\",\nuse the query parameter `search_query=foo+repo:demo`.\n\nSimilar to other APIs, you can request more fields using a\n`fields` query parameter. E.g. to get some more information about\nthe repository of matched files, use the query parameter\n`search_query=foo&fields=%2Bvalues.file.commit.repository`\n(the `%2B` is a URL-encoded `+`).\n\nTry `fields=%2Bvalues.*.*.*.*` to get an idea what's possible.\n", "operationId": "searchWorkspace", "parameters": [ { @@ -18852,6 +24158,80 @@ "application/json": { "schema": { "$ref": "#/components/schemas/search_result_page" + }, + "examples": { + "response": { + "value": { + "size": 1, + "page": 1, + "pagelen": 10, + "query_substituted": false, + "values": [ + { + "type": "code_search_result", + "content_match_count": 2, + "content_matches": [ + { + "lines": [ + { + "line": 2, + "segments": [] + }, + { + "line": 3, + "segments": [ + { + "text": "def " + }, + { + "text": "foo", + "match": true + }, + { + "text": "():" + } + ] + }, + { + "line": 4, + "segments": [ + { + "text": " print(\"snek\")" + } + ] + }, + { + "line": 5, + "segments": [] + } + ] + } + ], + "path_matches": [ + { + "text": "src/" + }, + { + "text": "foo", + "match": true + }, + { + "text": ".py" + } + ], + "file": { + "path": "src/foo.py", + "type": "commit_file", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py" + } + } + } + } + ] + } + } } } } @@ -18886,7 +24266,25 @@ } } } - } + }, + "x-atlassian-oauth2-scopes": [ + { + "state": "Current", + "scheme": "oauth2", + "scopes": ["read:repository:bitbucket"] + } + ], + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] } } }, @@ -18921,7 +24319,7 @@ }, { "name": "Issue tracker", - "description": "The issue resources provide functionality for getting information on\nissues in an issue tracker, creating new issues, updating them and deleting\nthem.\n\nYou can access public issues without authentication, but you can't gain access\nto private repositories' issues. By authenticating, you will get the ability\nto create issues, as well as access to updating data or deleting issues you\nhave access to.\n" + "description": "The issue resources provide functionality for getting information on\nissues in an issue tracker, creating new issues, updating them and deleting\nthem.\n\nYou can access public issues without authentication, but you can't gain access\nto private repositories' issues. By authenticating, you will get the ability\nto create issues, as well as access to updating data or deleting issues you\nhave access to. Issue Tracker features are not supported for repositories in workspaces administered through admin.atlassian.com.\n" }, { "name": "Pipelines", @@ -18980,7 +24378,7 @@ "description": "A workspace is where you create repositories, collaborate on\nyour code, and organize different streams of work in your Bitbucket\nCloud account. Workspaces replace the use of teams and users in API\ncalls.\n" } ], - "x-revision": "607fbe8bfcb2", + "x-revision": "d8aa33b24a80", "x-atlassian-narrative": { "documents": [ { @@ -18988,7 +24386,7 @@ "title": "Authentication methods", "description": "How to authenticate API actions", "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTcuNjQ3MyAxODYuODEzOCI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5jbHMtMSB7CiAgICAgICAgaXNvbGF0aW9uOiBpc29sYXRlOwogICAgICB9CgogICAgICAuY2xzLTIgewogICAgICAgIGZpbGw6ICNkZTM1MGI7CiAgICAgIH0KCiAgICAgIC5jbHMtMyB7CiAgICAgICAgZmlsbDogI2ZmNTYzMDsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZGZlMWU1OwogICAgICAgIG1peC1ibGVuZC1tb2RlOiBtdWx0aXBseTsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjZmFmYmZjOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIGZpbGw6ICNlYmVjZjA7CiAgICAgIH0KCiAgICAgIC5jbHMtNyB7CiAgICAgICAgZmlsbDogbm9uZTsKICAgICAgICBzdHJva2U6ICMwMDY1ZmY7CiAgICAgICAgc3Ryb2tlLW1pdGVybGltaXQ6IDEwOwogICAgICAgIHN0cm9rZS13aWR0aDogMnB4OwogICAgICB9CgogICAgICAuY2xzLTggewogICAgICAgIGZpbGw6ICM1ZTZjODQ7CiAgICAgIH0KCiAgICAgIC5jbHMtOSB7CiAgICAgICAgZmlsbDogIzI1Mzg1ODsKICAgICAgfQoKICAgICAgLmNscy0xMCB7CiAgICAgICAgZmlsbDogIzI2ODRmZjsKICAgICAgfQoKICAgICAgLmNscy0xMSB7CiAgICAgICAgZmlsbDogIzAwNjVmZjsKICAgICAgfQogICAgPC9zdHlsZT4KICA8L2RlZnM+CiAgPHRpdGxlPlNlY3VyaXR5IHdpdGggS2V5PC90aXRsZT4KICA8ZyBjbGFzcz0iY2xzLTEiPgogICAgPGcgaWQ9IkxheWVyXzIiIGRhdGEtbmFtZT0iTGF5ZXIgMiI+CiAgICAgIDxnIGlkPSJPYmplY3RzIj4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik00Mi4wNjcyLDBoLjYxMTRhOCw4LDAsMCwxLDgsOFYyMy4yMzM4YTAsMCwwLDAsMSwwLDBIMzQuMDY3MmEwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDQyLjA2NzIsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xMDguMjIsMGguNjExNGE4LDgsMCwwLDEsOCw4VjIzLjIzMzhhMCwwLDAsMCwxLDAsMEgxMDAuMjJhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSwxMDguMjIsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xNzQuMzcyMiwwaC42MTE0YTgsOCwwLDAsMSw4LDhWMjMuMjMzOGEwLDAsMCwwLDEsMCwwSDE2Ni4zNzIyYTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMTc0LjM3MjIsMFoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjM0LjA2NzIiIHk9IjIzLjIzMzgiIHdpZHRoPSIxNjMuNTgiIGhlaWdodD0iMTYzLjU4Ii8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNDIuMDY3MiwwSDU5LjI5YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDM0LjA2NzJhMCwwLDAsMCwxLDAsMFY4YTgsOCwwLDAsMSw4LThaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTA3LjI0NTgsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDk5LjI0NThhMCwwLDAsMCwxLDAsMFY4YTgsOCwwLDAsMSw4LThaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTcyLjQyNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDE2NC40MjQ0YTAsMCwwLDAsMSwwLDBWOGE4LDgsMCwwLDEsOC04WiIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMyIgeD0iMTcuNDU1OCIgeT0iMjMuMjMzOCIgd2lkdGg9IjE2My41OCIgaGVpZ2h0PSIxNjMuNTgiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik0yNS40NTU4LDBINDIuNjc4NmE4LDgsMCwwLDEsOCw4VjIzLjIyMjhhMCwwLDAsMCwxLDAsMEgxNy40NTU4YTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMjUuNDU1OCwwWiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTkwLjYzNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDgyLjYzNDRhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSw5MC42MzQ0LDBaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTU1LjgxMywwaDE3LjIyMjhhOCw4LDAsMCwxLDgsOFYyMy4yMjI4YTAsMCwwLDAsMSwwLDBIMTQ3LjgxM2EwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDE1NS44MTMsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik0yNS40NTU4LDBINDIuNjc4NmE4LDgsMCwwLDEsOCw4VjIzLjIyMjhhMCwwLDAsMCwxLDAsMEgxNy40NTU4YTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMjUuNDU1OCwwWiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTkwLjYzNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDgyLjYzNDRhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSw5MC42MzQ0LDBaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTU1LjgxMywwaDE3LjIyMjhhOCw4LDAsMCwxLDgsOFYyMy4yMjI4YTAsMCwwLDAsMSwwLDBIMTQ3LjgxM2EwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDE1NS44MTMsMFoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjM1Ljc1OTYiIHk9IjU2LjgwNjUiIHdpZHRoPSIzMy4yMjI4IiBoZWlnaHQ9IjE1LjYwMzgiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjEzMS4yMDE2IiB5PSIxMzYuOTYxNSIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTU3LjM3MDksNzEuNjAzNmg3MC43NWE5LDksMCwwLDEsOSw5djM1LjM3NDlhNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLTQ0LjM3NDgsNDQuMzc0OGgwYTQ0LjM3NDgsNDQuMzc0OCwwLDAsMS00NC4zNzQ4LTQ0LjM3NDhWODAuNjAzNkE5LDksMCwwLDEsNTcuMzcwOSw3MS42MDM2WiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNSIgZD0iTTY2LjM3MSw2Ni42NjE3aDcwLjc1YTksOSwwLDAsMSw5LDl2MzUuMzc0OWE0NC4zNzQ4LDQ0LjM3NDgsMCwwLDEtNDQuMzc0OCw0NC4zNzQ4aDBBNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLDU3LjM3MSwxMTEuMDM2NlY3NS42NjE3YTksOSwwLDAsMSw5LTlaIi8+CiAgICAgICAgPHBhdGggaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIGQ9Ik02MS4zNzEsNjYuNjYxN2g3MC43NWE5LDksMCwwLDEsOSw5djM1LjM3NDlhNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLTQ0LjM3NDgsNDQuMzc0OGgwQTQ0LjM3NDgsNDQuMzc0OCwwLDAsMSw1Mi4zNzEsMTExLjAzNjZWNzUuNjYxN0E5LDksMCwwLDEsNjEuMzcxLDY2LjY2MTdaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy03IiBkPSJNOTYuNzQ1OSwxNDcuNzQ0MWEzNi43NDg3LDM2Ljc0ODcsMCwwLDEtMzYuNzA3NC0zNi43MDc0Vjc4LjA1ODRhMy43MzMzLDMuNzMzMywwLDAsMSwzLjcyOS0zLjcyOWg2NS45NTYzYTMuNzMzMywzLjczMzMsMCwwLDEsMy43MjksMy43Mjl2MzIuOTc4NEEzNi43NDg2LDM2Ljc0ODYsMCwwLDEsOTYuNzQ1OSwxNDcuNzQ0MVoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMDAuNjg5MywxNjMuMzE2N1YxMTEuMDk3M2EzLjk0NDMsMy45NDQzLDAsMCwwLTcuODg4NywwdjUyLjIyYTIyLjUyNTIsMjIuNTI1MiwwLDAsMC0xOC41NDc5LDIyLjE0YzAsLjQ1Ni4wMTc4LjkwNzguMDQ0NywxLjM1NzFIODIuMjFjLS4wNDE0LS40NDc0LS4wNjg4LS44OTktLjA2ODgtMS4zNTcxYTE0LjYyLDE0LjYyLDAsMCwxLDE0LjU5NzQtMTQuNjA0MWwuMDA2MS4wMDA2LjAwNjgtLjAwMDdBMTQuNjIxMSwxNC42MjExLDAsMCwxLDExMS4zNSwxODUuNDU2NmMwLC40NTgxLS4wMjczLjkxLS4wNjg4LDEuMzU3MWg3LjkxMjhjLjAyNjktLjQ0OTMuMDQ0Ny0uOTAxMS4wNDQ3LTEuMzU3MUEyMi41MjU5LDIyLjUyNTksMCwwLDAsMTAwLjY4OTMsMTYzLjMxNjdaIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxNy40NTU4IiB5PSIzNi40NzAyIiB3aWR0aD0iMzMuMjIyOCIgaGVpZ2h0PSIxNS42MDM4Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxNy40NTU4IiB5PSIxNTguMTIxNyIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMTQ3LjgxMyIgeT0iMzYuNDcwMiIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMTUwLjA2NDMiIHk9IjE1Ny41NTEzIiB3aWR0aD0iMzMuMjIyOCIgaGVpZ2h0PSIxNS42MDM4Ii8+CiAgICAgICAgPHBhdGggaWQ9Il9QYXRoXyIgZGF0YS1uYW1lPSImbHQ7UGF0aCZndDsiIGNsYXNzPSJjbHMtOCIgZD0iTTEwNy41MjU0LDEwMS4wMDI3YTExLjc3OTQsMTEuNzc5NCwwLDEsMC0xOS44Niw4LjU1NDhBNC4wNDE3LDQuMDQxNywwLDAsMSw4OC44NSwxMTMuNjJsLTIuMTA0LDcuMjY4MWEzLDMsMCwwLDAsMi44ODE3LDMuODM0MmgxMi4yMzcxYTMsMywwLDAsMCwyLjg4MTctMy44MzQybC0yLjA5NTktNy4yNGE0LjA3NDMsNC4wNzQzLDAsMCwxLDEuMTgwOC00LjA5NDVBMTEuNzE3MiwxMS43MTcyLDAsMCwwLDEwNy41MjU0LDEwMS4wMDI3WiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTEwNC43NDYxLDEyMC44ODc3bC0yLjA5NTktNy4yNGE0LjA3NDQsNC4wNzQ0LDAsMCwxLDEuMTgwOC00LjA5NDUsMTEuNzYyOSwxMS43NjI5LDAsMCwwLTUuMDYtMTkuOTMxMywxMS45MSwxMS45MSwwLDAsMC04Ljc5OCwxMC45OTQ5LDExLjcxODUsMTEuNzE4NSwwLDAsMCwzLjY5MjksOC45NDFBNC4wNDE2LDQuMDQxNiwwLDAsMSw5NC44NSwxMTMuNjJsLTMuMjE0LDExLjEwMjNoMTAuMjI4OEEzLDMsMCwwLDAsMTA0Ljc0NjEsMTIwLjg4NzdaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTgxLjc5NzUsMTAwLjMxYTMuOTQzOSwzLjk0MzksMCwwLDAtMy45NDQzLTMuOTQ0M0g0MS4wNDE3YTMuOTQ0MywzLjk0NDMsMCwwLDAsMCw3Ljg4ODdINzcuODUzMkEzLjk0MzksMy45NDM5LDAsMCwwLDgxLjc5NzUsMTAwLjMxWiIvPgogICAgICAgIDxwYXRoIGlkPSJfUGF0aF8yIiBkYXRhLW5hbWU9IiZsdDtQYXRoJmd0OyIgY2xhc3M9ImNscy0xMSIgZD0iTTQxLjA0MTYsMTA0LjI1MzlIOTYuODUzMmEzLjk0NDMsMy45NDQzLDAsMCwwLDAtNy44ODg3SDQxLjA0MTZhMy45NDQzLDMuOTQ0MywwLDAsMCwwLDcuODg4N1oiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTEwIiBkPSJNODEuNzk3NSwxMDAuMzFhMy45NDM5LDMuOTQzOSwwLDAsMC0zLjk0NDMtMy45NDQzSDQxLjA0MTdhMy45NDQzLDMuOTQ0MywwLDAsMCwwLDcuODg4N0g3Ny44NTMyQTMuOTQzOSwzLjk0MzksMCwwLDAsODEuNzk3NSwxMDAuMzFaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTIyLjQ5MzIsMTIyLjgwMjlBMjIuNDkyOSwyMi40OTI5LDAsMSwxLDQ0Ljk4NTgsMTAwLjMxLDIyLjUxODUsMjIuNTE4NSwwLDAsMSwyMi40OTMyLDEyMi44MDI5Wm0wLTM3LjA5NzJBMTQuNjA0MiwxNC42MDQyLDAsMSwwLDM3LjA5NzIsMTAwLjMxLDE0LjYyMDcsMTQuNjIwNywwLDAsMCwyMi40OTMyLDg1LjcwNTdaIi8+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo='", - "body": "\nThe purpose of this section is to describe how to authenticate when making API calls using the Bitbucket REST API.\n\n-----\n\n* [Oauth 2](#oauth-2)\n * [Making requests](#making-requests)\n * [Repository cloning](#repository-cloning)\n * [Refresh tokens](#refresh-tokens)\n* [Scopes](#scopes)\n* [Basic auth](#basic-auth)\n* [Repository Access Tokens](#repository-access-tokens)\n* [App passwords](#app-passwords)\n\n---\n\n### OAuth 2.0\n\nOur OAuth 2 implementation is merged in with our existing OAuth 1 in\nsuch a way that existing OAuth 1 consumers automatically become\nvalid OAuth 2 clients. The only thing you need to do is edit your\nexisting consumer and configure a callback URL.\n\nOnce that is in place, you'll have the following 2 URLs:\n\n https://bitbucket.org/site/oauth2/authorize\n https://bitbucket.org/site/oauth2/access_token\n\nFor obtaining access/bearer tokens, we support three of RFC-6749's grant\nflows, plus a custom Bitbucket flow for exchanging JWT tokens for access tokens.\nNote that Resource Owner Password Credentials Grant (4.3) is no longer supported.\n\n\n#### 1. Authorization Code Grant (4.1)\n\nThe full-blown 3-LO flow. Request authorization from the end user by\nsending their browser to:\n\n https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=code\n\nThe callback includes the `?code={}` query parameter that you can swap\nfor an access token:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=authorization_code -d code={code}\n\n\n#### 2. Implicit Grant (4.2)\n\nThis flow is useful for browser-based add-ons that operate without server-side backends.\n\nRequest the end user for authorization by directing the browser to:\n\n https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=token\n\nThat will redirect to your preconfigured callback URL with a fragment\ncontaining the access token\n(`#access_token={token}&token_type=bearer`) where your page's js can\npull it out of the URL.\n\n\n#### 3. Client Credentials Grant (4.4)\n\nSomewhat like our existing \"2-LO\" flow for OAuth 1. Obtain an access\ntoken that represents not an end user, but the owner of the\nclient/consumer:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=client_credentials\n\n\n#### 4. Bitbucket Cloud JWT Grant (urn:bitbucket:oauth2:jwt)\n\nIf your Atlassian Connect add-on uses JWT authentication, you can swap a\nJWT for an OAuth access token. The resulting access token represents the\naccount for which the add-on is installed.\n\nMake sure you send the JWT token in the Authorization request header\nusing the \"JWT\" scheme (case sensitive). Note that this custom scheme\nmakes this different from HTTP Basic Auth (and so you cannot use \"curl\n-u\").\n\n $ curl -X POST -H \"Authorization: JWT {jwt_token}\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=urn:bitbucket:oauth2:jwt\n\n\n#### Making Requests\n\nOnce you have an access token, as per RFC-6750, you can use it in a request in any of\nthe following ways (in decreasing order of desirability):\n\n1. Send it in a request header: `Authorization: Bearer {access_token}`\n2. Include it in a (application/x-www-form-urlencoded) POST body as `access_token={access_token}`\n3. Put it in the query string of a non-POST: `?access_token={access_token}`\n\n\n#### Repository Cloning\n\nSince add-ons will not be able to upload their own SSH keys to clone\nwith, access tokens can be used as Basic HTTP Auth credentials to\nclone securely over HTTPS. This is much like GitHub, yet slightly\ndifferent:\n\n $ git clone https://x-token-auth:{access_token}@bitbucket.org/user/repo.git\n\nThe literal string `x-token-auth` as a substitute for username is\nrequired (note the difference with GitHub where the actual token is in\nthe username field).\n\n\n#### Refresh Tokens\n\nOur access tokens expire in one hour. When this happens you'll get 401\nresponses.\n\nMost access tokens grant responses (Implicit and JWT excluded). Therefore, you should include a\nrefresh token that can then be used to generate a new access token,\nwithout the need for end user participation:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=refresh_token -d refresh_token={refresh_token}\n\n\n### Scopes\n\nBitbucket's API applies a number of privilege scopes to endpoints. In order to access an endpoint, a request will need to have the necessary scopes.\n\nScopes are declared in the descriptor as a list of strings, with each string being the name of a unique scope.\n\nA descriptor lacking the `scopes` element is implicitly assumed to require all scopes and as a result, Bitbucket will require end users authorizing/installing the add-on\nto explicitly accept all scopes.\n\nOur best practice suggests you add the scopes your add-on needs, but no more than it needs.\n\nInvalid scope strings will cause the descriptor to be rejected and the installation to fail.\n\nThe available scopes are:\n\n- [project](#project)\n- [project:write](#project-write)\n- [project:admin](#project-admin)\n- [repository](#repository)\n- [repository:write](#repository-write)\n- [repository:admin](#repository-admin)\n- [repository:delete](#repository-delete)\n- [pullrequest](#pullrequest)\n- [pullrequest:write](#pullrequest-write)\n- [issue](#issue)\n- [issue:write](#issue-write)\n- [wiki](#wiki)\n- [webhook](#webhook)\n- [snippet](#snippet)\n- [snippet:write](#snippet-write)\n- [email](#email)\n- [account](#account)\n- [account:write](#account-write)\n- [pipeline](#pipeline)\n- [pipeline:write](#pipeline-write)\n- [pipeline:variable](#pipeline-variable)\n- [runner](#runner)\n- [runner:write](#runner-write)\n\n#### project\n\nProvides the [`repository`](#repository) scope permission for every repository under a project or projects.\n\n#### project:write\n\nThis scope is deprecated, and has been made obsolete by `project:admin`. Please see the deprecation notice [here](/cloud/bitbucket/deprecation-notice-project-write-scope).\n\n#### project:admin\n\nProvides admin access to a project or projects. No distinction is made between public and private projects. This scope doesn't implicitly grant the [`project`](#project) scope or the [`repository:write`](#repository-write) scope on any repositories under the project. It gives access to the admin features of a project only, not direct access to its repositories' contents.\n\n* ability to create the project\n* ability to update the project\n* ability to delete the project\n\n#### repository\n\nProvides read access to a repository or repositories.\nNote that this scope does not give access to a repository's pull requests.\n\n* access to the repo's source code\n* clone over HTTPS\n* access the file browsing API\n* download zip archives of the repo's contents\n* the ability to view and use the issue tracker on any repo (created issues, comment, vote, etc)\n* the ability to view and use the wiki on any repo (create/edit pages)\n\n#### repository:write\n\nProvides write (not admin) access to a repository or repositories. No distinction is made between public and private repositories. This scope implicitly grants the [`repository`](#repository) scope, which does not need to be requested separately.\nThis scope alone does not give access to the pull requests API.\n\n* push access over HTTPS\n* fork repos\n\n#### repository:admin\n\nProvides admin access to a repository or repositories. No distinction is made between public and private repositories. This scope doesn't implicitly grant the [`repository`](#repository) or the [`repository:write`](#repository-write) scopes. It gives access to the admin features of a repo only, not direct access to its contents. This scope can be used or misused to grant read access to other users, who can then clone the repo, but users that need to read and write source code would also request explicit read or write.\nThis scope comes with access to the following functionality:\n\n* view and manipulate committer mappings\n* list and edit deploy keys\n* ability to delete the repo\n* view and edit repo permissions\n* view and edit branch permissions\n* import and export the issue tracker\n* enable and disable the issue tracker\n* list and edit issue tracker version, milestones and components\n* enable and disable the wiki\n* list and edit default reviewers\n* list and edit repo links (Jira/Bamboo/Custom)\n* list and edit the repository webhooks\n* initiate a repo ownership transfer\n\n#### repository:delete\n\nProvides access to delete a repository or repositories.\n\n#### pullrequest\n\nProvides read access to pull requests.\nThis scope implies the [`repository`](#repository) scope, giving read access to the pull request's destination repository.\n\n* see and list pull requests\n* create and resolve tasks\n* comment on pull requests\n\n#### pullrequest:write\n\nImplicitly grants the [`pullrequest`](#pullrequest) scope and adds the ability to create, merge and decline pull requests.\nThis scope also implicitly grants the [`repository:write`](#repository-write) scope, giving write access to the pull request's destination repository. This is necessary to allow merging.\n\n* merge pull requests\n* decline pull requests\n* create pull requests\n* approve pull requests\n\n#### issue\n\nAbility to interact with issue trackers the way non-repo members can.\nThis scope doesn't implicitly grant any other scopes and doesn't give implicit access to the repository.\n\n* view, list and search issues\n* create new issues\n* comment on issues\n* watch issues\n* vote for issues\n\n#### issue:write\n\nThis scope implicitly grants the [`issue`](#issue) scope and adds the ability to transition and delete issues.\nThis scope doesn't implicitly grant any other scopes and doesn't give implicit access to the repository.\n\n* transition issues\n* delete issues\n\n#### wiki\n\nProvides access to wikis. This scope provides both read and write access (wikis are always editable by anyone with access to them).\nThis scope doesn't implicitly grant any other scopes and doesn't give implicit access to the repository.\n\n* view wikis\n* create pages\n* edit pages\n* push to wikis\n* clone wikis\n\n#### webhook\n\nGives access to webhooks. This scope is required for any webhook-related operation.\n\nThis scope gives read access to existing webhook subscriptions on all\nresources the authorization mechanism can access, without needing further scopes. \nFor example:\n\n- A client can list all existing webhook subscriptions on a repository. The [`repository`](#repository) scope is not required.\n- Existing webhook subscriptions for the issue tracker on a repo can be retrieved without the [`issue`](#issue) scope. All that is required is the `webhook` scope.\n\nTo create webhooks, the client will need read access to the resource. Such as: for [`issue:created`](#issue-created), the client will need to\nhave both the `webhook` and the [`issue`](#issue) scope.\n\n* list webhook subscriptions on any accessible repository, user, team, or snippet\n* create/update/delete webhook subscriptions.\n\n#### snippet\n\nProvides read access to snippets.\nNo distinction is made between public and private snippets (public snippets are accessible without any form of authentication).\n\n* view any snippet\n* create snippet comments\n\n#### snippet:write\n\nProvides write access to snippets.\nNo distinction is made between public and private snippets (public snippets are accessible without any form of authentication).\nThis scope implicitly grants the [`snippet`](#snippet) scope which does not need to be requested separately.\n\n* create snippets\n* edit snippets\n* delete snippets\n\n#### email\n\nAbility to see the user's primary email address. This should make it easier to use Bitbucket Cloud as a login provider for apps or external applications.\n\n#### account\n\nAbility to see all the user's account information. Note that this doesn't include any ability to change any of the data.\n\n* see all email addresses\n* language\n* location\n* website\n* full name\n* SSH keys\n* user groups\n\n#### account:write\n\nAbility to change properties on the user's account.\n\n* delete the authorizing user's account\n* manage the user's groups\n* change a user's email addresses\n* change username, display name and avatar\n\n#### pipeline\n\nGives read-only access to pipelines, steps, deployment environments and variables.\n\n#### pipeline:write\n\nGives write access to pipelines. This scope allows a user to:\n* Stop pipelines\n* Rerun failed pipelines\n* Resume halted pipelines\n* Trigger manual pipelines.\n\nThis scope is not needed to trigger a build using a push. Performing a `git push` (or equivalent actions) will trigger the build. The token doing the push only needs the [`repository:write`](#repository-write) scope.\n\nThis doesn't give write access to create variables.\n\n#### pipeline:variable\n\nGives write access to create variables in pipelines at the various levels:\n* Workspace\n* Repository\n* Deployment\n\n#### runner\n\nGives read-only access to pipelines runners setup against a workspace or repository.\n\n#### runner:write\n\nGives write access to create/edit/disable/delete pipelines runners setup against a workspace or repository.\n\n### Basic auth\n\nBasic HTTP Authentication as per [RFC-2617](https://tools.ietf.org/html/rfc2617) (Digest not supported). Note that Basic Auth is available only with username and [app password](https://bitbucket.org/account/settings/app-passwords/) as credentials.\n\n### Repository Access Tokens\n\nRepository Access Tokens are passwords (or tokens) that provide access to\n_a single repository_. These tokens can authenticate with Bitbucket APIs for\nscripting, CI/CD tools, Bitbucket Cloud-connected apps, and Bitbucket Cloud\nintegrations. The level of access provided by the token is set when a repository\nadmin creates it, by setting permission scopes. Repository Access Tokens are\nlinked to their repository, not a user or a workspace, preventing them from\nbeing used to access any other repositories or workspaces.\n\nWhen using Bitbucket APIs with a Repository Access Token, the token will be\ntreated as the \"user\" in the Bitbucket UI and Bitbucket logs. This includes\nusing the Repository Access Token to leave a comment on a pull request, push a\ncommit, or merge a pull request. The Bitbucket UI and API responses will show\nthe Repository Access Token as a user. This user uses the Repository Access\nToken name and a custom icon to differentiate it from a regular user in the UI.\n\nFor details on creating, managing, and using Repository Access Tokens, visit\n[Repository Access Tokens](https://support.atlassian.com/bitbucket-cloud/docs/repository-access-tokens/).\n\n#### Considerations for using Repository Access Tokens\n\n* After creation, a Repository Access Token can't be viewed or modified. The\ntoken's name, created date, last accessed date, and scopes are visible on the\nRepository Access Token page.\n* Repository Access Tokens can only be granted a limited set of Bitbucket's \npermission scopes.\n* Provided you set the correct permission scopes, you can use a Repository Access\nToken to clone (`repository`) and push (`repository:write`) code to the\ntoken's corresponding repository.\n* You can't use a Repository Access Token to log into the Bitbucket website.\n* Repository Access Tokens don't require two-step verification.\n* You can set permission scopes (specific access rights) for each Repository\nAccess Token.\n* You can't use a Repository Access Token to manipulate or query repository\npermissions.\n* Repository Access Tokens will not be listed in any repository or workspace \npermission API response.\n* Repository Access Tokens are deactivated when a repository is transferred or deleted.\n* Any content created by the Repository Access Token will persist after the\nRepository Access Token has been revoked.\n\n#### Available permissions scopes\n\nThe available scopes for Repository Access Tokens are:\n\n- [repository](#repository)\n- [repository:write](#repository-write)\n- [repository:admin](#repository-admin)\n- [repository:delete](#repository-delete)\n- [pullrequest](#pullrequest)\n- [pullrequest:write](#pullrequest-write)\n- [webhook](#webhook)\n- [pipeline](#pipeline)\n- [pipeline:write](#pipeline-write)\n- [pipeline:variable](#pipeline-variable)\n- [runner](#runner)\n- [runner:write](#runner-write)\n\nThere are some APIs which are inaccessible for Repository Access Tokens, these are: \n\n* [Add a repository deploy key](/cloud/bitbucket/rest/api-group-deployments/#api-repositories-workspace-repo-slug-deploy-keys-post)\n* [Update a repository deploy key](/cloud/bitbucket/rest/api-group-deployments/#api-repositories-workspace-repo-slug-deploy-keys-key-id-put)\n* [Delete a repository deploy key](/cloud/bitbucket/rest/api-group-deployments/#api-repositories-workspace-repo-slug-deploy-keys-key-id-delete)\n\n### App passwords\n\nApp passwords allow users to make API calls to their Bitbucket account through apps such as Sourcetree.\n\nSome important points about app passwords:\n\n* You cannot view an app password or adjust permissions after you create the app password. Because app passwords are encrypted on our database and cannot be viewed by anyone. They are essentially designed to be disposable. If you need to change the scopes or lost the password just create a new one.\n* You cannot use them to log into your Bitbucket account.\n* You cannot use app passwords to manage team actions.\n\n App passwords are tied to an individual account's credentials and should not be shared. If you're sharing your app password you're essentially giving direct, authenticated, access to everything that password has been scoped to do with the Bitbucket API's.\n\n* You can use them for API call authentication, even if you don't have two-step verification enabled.\n* You can set permission scopes (specific access rights) for each app password.\n\n#### Create an app password\n\nTo create an app password:\n\n1. Select **Avatar > Bitbucket settings**.\n2. [Click **App passwords** in the Access management section.](https://bitbucket.org/account/settings/app-passwords/)\n3. Click **Create app password**.\n4. Give the app password a name related to the application that will use the password.\n5. Select the specific access and permissions you want this application password to have.\n6. Copy the generated password and either record or paste it into the application you want to give access. The password is only displayed this one time.\n\nThat's all there is to creating an app password. See your applications documentation for how to apply the app password for a specific application." + "body": "\nThe purpose of this section is to describe how to authenticate when making API calls using the Bitbucket REST API.\n\n-----\n\n* [Basic auth](#basic-auth)\n* [Access Tokens](#access-tokens)\n * [Repository Access Tokens](#repository-access-tokens)\n * [Project Access Tokens](#project-access-tokens)\n * [Workspace Access Tokens](#workspace-access-tokens)\n* [App passwords](#app-passwords)\n* [OAuth 2.0](#oauth-2-0)\n * [Making requests](#making-requests)\n * [Repository cloning](#repository-cloning)\n * [Refresh tokens](#refresh-tokens)\n* [Bitbucket OAuth 2.0 Scopes](#bitbucket-oauth-2-0-scopes)\n* [Forge App Scopes](#forge-app-scopes)\n\n---\n\n### Basic auth\n\nBasic HTTP Authentication as per [RFC-2617](https://tools.ietf.org/html/rfc2617) (Digest not supported).\nNote that Basic Auth is available only with username and [app password](https://bitbucket.org/account/settings/app-passwords/) as credentials.\n\n### Access Tokens\n\nAccess Tokens are passwords (or tokens) that provide access to a _single_ repository, project or workspace.\nThese tokens can authenticate with Bitbucket APIs for scripting, CI/CD tools, Bitbucket Cloud-connected apps,\nand Bitbucket Cloud integrations.\n\nAccess Tokens are linked to a repository, project, or workspace, not a user account.\nThe level of access provided by the token is set when a repository, or workspace admin creates it,\nby setting permission scopes.\n\nThere are three types of Access Token:\n\n* **Repository Access Tokens** can connect to a single repository, preventing them from accessing any other repositories or workspaces.\n* **Project Access Tokens** can connect to a single project, providing access to any repositories within the project.\n* **Workspace Access Tokens** can connect to a single workspace and have access to any projects and repositories within that workspace.\n\nWhen using Bitbucket APIs with an Access Token, the token will be treated as the \"user\" in the\nBitbucket UI and Bitbucket logs. This includes when using the Access Token to leave a comment on a pull request,\npush a commit, or merge a pull request. The Bitbucket UI and API responses will show the\nRepository/Project/Workspace Access Token as a user. The username shown in the Bitbucket UI is the Access\nToken _name_, and a custom icon is used to differentiate it from a regular user in the UI.\n\n#### Considerations for using Access Tokens\n\n* After creation, an Access Token can't be viewed or modified. The token's name, created date,\nlast accessed date, and scopes are visible on the repository, project, or workspace **Access Tokens** page.\n* Access Tokens can access a limited set of Bitbucket's permission scopes.\n* Provided you set the correct permission scopes, you can use an Access Token to clone (`repository`)\nand push (`repository:write`) code to the token's repository or the repositories the token can access.\n* You can't use an Access Token to log into the Bitbucket website.\n* Access Tokens don't require two-step verification.\n* You can set permission scopes (specific access rights) for each Access Token.\n* You can't use an Access Token to manipulate or query repository, project, or workspace permissions.\n* Access Tokens are not listed in any repository or workspace permission API response.\n* Access Tokens are deactivated when deleting the resource tied to it (a repository, project, or workspace).\nRepository Access Tokens are also revoked when transferring the repository to another workspace.\n* Any content created by the Access Token will persist after the Access Token has been revoked.\n* Access Tokens can interact with branch restriction APIs, but the token can't be configured as a user with merge access when using branch restrictions.\n\nThere are some APIs which are inaccessible for Access Tokens, these are:\n\n* [Add a repository deploy key](/cloud/bitbucket/rest/api-group-deployments/#api-repositories-workspace-repo-slug-deploy-keys-post)\n* [Update a repository deploy key](/cloud/bitbucket/rest/api-group-deployments/#api-repositories-workspace-repo-slug-deploy-keys-key-id-put)\n* [Delete a repository deploy key](/cloud/bitbucket/rest/api-group-deployments/#api-repositories-workspace-repo-slug-deploy-keys-key-id-delete)\n\n#### Repository Access Tokens\n\nFor details on creating, managing, and using Repository Access Tokens, visit\n[Repository Access Tokens](https://support.atlassian.com/bitbucket-cloud/docs/repository-access-tokens/).\n\nThe available scopes for Repository Access Tokens are:\n\n- [`repository`](#repository)\n- [`repository:write`](#repository-write)\n- [`repository:admin`](#repository-admin)\n- [`repository:delete`](#repository-delete)\n- [`pullrequest`](#pullrequest)\n- [`pullrequest:write`](#pullrequest-write)\n- [`webhook`](#webhook)\n- [`pipeline`](#pipeline)\n- [`pipeline:write`](#pipeline-write)\n- [`pipeline:variable`](#pipeline-variable)\n- [`runner`](#runner)\n- [`runner:write`](#runner-write)\n\n#### Project Access Tokens\n\nFor details on creating, managing, and using Project Access Tokens, visit\n[Project Access Tokens](https://support.atlassian.com/bitbucket-cloud/docs/project-access-tokens/).\n\nThe available scopes for Project Access Tokens are:\n\n- [`project`](#project)\n- [`repository`](#repository)\n- [`repository:write`](#repository-write)\n- [`repository:admin`](#repository-admin)\n- [`repository:delete`](#repository-delete)\n- [`pullrequest`](#pullrequest)\n- [`pullrequest:write`](#pullrequest-write)\n- [`webhook`](#webhook)\n- [`pipeline`](#pipeline)\n- [`pipeline:write`](#pipeline-write)\n- [`pipeline:variable`](#pipeline-variable)\n- [`runner`](#runner)\n- [`runner:write`](#runner-write)\n\n#### Workspace Access Tokens\n\nFor details on creating, managing, and using Workspace Access Tokens, visit\n[Workspace Access Tokens](https://support.atlassian.com/bitbucket-cloud/docs/workspace-access-tokens/).\n\nThe available scopes for Workspace Access Tokens are:\n\n- [`project`](#project)\n- [`project:admin`](#project-admin)\n- [`repository`](#repository)\n- [`repository:write`](#repository-write)\n- [`repository:admin`](#repository-admin)\n- [`repository:delete`](#repository-delete)\n- [`pullrequest`](#pullrequest)\n- [`pullrequest:write`](#pullrequest-write)\n- [`webhook`](#webhook)\n- [`account`](#account)\n- [`pipeline`](#pipeline)\n- [`pipeline:write`](#pipeline-write)\n- [`pipeline:variable`](#pipeline-variable)\n- [`runner`](#runner)\n- [`runner:write`](#runner-write)\n\n### App passwords\n\nApp passwords allow users to make API calls to their Bitbucket account through apps such as Sourcetree.\n\nSome important points about app passwords:\n\n* You cannot view an app password or adjust permissions after you create the app password. Because app passwords are encrypted on our database and cannot be viewed by anyone. They are essentially designed to be disposable. If you need to change the scopes or lost the password just create a new one.\n* You cannot use them to log into your Bitbucket account.\n* You cannot use app passwords to manage team actions.\n\n App passwords are tied to an individual account's credentials and should not be shared. If you're sharing your app password you're essentially giving direct, authenticated, access to everything that password has been scoped to do with the Bitbucket API's.\n\n* You can use them for API call authentication, even if you don't have two-step verification enabled.\n* You can set permission scopes (specific access rights) for each app password.\n\nFor details on creating, managing, and using App passwords, visit\n[App passwords](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/).\n\n### OAuth 2.0\n\nOur OAuth 2 implementation is merged in with our existing OAuth 1 in\nsuch a way that existing OAuth 1 consumers automatically become\nvalid OAuth 2 clients. The only thing you need to do is edit your\nexisting consumer and configure a callback URL.\n\nOnce that is in place, you'll have the following 2 URLs:\n\n https://bitbucket.org/site/oauth2/authorize\n https://bitbucket.org/site/oauth2/access_token\n\nFor obtaining access/bearer tokens, we support three of RFC-6749's grant\nflows, plus a custom Bitbucket flow for exchanging JWT tokens for access tokens.\nNote that Resource Owner Password Credentials Grant (4.3) is no longer supported.\n\n\n#### 1. Authorization Code Grant (4.1)\n\nThe full-blown 3-LO flow. Request authorization from the end user by\nsending their browser to:\n\n https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=code\n\nThe callback includes the `?code={}` query parameter that you can swap\nfor an access token:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=authorization_code -d code={code}\n\n\n#### 2. Implicit Grant (4.2)\n\nThis flow is useful for browser-based add-ons that operate without server-side backends.\n\nRequest the end user for authorization by directing the browser to:\n\n https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=token\n\nThat will redirect to your preconfigured callback URL with a fragment\ncontaining the access token\n(`#access_token={token}&token_type=bearer`) where your page's js can\npull it out of the URL.\n\n\n#### 3. Client Credentials Grant (4.4)\n\nSomewhat like our existing \"2-LO\" flow for OAuth 1. Obtain an access\ntoken that represents not an end user, but the owner of the\nclient/consumer:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=client_credentials\n\n\n#### 4. Bitbucket Cloud JWT Grant (urn:bitbucket:oauth2:jwt)\n\nIf your Atlassian Connect add-on uses JWT authentication, you can swap a\nJWT for an OAuth access token. The resulting access token represents the\naccount for which the add-on is installed.\n\nMake sure you send the JWT token in the Authorization request header\nusing the \"JWT\" scheme (case sensitive). Note that this custom scheme\nmakes this different from HTTP Basic Auth (and so you cannot use \"curl\n-u\").\n\n $ curl -X POST -H \"Authorization: JWT {jwt_token}\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=urn:bitbucket:oauth2:jwt\n\n\n#### Making Requests\n\nOnce you have an access token, as per RFC-6750, you can use it in a request in any of\nthe following ways (in decreasing order of desirability):\n\n1. Send it in a request header: `Authorization: Bearer {access_token}`\n2. Include it in a (application/x-www-form-urlencoded) POST body as `access_token={access_token}`\n3. Put it in the query string of a non-POST: `?access_token={access_token}`\n\n\n#### Repository Cloning\n\nSince add-ons will not be able to upload their own SSH keys to clone\nwith, access tokens can be used as Basic HTTP Auth credentials to\nclone securely over HTTPS. This is much like GitHub, yet slightly\ndifferent:\n\n $ git clone https://x-token-auth:{access_token}@bitbucket.org/user/repo.git\n\nThe literal string `x-token-auth` as a substitute for username is\nrequired (note the difference with GitHub where the actual token is in\nthe username field).\n\n\n#### Refresh Tokens\n\nOur access tokens expire in one hour. When this happens you'll get 401\nresponses.\n\nMost access tokens grant responses (Implicit and JWT excluded). Therefore, you should include a\nrefresh token that can then be used to generate a new access token,\nwithout the need for end user participation:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=refresh_token -d refresh_token={refresh_token}\n\n\n### Bitbucket OAuth 2.0 scopes\n\nBitbucket's API applies a number of privilege scopes to endpoints. In order to access an endpoint, a request will need to have the necessary scopes.\n\nOAuth 2.0 Scopes are applicable for OAuth 2, Access Tokens, and App passwords auth mechanisms as well as Bitbucket Connect apps.\n\nScopes are declared in the descriptor as a list of strings, with each string being the name of a unique scope.\n\nA descriptor lacking the `scopes` element is implicitly assumed to require all scopes and as a result, Bitbucket will require end users authorizing/installing the add-on\nto explicitly accept all scopes.\n\nOur best practice suggests you add only the scopes your add-on needs, but no more than it needs.\n\nInvalid scope strings will cause the descriptor to be rejected and the installation to fail.\n\nThe available scopes are:\n\n- [project](#project)\n- [project:write](#project-write)\n- [project:admin](#project-admin)\n- [repository](#repository)\n- [repository:write](#repository-write)\n- [repository:admin](#repository-admin)\n- [repository:delete](#repository-delete)\n- [pullrequest](#pullrequest)\n- [pullrequest:write](#pullrequest-write)\n- [issue](#issue)\n- [issue:write](#issue-write)\n- [wiki](#wiki)\n- [webhook](#webhook)\n- [snippet](#snippet)\n- [snippet:write](#snippet-write)\n- [email](#email)\n- [account](#account)\n- [account:write](#account-write)\n- [pipeline](#pipeline)\n- [pipeline:write](#pipeline-write)\n- [pipeline:variable](#pipeline-variable)\n- [runner](#runner)\n- [runner:write](#runner-write)\n\n#### project\n\nProvides access to view the project or projects.\nThis scope implies the [`repository`](#repository) scope, giving read access to all the repositories in a project or projects.\n\n#### project:write\n\nThis scope is deprecated, and has been made obsolete by `project:admin`. Please see the deprecation notice [here](/cloud/bitbucket/deprecation-notice-project-write-scope).\n\n#### project:admin\n\nProvides admin access to a project or projects. No distinction is made between public and private projects. This scope doesn't implicitly grant the [`project`](#project) scope or the [`repository:write`](#repository-write) scope on any repositories under the project. It gives access to the admin features of a project only, not direct access to its repositories' contents.\n\n* ability to create the project\n* ability to update the project\n* ability to delete the project\n\n#### repository\n\nProvides read access to a repository or repositories.\nNote that this scope does not give access to a repository's pull requests.\n\n* access to the repo's source code\n* clone over HTTPS\n* access the file browsing API\n* download zip archives of the repo's contents\n* the ability to view and use the issue tracker on any repo (created issues, comment, vote, etc)\n* the ability to view and use the wiki on any repo (create/edit pages)\n\n#### repository:write\n\nProvides write (not admin) access to a repository or repositories. No distinction is made between public and private repositories. This scope implicitly grants the [`repository`](#repository) scope, which does not need to be requested separately.\nThis scope alone does not give access to the pull requests API.\n\n* push access over HTTPS\n* fork repos\n\n#### repository:admin\n\nProvides admin access to a repository or repositories. No distinction is made between public and private repositories. This scope doesn't implicitly grant the [`repository`](#repository) or the [`repository:write`](#repository-write) scopes. It gives access to the admin features of a repo only, not direct access to its contents. This scope can be used or misused to grant read access to other users, who can then clone the repo, but users that need to read and write source code would also request explicit read or write.\nThis scope comes with access to the following functionality:\n\n* View and manipulate committer mappings\n* List and edit deploy keys\n* Ability to delete the repo\n* View and edit repo permissions\n* View and edit branch permissions\n* Import and export the issue tracker\n* Enable and disable the issue tracker\n* List and edit issue tracker version, milestones and components\n* Enable and disable the wiki\n* List and edit default reviewers\n* List and edit repo links (Jira/Bamboo/Custom)\n* List and edit the repository webhooks\n* Initiate a repo ownership transfer\n\n#### repository:delete\n\nProvides access to delete a repository or repositories.\n\n#### pullrequest\n\nProvides read access to pull requests.\nThis scope implies the [`repository`](#repository) scope, giving read access to the pull request's destination repository.\n\n* see and list pull requests\n* create and resolve tasks\n* comment on pull requests\n\n#### pullrequest:write\n\nImplicitly grants the [`pullrequest`](#pullrequest) scope and adds the ability to create, merge and decline pull requests.\nThis scope also implicitly grants the [`repository:write`](#repository-write) scope, giving write access to the pull request's destination repository. This is necessary to allow merging.\n\n* merge pull requests\n* decline pull requests\n* create pull requests\n* approve pull requests\n\n#### issue\n\nAbility to interact with issue trackers the way non-repo members can.\nThis scope doesn't implicitly grant any other scopes and doesn't give implicit access to the repository.\n\n* view, list and search issues\n* create new issues\n* comment on issues\n* watch issues\n* vote for issues\n\n#### issue:write\n\nThis scope implicitly grants the [`issue`](#issue) scope and adds the ability to transition and delete issues.\nThis scope doesn't implicitly grant any other scopes and doesn't give implicit access to the repository.\n\n* transition issues\n* delete issues\n\n#### wiki\n\nProvides access to wikis. This scope provides both read and write access (wikis are always editable by anyone with access to them).\nThis scope doesn't implicitly grant any other scopes and doesn't give implicit access to the repository.\n\n* view wikis\n* create pages\n* edit pages\n* push to wikis\n* clone wikis\n\n#### webhook\n\nGives access to webhooks. This scope is required for any webhook-related operation.\n\nThis scope gives read access to existing webhook subscriptions on all\nresources the authorization mechanism can access, without needing further scopes.\nFor example:\n\n- A client can list all existing webhook subscriptions on a repository. The [`repository`](#repository) scope is not required.\n- Existing webhook subscriptions for the issue tracker on a repo can be retrieved without the [`issue`](#issue) scope. All that is required is the `webhook` scope.\n\nTo create webhooks, the client will need read access to the resource. Such as: for [`issue:created`](#issue-created), the client will need to\nhave both the `webhook` and the [`issue`](#issue) scope.\n\n* list webhook subscriptions on any accessible repository, user, team, or snippet\n* create/update/delete webhook subscriptions.\n\n#### snippet\n\nProvides read access to snippets.\nNo distinction is made between public and private snippets (public snippets are accessible without any form of authentication).\n\n* view any snippet\n* create snippet comments\n\n#### snippet:write\n\nProvides write access to snippets.\nNo distinction is made between public and private snippets (public snippets are accessible without any form of authentication).\nThis scope implicitly grants the [`snippet`](#snippet) scope which does not need to be requested separately.\n\n* create snippets\n* edit snippets\n* delete snippets\n\n#### email\n\nAbility to see the user's primary email address. This should make it easier to use Bitbucket Cloud as a login provider for apps or external applications.\n\n#### account\n\nWhen used for:\n* **user-related APIs** — Gives read-only access to the user's account information.\nNote that this doesn't include any ability to change any of the data. This scope allows you to view the user's:\n * email addresses\n * language\n * location\n * website\n * full name\n * SSH keys\n * user groups\n* **workspace-related APIs** — Grants access to view the workspace's:\n * users\n * user permissions\n * projects\n\n#### account:write\n\nAbility to change properties on the user's account.\n\n* delete the authorizing user's account\n* manage the user's groups\n* change a user's email addresses\n* change username, display name and avatar\n\n#### pipeline\n\nGives read-only access to pipelines, steps, deployment environments and variables.\n\n#### pipeline:write\n\nGives write access to pipelines. This scope allows a user to:\n* Stop pipelines\n* Rerun failed pipelines\n* Resume halted pipelines\n* Trigger manual pipelines.\n\nThis scope is not needed to trigger a build using a push. Performing a `git push` (or equivalent actions) will trigger the build. The token doing the push only needs the [`repository:write`](#repository-write) scope.\n\nThis doesn't give write access to create variables.\n\n#### pipeline:variable\n\nGives write access to create variables in pipelines at the various levels:\n* Workspace\n* Repository\n* Deployment\n\n#### runner\n\nGives read-only access to pipelines runners setup against a workspace or repository.\n\n#### runner:write\n\nGives write access to create/edit/disable/delete pipelines runners setup against a workspace or repository.\n### Forge app scopes\n\nIn order for a Forge app integration to access Bitbucket API endpoints, it needs to include certain privilege scopes in the app manifest. These are different from Bitbucket OAuth 2.0 scopes.\n\nUnlike OAuth 2.0 scopes, Forge app scopes do not implicitly grant other scopes, for example, `write:repository:bitbucket` does not implicitly grant `read:repository:bitbucket`.\n\nOnly a subset of Bitbucket API endpoints are currently available for Forge app integrations. These will be labeled with Forge app scopes.\n\nOur best practice suggests you only add the scopes your app needs, but no more than it needs.\n\nThe available scopes are:\n\n- [`read:repository:bitbucket`](#read-repository-bitbucket)\n- [`write:repository:bitbucket`](#write-repository-bitbucket)\n- [`admin:repository:bitbucket`](#admin-repository-bitbucket)\n- [`delete:repository:bitbucket`](#delete-repository-bitbucket)\n- [`read:pullrequest:bitbucket`](#read-pullrequest-bitbucket)\n- [`write:pullrequest:bitbucket`](#write-pullrequest-bitbucket)\n- [`read:project:bitbucket`](#read-project-bitbucket)\n- [`admin:project:bitbucket`](#admin-project-bitbucket)\n- [`read:workspace:bitbucket`](#read-workspace-bitbucket)\n- [`read:user:bitbucket`](#read-user-bitbucket)\n- [`read:pipeline:bitbucket`](#read-pipeline-bitbucket)\n- [`write:pipeline:bitbucket`](#write-pipeline-bitbucket)\n- [`admin:pipeline:bitbucket`](#admin-pipeline-bitbucket)\n- [`read:runner:bitbucket`](#read-runner-bitbucket)\n- [`write:runner:bitbucket`](#write-runner-bitbucket)\n\n#### read:repository:bitbucket\n\nAllows viewing of repository data. Note that this scope does not give access to a repository's pull requests.\n* access to the repository's source code\n* access the file browsing API\n* access to certain repository configurations such as branching model, default reviewers, etc.\n\n#### write:repository:bitbucket\n\nAllows modification of repository data. No distinction is made between public and private repositories. This scope does not imply the `read:repository:bitbucket` scope, so you need to request that separately if required. This scope alone does not give access to the pull request API.\n* update/delete source, branches, tags, etc.\n* fork repositories\n\n#### admin:repository:bitbucket\n\nAllows admin activities on repositories. No distinction is made between public and private repositories. This scope does not implicitly grant the `read:repository:bitbucket` or the `write:repository:bitbucket` scopes. It gives access to the admin features of a repository only, not direct access to its contents. This scope does not allow modification of repository permissions. This scope comes with access to the following functionality:\n* create repository\n* view repository permissions\n* view and edit branch restrictions\n* edit branching model settings\n* edit default reviewers\n* view and edit inheritance state for repository settings\n\n#### delete:repository:bitbucket\nAllows deletion of repositories.\n\n#### read:pullrequest:bitbucket\nAllows viewing of pull requests, plus the ability to comment on pull requests.\n\nThis scope does not imply the `read:repository:bitbucket` scope. With this scope, you could retrieve some data specific to the source/destination repositories of a pull request using pull request endpoints, but it does not give access to repository API endpoints.\n\n#### write:pullrequest:bitbucket\nAllows the ability to create, update, approve, decline, and merge pull requests.\n\nThis scope does not imply the `write:repository:bitbucket` scope.\n\n#### read:project:bitbucket\nAllows viewing of project and project permission data.\n\n#### admin:project:bitbucket\nAllows the ability to create, update, and delete project. No distinction is made between public and private projects.\n\nThis scope does not implicitly grant the `read:project:bitbucket` scope or any repository scopes. It gives access to the admin features of a project only, not direct access to its repositories' contents.\n\n#### read:workspace:bitbucket\nAllows viewing of workspace and workspace permission data.\n\n#### read:user:bitbucket\nAllows viewing of user data. This scope is typically required for permission related endpoints.\n\n#### read:pipeline:bitbucket\nAllows read access to all pipeline information (pipelines, steps, caches, artifacts, logs, tests, code-insights).\n\n#### write:pipeline:bitbucket\nAllows running pipelines (i.e., start/stop/create pipeline) and uploading tests/code-insights.\n\nThis scope does not imply the `read:pipeline:bitbucket` scope.\n\n#### admin:pipeline:bitbucket\nAllows admin activities, such as creating pipeline variables.\n\nThis scope does not implicitly grant the `read:pipeline:bitbucket` or the `write:pipeline:bitbucket` scopes.\n\n#### read:runner:bitbucket\nAllows viewing of runners information.\n\n#### write:runner:bitbucket\nAllows runners management.\n\nThis scope does not imply the `read:runners:bitbucket` scope.\n" }, { "anchor": "filtering", @@ -19048,6 +24446,17 @@ ], "components": { "requestBodies": { + "bitbucket.apps.permissions.serializers.ProjectPermissionUpdateSchema": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bitbucket.apps.permissions.serializers.ProjectPermissionUpdateSchema" + } + } + }, + "description": "The permission to grant", + "required": true + }, "application_property": { "content": { "application/json": { @@ -19081,6 +24490,17 @@ "description": "The new snippet object.", "required": true }, + "bitbucket.apps.permissions.serializers.RepoPermissionUpdateSchema": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/bitbucket.apps.permissions.serializers.RepoPermissionUpdateSchema" + } + } + }, + "description": "The permission to grant", + "required": true + }, "pipeline_variable2": { "content": { "application/json": { @@ -19132,13 +24552,13 @@ "pipeline:variable": "Access your repositories' build pipelines and configure their variables", "runner": "Access your workspaces/repositories' runners", "runner:write": "Access and edit your workspaces/repositories' runners", - "issue": "Read your repositories' issues", - "issue:write": "Read and modify your repositories' issues", "pullrequest": "Read your repositories and their pull requests", "pullrequest:write": "Read and modify your repositories and their pull requests", + "webhook": "Read and modify your repositories' webhooks", + "issue": "Read your repositories' issues", + "issue:write": "Read and modify your repositories' issues", "snippet": "Read your snippets", "snippet:write": "Read and modify your snippets", - "webhook": "Read and modify your repositories' webhooks", "wiki": "Read and modify your repositories' wikis" } } @@ -19152,6 +24572,128 @@ } }, "schemas": { + "A_pull_request_task": { + "allOf": [ + { + "$ref": "#/components/schemas/task" + }, + { + "type": "object", + "properties": { + "links": { + "type": "object", + "properties": { + "html": { + "type": "object", + "title": "Link", + "description": "A link to a resource related to this object.", + "properties": { + "href": { + "type": "string", + "format": "uri" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "self": { + "type": "object", + "title": "Link", + "description": "A link to a resource related to this object.", + "properties": { + "href": { + "type": "string", + "format": "uri" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "title": "Pull Request Task", + "description": "A pull request task." + }, + "A_pullrequest_comment_task": { + "allOf": [ + { + "$ref": "#/components/schemas/A_pull_request_task" + }, + { + "type": "object", + "properties": { + "comment": { + "$ref": "#/components/schemas/comment" + } + }, + "additionalProperties": false + } + ], + "title": "Pull Request Comment Task", + "description": "A pullrequest comment task" + }, + "A_pullrequest_task_create": { + "type": "object", + "title": "Pull Request Task Create", + "description": "A pullrequest task create", + "properties": { + "comment": { + "$ref": "#/components/schemas/comment" + }, + "content": { + "type": "object", + "title": "Task Raw Content", + "description": "task raw content", + "properties": { + "raw": { + "type": "string", + "description": "The task contents" + } + }, + "required": ["raw"], + "additionalProperties": false + }, + "pending": { + "type": "boolean" + } + }, + "required": ["content"], + "additionalProperties": false + }, + "A_pullrequest_task_update": { + "type": "object", + "title": "Pull Request Task Update", + "description": "A pullrequest task update", + "properties": { + "content": { + "type": "object", + "title": "Task Raw Content", + "description": "task raw content", + "properties": { + "raw": { + "type": "string", + "description": "The task contents" + } + }, + "required": ["raw"], + "additionalProperties": false + }, + "state": { + "type": "string", + "enum": ["RESOLVED", "UNRESOLVED"] + } + }, + "additionalProperties": false + }, "account": { "allOf": [ { @@ -19314,6 +24856,28 @@ "title": "Base Commit", "description": "The common base type for both repository and snippet commits." }, + "bitbucket.apps.permissions.serializers.ProjectPermissionUpdateSchema": { + "type": "object", + "properties": { + "permission": { + "type": "string", + "enum": ["read", "write", "create-repo", "admin"] + } + }, + "required": ["permission"], + "additionalProperties": false + }, + "bitbucket.apps.permissions.serializers.RepoPermissionUpdateSchema": { + "type": "object", + "properties": { + "permission": { + "type": "string", + "enum": ["read", "write", "admin"] + } + }, + "required": ["permission"], + "additionalProperties": false + }, "branch": { "allOf": [ { @@ -19672,6 +25236,26 @@ "title": "Comment", "description": "The base type for all comments. This type should be considered abstract. Each of the \"commentable\" resources defines its own subtypes (e.g. `issue_comment`)." }, + "comment_resolution": { + "type": "object", + "title": "Comment Resolution", + "description": "The resolution object for a Comment.", + "properties": { + "created_on": { + "type": "string", + "description": "The ISO8601 timestamp the resolution was created.", + "format": "date-time" + }, + "type": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/account" + } + }, + "required": ["type"], + "additionalProperties": true + }, "commit": { "allOf": [ { @@ -19809,7 +25393,7 @@ "state": { "type": "string", "description": "Provides some indication of the status of this commit", - "enum": ["INPROGRESS", "STOPPED", "FAILED", "SUCCESSFUL"] + "enum": ["STOPPED", "INPROGRESS", "FAILED", "SUCCESSFUL"] }, "updated_on": { "type": "string", @@ -20675,31 +26259,33 @@ "type": "string", "description": "The event identifier.", "enum": [ - "issue:created", - "repo:commit_status_updated", - "pullrequest:comment_created", - "pullrequest:created", + "issue:comment_created", + "repo:push", + "repo:transfer", + "pullrequest:changes_request_removed", + "pullrequest:comment_updated", "pullrequest:unapproved", - "repo:commit_status_created", + "pullrequest:comment_created", "repo:created", "pullrequest:changes_request_created", - "pullrequest:comment_updated", - "pullrequest:changes_request_removed", - "pullrequest:comment_deleted", "repo:imported", - "repo:commit_comment_created", + "repo:fork", "project:updated", - "repo:updated", + "issue:created", + "repo:deleted", "issue:updated", + "pullrequest:comment_reopened", + "pullrequest:updated", + "repo:commit_comment_created", + "pullrequest:created", + "pullrequest:approved", "pullrequest:rejected", "pullrequest:fulfilled", - "issue:comment_created", - "repo:fork", - "pullrequest:updated", - "repo:push", - "pullrequest:approved", - "repo:deleted", - "repo:transfer" + "pullrequest:comment_resolved", + "repo:commit_status_updated", + "repo:updated", + "pullrequest:comment_deleted", + "repo:commit_status_created" ] }, "label": { @@ -20869,6 +26455,7 @@ "state": { "type": "string", "enum": [ + "submitted", "new", "open", "resolved", @@ -21988,6 +27575,52 @@ } ] }, + "paginated_project_group_permissions": { + "title": "Paginated Project Group Permissions", + "description": "A paginated list of project group permissions.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/project_group_permission" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_project_user_permissions": { + "title": "Paginated Project User Permissions", + "description": "A paginated list of project user permissions.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/project_user_permission" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, "paginated_projects": { "title": "Paginated Projects", "description": "A paginated list of projects", @@ -22308,6 +27941,29 @@ } ] }, + "paginated_tasks": { + "title": "Paginated Tasks", + "description": "A paginated list of tasks.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/A_pullrequest_comment_task" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, "paginated_treeentries": { "title": "Paginated Tree Entry", "description": "A paginated list of commit_file and/or commit_directory objects.", @@ -22502,6 +28158,14 @@ "uuid": { "type": "string", "description": "The UUID identifying the pipeline." + }, + "variables": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/pipeline_variable" + }, + "description": "The variables for the pipeline." } } } @@ -22546,6 +28210,10 @@ "type": "integer", "description": "The size of the file containing the archive of the cache." }, + "key_hash": { + "type": "string", + "description": "The key hash of the cache version." + }, "name": { "type": "string", "description": "The name of the cache." @@ -22738,17 +28406,14 @@ }, "cron_pattern": { "type": "string", - "description": "The cron expression that the schedule applies." + "description": "The cron expression with second precision (7 fields) that the schedule applies. For example, for expression: 0 0 12 * * ? *, will execute at 12pm UTC every day." }, "enabled": { "type": "boolean", "description": "Whether the schedule is enabled." }, - "selector": { - "$ref": "#/components/schemas/pipeline_selector" - }, "target": { - "$ref": "#/components/schemas/pipeline_target" + "$ref": "#/components/schemas/pipeline_ref_target" }, "updated_on": { "type": "string", @@ -22813,6 +28478,66 @@ } ] }, + "pipeline_schedule_post_request_body": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "cron_pattern": { + "type": "string", + "description": "The cron expression with second precision (7 fields) that the schedule applies. For example, for expression: 0 0 12 * * ? *, will execute at 12pm UTC every day." + }, + "enabled": { + "type": "boolean", + "description": "Whether the schedule is enabled." + }, + "target": { + "type": "object", + "description": "The target on which the schedule will be executed.", + "properties": { + "ref_name": { + "type": "string", + "description": "The name of the reference." + }, + "ref_type": { + "type": "string", + "description": "The type of reference (branch only).", + "enum": ["branch"] + }, + "selector": { + "$ref": "#/components/schemas/pipeline_selector" + } + }, + "required": ["selector", "ref_name", "ref_type"] + } + }, + "required": ["target", "cron_pattern"] + } + ], + "title": "Request body for Pipeline Schedule POST request" + }, + "pipeline_schedule_put_request_body": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the schedule is enabled." + } + } + } + ], + "title": "Request body for Pipeline Schedule PUT request" + }, "pipeline_selector": { "title": "Pipeline Selector", "description": "A representation of the selector that was used to identify the pipeline in the YML file.", @@ -23785,6 +29510,92 @@ "title": "Project Deploy Key", "description": "Represents deploy key for a project." }, + "project_group_permission": { + "type": "object", + "title": "Project Group Permission", + "description": "A group's permission for a given project.", + "properties": { + "group": { + "$ref": "#/components/schemas/group" + }, + "links": { + "type": "object", + "properties": { + "self": { + "type": "object", + "title": "Link", + "description": "A link to a resource related to this object.", + "properties": { + "href": { + "type": "string", + "format": "uri" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "permission": { + "type": "string", + "enum": ["read", "write", "create-repo", "admin", "none"] + }, + "project": { + "$ref": "#/components/schemas/project" + }, + "type": { + "type": "string" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "project_user_permission": { + "type": "object", + "title": "Project User Permission", + "description": "A user's direct permission for a given project.", + "properties": { + "links": { + "type": "object", + "properties": { + "self": { + "type": "object", + "title": "Link", + "description": "A link to a resource related to this object.", + "properties": { + "href": { + "type": "string", + "format": "uri" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "permission": { + "type": "string", + "enum": ["read", "write", "create-repo", "admin", "none"] + }, + "project": { + "$ref": "#/components/schemas/project" + }, + "type": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/user" + } + }, + "required": ["type"], + "additionalProperties": true + }, "pullrequest": { "allOf": [ { @@ -24126,8 +29937,14 @@ { "type": "object", "properties": { + "pending": { + "type": "boolean" + }, "pullrequest": { "$ref": "#/components/schemas/pullrequest" + }, + "resolution": { + "$ref": "#/components/schemas/comment_resolution" } }, "additionalProperties": true @@ -24478,10 +30295,12 @@ "description": "The concatenation of the repository owner's username and the slugified name, e.g. \"evzijst/interruptingcow\". This is the same string used in Bitbucket URLs." }, "has_issues": { - "type": "boolean" + "type": "boolean", + "description": "\nThe issue tracker for this repository is enabled. Issue Tracker\nfeatures are not supported for repositories in workspaces\nadministered through admin.atlassian.com.\n" }, "has_wiki": { - "type": "boolean" + "type": "boolean", + "description": "\nThe wiki for this repository is enabled. Wiki\nfeatures are not supported for repositories in workspaces\nadministered through admin.atlassian.com.\n" }, "is_private": { "type": "boolean" @@ -25245,6 +31064,63 @@ "title": "Tag", "description": "A tag object, representing a tag in a repository." }, + "task": { + "type": "object", + "title": "Task", + "description": "A task object.", + "properties": { + "content": { + "type": "object", + "properties": { + "html": { + "type": "string", + "description": "The user's content rendered as HTML." + }, + "markup": { + "type": "string", + "description": "The type of markup language the raw content is to be interpreted in.", + "enum": ["markdown", "creole", "plaintext"] + }, + "raw": { + "type": "string", + "description": "The text as it was typed by a user." + } + }, + "additionalProperties": false + }, + "created_on": { + "type": "string", + "format": "date-time" + }, + "creator": { + "$ref": "#/components/schemas/account" + }, + "id": { + "type": "integer" + }, + "pending": { + "type": "boolean" + }, + "resolved_by": { + "$ref": "#/components/schemas/account" + }, + "resolved_on": { + "type": "string", + "description": "The ISO8601 timestamp for when the task was resolved.", + "format": "date-time" + }, + "state": { + "type": "string", + "enum": ["RESOLVED", "UNRESOLVED"] + }, + "updated_on": { + "type": "string", + "format": "date-time" + } + }, + "required": ["created_on", "updated_on", "state", "content", "creator"], + "additionalProperties": false + }, "team": { "allOf": [ { @@ -25442,36 +31318,48 @@ "items": { "type": "string", "enum": [ - "issue:created", - "repo:commit_status_updated", - "pullrequest:comment_created", - "pullrequest:created", + "issue:comment_created", + "repo:push", + "repo:transfer", + "pullrequest:changes_request_removed", + "pullrequest:comment_updated", "pullrequest:unapproved", - "repo:commit_status_created", + "pullrequest:comment_created", "repo:created", "pullrequest:changes_request_created", - "pullrequest:comment_updated", - "pullrequest:changes_request_removed", - "pullrequest:comment_deleted", "repo:imported", - "repo:commit_comment_created", + "repo:fork", "project:updated", - "repo:updated", + "issue:created", + "repo:deleted", "issue:updated", + "pullrequest:comment_reopened", + "pullrequest:updated", + "repo:commit_comment_created", + "pullrequest:created", + "pullrequest:approved", "pullrequest:rejected", "pullrequest:fulfilled", - "issue:comment_created", - "repo:fork", - "pullrequest:updated", - "repo:push", - "pullrequest:approved", - "repo:deleted", - "repo:transfer" + "pullrequest:comment_resolved", + "repo:commit_status_updated", + "repo:updated", + "pullrequest:comment_deleted", + "repo:commit_status_created" ] }, "minItems": 1, "uniqueItems": true }, + "secret": { + "type": "string", + "description": "The secret to associate with the hook. The secret is never returned via the API. As such, this field is only used during updates. The secret can be set to `null` or \"\" to remove the secret (or create a hook with no secret). Leaving out the secret field during updates will leave the secret unchanged. Leaving out the secret during creation will create a hook with no secret.", + "minLength": 0, + "maxLength": 128 + }, + "secret_set": { + "type": "boolean", + "description": "Indicates whether or not the hook has an associated secret. It is not possible to see the hook's secret. This field is ignored during updates." + }, "subject": { "$ref": "#/components/schemas/object" }, diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index 0aa9d3550e..3c94cb30c3 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", "description": "Common functionalities for bitbucket-cloud plugins", - "version": "0.2.15-next.0", + "version": "0.2.16-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -41,7 +41,7 @@ "@backstage/cli": "workspace:^", "@openapitools/openapi-generator-cli": "^2.4.26", "msw": "^1.0.0", - "ts-morph": "^20.0.0" + "ts-morph": "^21.0.0" }, "files": [ "dist" diff --git a/plugins/bitbucket-cloud-common/src/models/index.ts b/plugins/bitbucket-cloud-common/src/models/index.ts index d868e2926c..8fce389a32 100644 --- a/plugins/bitbucket-cloud-common/src/models/index.ts +++ b/plugins/bitbucket-cloud-common/src/models/index.ts @@ -373,7 +373,19 @@ export namespace Models { * The concatenation of the repository owner's username and the slugified name, e.g. "evzijst/interruptingcow". This is the same string used in Bitbucket URLs. */ full_name?: string; + /** + * + * The issue tracker for this repository is enabled. Issue Tracker + * features are not supported for repositories in workspaces + * administered through admin.atlassian.com. + */ has_issues?: boolean; + /** + * + * The wiki for this repository is enabled. Wiki + * features are not supported for repositories in workspaces + * administered through admin.atlassian.com. + */ has_wiki?: boolean; is_private?: boolean; language?: string; diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index 3b180f797e..68defbcc7f 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,99 @@ # @backstage/plugin-bitrise +## 0.1.57-next.0 + +### Patch Changes + +- e24e4d3: Update README +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.1.56 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.1.56-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.56-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.1.56-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.1.55 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## 0.1.55-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.55-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.1.55-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.1.55-next.0 ### Patch Changes diff --git a/plugins/bitrise/README.md b/plugins/bitrise/README.md index 9c99f28568..e5d5c2122c 100644 --- a/plugins/bitrise/README.md +++ b/plugins/bitrise/README.md @@ -28,9 +28,8 @@ const websiteEntityPage = ( ``` -Now your plugin should be visible as a tab at the top of the entity pages, -specifically for components that are of the type `website`. -However, it warns of a missing `bitrise.io/app` annotation. +Your plugin should now appear as a tab at the top of entity pages, particularly for `website` component types. +However, it alerts you to a missing `bitrise.io/app` annotation. Add the annotation to your component [catalog-info.yaml](https://github.com/backstage/backstage/blob/master/catalog-info.yaml) as shown in the highlighted example below: @@ -51,4 +50,4 @@ proxy: Authorization: ${BITRISE_AUTH_TOKEN} ``` -Learn on https://devcenter.bitrise.io/api/authentication how to create a new Bitrise token. +Learn how to generate a new Bitrise token at https://devcenter.bitrise.io/api/authentication. diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 5f4df1022f..7c1677919e 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitrise", "description": "A Backstage plugin that integrates towards Bitrise", - "version": "0.1.55-next.0", + "version": "0.1.57-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,7 +34,6 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 4447c07186..afb57360f9 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,163 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.3.4-next.0 + +### Patch Changes + +- a81b1ba: The default EKS cluster entity transformer now sets the new + `kubernetes.io/x-k8s-aws-id` annotation. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.3.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- 22e88d0: Added status and e-mail as labels to the AWS Account Resource +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.3.3-next.1 + +### Patch Changes + +- 22e88d0: Added status and e-mail as labels to the AWS Account Resource +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + +## 0.3.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-aws-node@0.1.8 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.3.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.3.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## 0.3.2-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.3.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/alpha-api-report.md b/plugins/catalog-backend-module-aws/api-report-alpha.md similarity index 100% rename from plugins/catalog-backend-module-aws/alpha-api-report.md rename to plugins/catalog-backend-module-aws/api-report-alpha.md diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index a4ae3b395f..613dea2f2f 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", "description": "A Backstage catalog backend module that helps integrate towards AWS", - "version": "0.3.2-next.0", + "version": "0.3.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -62,8 +62,6 @@ "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-kubernetes-common": "workspace:^", - "@backstage/types": "workspace:^", - "lodash": "^4.17.21", "p-limit": "^3.0.2", "uuid": "^8.0.0", "winston": "^3.2.1" @@ -72,7 +70,6 @@ "@aws-sdk/util-stream-node": "^3.350.0", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/lodash": "^4.14.151", "aws-sdk-client-mock": "^3.0.0", "aws-sdk-client-mock-jest": "^3.0.0", "luxon": "^3.0.0", diff --git a/plugins/catalog-backend-module-aws/src/lib/defaultTransformers.ts b/plugins/catalog-backend-module-aws/src/lib/defaultTransformers.ts index 9de79e9811..07862bb298 100644 --- a/plugins/catalog-backend-module-aws/src/lib/defaultTransformers.ts +++ b/plugins/catalog-backend-module-aws/src/lib/defaultTransformers.ts @@ -18,6 +18,7 @@ import { ANNOTATION_KUBERNETES_API_SERVER, ANNOTATION_KUBERNETES_API_SERVER_CA, ANNOTATION_KUBERNETES_AUTH_PROVIDER, + ANNOTATION_KUBERNETES_AWS_CLUSTER_ID, } from '@backstage/plugin-kubernetes-common'; import type { EksClusterEntityTransformer } from '../processors/types'; import { ANNOTATION_AWS_ACCOUNT_ID, ANNOTATION_AWS_ARN } from '../constants'; @@ -29,6 +30,7 @@ import { ANNOTATION_AWS_ACCOUNT_ID, ANNOTATION_AWS_ARN } from '../constants'; export const defaultEksClusterEntityTransformer: EksClusterEntityTransformer = async (cluster: Cluster, accountId: string) => { const { arn, endpoint, certificateAuthority, name } = cluster; + const normalizedName = normalizeName(name as string); return { apiVersion: 'backstage.io/v1alpha1', kind: 'Resource', @@ -40,8 +42,9 @@ export const defaultEksClusterEntityTransformer: EksClusterEntityTransformer = [ANNOTATION_KUBERNETES_API_SERVER_CA]: certificateAuthority?.data || '', [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'aws', + [ANNOTATION_KUBERNETES_AWS_CLUSTER_ID]: normalizedName, }, - name: normalizeName(name as string), + name: normalizedName, namespace: 'default', }, spec: { diff --git a/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.ts b/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.ts index 521d61d3d8..e7816ae12f 100644 --- a/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.ts +++ b/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.ts @@ -29,7 +29,7 @@ import { AwsS3EntityProvider } from '../providers'; */ export const catalogModuleAwsS3EntityProvider = createBackendModule({ pluginId: 'catalog', - moduleId: 'awsS3EntityProvider', + moduleId: 'aws-s3-entity-provider', register(env) { env.registerInit({ deps: { diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.test.ts b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.test.ts index 6cec6ed66d..a07f775fb9 100644 --- a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.test.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.test.ts @@ -67,6 +67,7 @@ describe('AwsEKSClusterProcessor', () => { 'kubernetes.io/api-server-certificate-authority': cluster.cluster?.certificateAuthority?.data, 'kubernetes.io/auth-provider': 'aws', + 'kubernetes.io/x-k8s-aws-id': 'backstage-test', }, name: 'backstage-test', namespace: 'default', diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.test.ts b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.test.ts index 458a1f3531..05b247720a 100644 --- a/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.test.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.test.ts @@ -40,7 +40,9 @@ describe('AwsOrganizationCloudAccountProcessor', () => { Accounts: [ { Arn: 'arn:aws:organizations::192594491037:account/o-1vl18kc5a3/957140518395', - Name: 'testaccount', + Name: 'Test Account', + Email: 'aws-test-account@backstage.io', + Status: 'ACTIVE', }, ], NextToken: undefined, @@ -59,7 +61,12 @@ describe('AwsOrganizationCloudAccountProcessor', () => { 'amazonaws.com/account-id': '957140518395', 'amazonaws.com/organization-id': 'o-1vl18kc5a3', }, - name: 'testaccount', + labels: { + 'amazonaws.com/account-status': 'active', + 'amazonaws.com/account-email': 'aws-test-account@backstage.io', + }, + name: 'test-account', + title: 'Test Account', namespace: 'default', }, spec: { @@ -79,11 +86,11 @@ describe('AwsOrganizationCloudAccountProcessor', () => { Accounts: [ { Arn: 'arn:aws:organizations::192594491037:account/o-1vl18kc5a3/957140518395', - Name: 'testaccount', + Name: 'Test Account', }, { Arn: 'arn:aws:organizations::192594491037:account/o-zzzzzzzzz/957140518395', - Name: 'testaccount2', + Name: 'Test Account 2', }, ], NextToken: undefined, @@ -103,7 +110,12 @@ describe('AwsOrganizationCloudAccountProcessor', () => { 'amazonaws.com/account-id': '957140518395', 'amazonaws.com/organization-id': 'o-1vl18kc5a3', }, - name: 'testaccount', + labels: { + 'amazonaws.com/account-status': '', + 'amazonaws.com/account-email': '', + }, + name: 'test-account', + title: 'Test Account', namespace: 'default', }, spec: { diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts index 264fa7a50d..3e42fc79d2 100644 --- a/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts @@ -41,6 +41,9 @@ const ACCOUNTID_ANNOTATION = 'amazonaws.com/account-id'; const ARN_ANNOTATION = 'amazonaws.com/arn'; const ORGANIZATION_ANNOTATION = 'amazonaws.com/organization-id'; +const ACCOUNT_STATUS_LABEL = 'amazonaws.com/account-status'; +const ACCOUNT_EMAIL_LABEL = 'amazonaws.com/account-email'; + /** * A processor for ingesting AWS Accounts from AWS Organizations. * @@ -123,6 +126,10 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { .replace(/[^a-zA-Z0-9\-]/g, '-'); } + private normalizeAccountStatus(name: string): string { + return name.toLocaleLowerCase('en-US'); + } + private extractInformationFromArn(arn: string): { accountId: string; organizationId: string; @@ -165,7 +172,14 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { [ARN_ANNOTATION]: account.Arn || '', [ORGANIZATION_ANNOTATION]: organizationId, }, + labels: { + [ACCOUNT_EMAIL_LABEL]: account.Email || '', + [ACCOUNT_STATUS_LABEL]: this.normalizeAccountStatus( + account.Status || '', + ), + }, name: this.normalizeName(account.Name || ''), + title: account.Name || '', namespace: 'default', }, spec: { diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 04d6dcd33b..bb760f1136 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,135 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.1.28 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.1.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.1.28-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.1.27 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.1.27-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.1.27-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + +## 0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.1.27-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/alpha-api-report.md b/plugins/catalog-backend-module-azure/api-report-alpha.md similarity index 100% rename from plugins/catalog-backend-module-azure/alpha-api-report.md rename to plugins/catalog-backend-module-azure/api-report-alpha.md diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 1ff6c4cc78..c3c9ac8a81 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", "description": "A Backstage catalog backend module that helps integrate towards Azure", - "version": "0.1.27-next.0", + "version": "0.1.29-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -48,14 +48,10 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", - "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", - "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", - "@backstage/types": "workspace:^", - "lodash": "^4.17.21", "node-fetch": "^2.6.7", "uuid": "^8.0.0", "winston": "^3.2.1" @@ -63,7 +59,6 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/lodash": "^4.14.151", "luxon": "^3.0.0", "msw": "^1.0.0" }, diff --git a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts index 804c0d4712..b9c55041b3 100644 --- a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts +++ b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts @@ -29,7 +29,7 @@ import { AzureDevOpsEntityProvider } from '../providers'; */ export const catalogModuleAzureDevOpsEntityProvider = createBackendModule({ pluginId: 'catalog', - moduleId: 'azureDevOpsEntityProvider', + moduleId: 'azure-dev-ops-entity-provider', register(env) { env.registerInit({ deps: { diff --git a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md index cf6015342e..ee80978b65 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,132 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-openapi-utils@0.1.3-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.2 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-openapi-utils@0.1.2 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/backend-openapi-utils@0.1.2-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-openapi-utils@0.1.2-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-openapi-utils@0.1.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.1 + +### Patch Changes + +- eb44e92: Support authenticated backends +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-openapi-utils@0.1.1 + - @backstage/backend-tasks@0.5.13 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-openapi-utils@0.1.1-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-openapi-utils@0.1.1-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.1-next.1 + +### Patch Changes + +- eb44e92898: Support authenticated backends +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-openapi-utils@0.1.1-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.1.1-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 57611b6920..4f2b1a5b11 100644 --- a/plugins/catalog-backend-module-backstage-openapi/package.json +++ b/plugins/catalog-backend-module-backstage-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-backstage-openapi", - "version": "0.1.1-next.0", + "version": "0.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,6 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "cross-fetch": "^4.0.0", - "express": "^4.17.1", - "express-promise-router": "^4.1.0", "lodash": "^4.17.21", "openapi-merge": "^1.3.2", "uuid": "^9.0.0", @@ -40,12 +38,9 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/test-utils": "workspace:^", "@types/express": "*", "@types/supertest": "^2.0.8", - "msw": "^1.0.0", - "openapi3-ts": "^3.1.2", - "supertest": "^6.2.4" + "openapi3-ts": "^3.1.2" }, "configSchema": "config.d.ts", "files": [ diff --git a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts index 33c461d95a..a8782e5eea 100644 --- a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts +++ b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts @@ -25,6 +25,7 @@ import { EntityProviderConnection, } from '@backstage/plugin-catalog-node'; import { merge, isErrorResult } from 'openapi-merge'; +import { TokenManager } from '@backstage/backend-common'; import { getOpenApiSpecRoute } from '@backstage/backend-openapi-utils'; import type { OpenAPIObject, @@ -107,17 +108,25 @@ const loadSpecs = async ({ discovery, plugins, logger, + tokenManager, }: { baseUrl: string; plugins: string[]; discovery: DiscoveryService; logger: LoggerService; + tokenManager: TokenManager; }) => { const specs: OpenAPIObject[] = []; for (const pluginId of plugins) { const url = await discovery.getExternalBaseUrl(pluginId); const openApiUrl = getOpenApiSpecRoute(url); - const response = await fetch(openApiUrl); + const { token } = await tokenManager.getToken(); + const response = await fetch(openApiUrl, { + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + }, + }); if (response.ok) { const spec = await response.json(); addTagsToSpec(spec, pluginId); @@ -144,6 +153,7 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { public readonly config: Config, public readonly discovery: DiscoveryService, public readonly logger: LoggerService, + public readonly tokenManager: TokenManager, taskRunner: TaskRunner, ) { this.scheduleFn = this.createScheduleFn(taskRunner); @@ -155,6 +165,7 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { discovery: DiscoveryService; logger: LoggerService; schedule: PluginTaskScheduler; + tokenManager: TokenManager; }, ) { const taskRunner = options.schedule.createScheduledTaskRunner({ @@ -169,6 +180,7 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { config, options.discovery, options.logger, + options.tokenManager, taskRunner, ); } @@ -231,6 +243,7 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { await loadSpecs({ baseUrl: this.config.getString('backend.baseUrl'), discovery: this.discovery, + tokenManager: this.tokenManager, plugins: pluginsToMerge, logger, }), diff --git a/plugins/catalog-backend-module-backstage-openapi/src/index.ts b/plugins/catalog-backend-module-backstage-openapi/src/index.ts index 226442666c..ac425afaf2 100644 --- a/plugins/catalog-backend-module-backstage-openapi/src/index.ts +++ b/plugins/catalog-backend-module-backstage-openapi/src/index.ts @@ -44,13 +44,22 @@ export const catalogModuleInternalOpenApiSpec = createBackendModule({ discovery: coreServices.discovery, scheduler: coreServices.scheduler, logger: coreServices.logger, + tokenManager: coreServices.tokenManager, }, - async init({ catalog, config, discovery, scheduler, logger }) { + async init({ + catalog, + config, + discovery, + scheduler, + logger, + tokenManager, + }) { catalog.addEntityProvider( InternalOpenApiDocumentationProvider.fromConfig(config, { discovery, schedule: scheduler, logger, + tokenManager, }), ); }, diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index d0824e1fe6..a9c1aa2945 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,154 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.16-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-events-node@0.2.19-next.0 + +## 0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-events-node@0.2.18 + +## 0.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## 0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-events-node@0.2.18-next.0 + +## 0.1.23 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-events-node@0.2.17 + +## 0.1.23-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + +## 0.1.23-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-events-node@0.2.17-next.2 + +## 0.1.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + ## 0.1.23-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/alpha-api-report.md b/plugins/catalog-backend-module-bitbucket-cloud/api-report-alpha.md similarity index 100% rename from plugins/catalog-backend-module-bitbucket-cloud/alpha-api-report.md rename to plugins/catalog-backend-module-bitbucket-cloud/api-report-alpha.md diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index fd991c00a6..7dd551e15a 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", - "version": "0.1.23-next.0", + "version": "0.1.25-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts index e5e1ad1609..39265dc1a5 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts @@ -31,7 +31,7 @@ import { BitbucketCloudEntityProvider } from '../providers/BitbucketCloudEntityP */ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({ pluginId: 'catalog', - moduleId: 'bitbucketCloudEntityProvider', + moduleId: 'bitbucket-cloud-entity-provider', register(env) { env.registerInit({ deps: { diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index f96a8ee235..c6eeebe0e6 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,129 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.1.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.21 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.21-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.1.21-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## 0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.1.21-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/alpha-api-report.md b/plugins/catalog-backend-module-bitbucket-server/api-report-alpha.md similarity index 100% rename from plugins/catalog-backend-module-bitbucket-server/alpha-api-report.md rename to plugins/catalog-backend-module-bitbucket-server/api-report-alpha.md diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index acba1bc02a..ab8febdc20 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.1.21-next.0", + "version": "0.1.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.ts index ef71df9b06..dd2a423cdc 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.ts @@ -27,7 +27,7 @@ import { BitbucketServerEntityProvider } from '../providers'; */ export const catalogModuleBitbucketServerEntityProvider = createBackendModule({ pluginId: 'catalog', - moduleId: 'bitbucketServerEntityProvider', + moduleId: 'bitbucket-server-entity-provider', register(env) { env.registerInit({ deps: { diff --git a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md index a611562cfe..27fb897ae2 100644 --- a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,115 @@ # @backstage/plugin-catalog-backend-module-bitbucket +## 0.2.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.16-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + +## 0.2.24 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + +## 0.2.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + +## 0.2.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.2.24-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + +## 0.2.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.15 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.23-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.2.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + +## 0.2.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.15-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.2.23-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index cc0d27df6b..8db2d2dabe 100644 --- a/plugins/catalog-backend-module-bitbucket/package.json +++ b/plugins/catalog-backend-module-bitbucket/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket", - "version": "0.2.23-next.0", + "version": "0.2.25-next.0", "deprecated": true, "main": "src/index.ts", "types": "src/index.ts", @@ -34,21 +34,16 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", - "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", - "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-bitbucket-cloud-common": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", - "@backstage/types": "workspace:^", - "lodash": "^4.17.21", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/lodash": "^4.14.151", "msw": "^1.0.0" }, "files": [ diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index a438d1dc65..bdf72f7171 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,122 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## 0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + +## 0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## 0.1.8 + +### Patch Changes + +- 42c1aee: Updated dependency `@google-cloud/container` to `^5.0.0`. +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## 0.1.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## 0.1.8-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.1.8-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/alpha-api-report.md b/plugins/catalog-backend-module-gcp/api-report-alpha.md similarity index 100% rename from plugins/catalog-backend-module-gcp/alpha-api-report.md rename to plugins/catalog-backend-module-gcp/api-report-alpha.md diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index d33ffa9d47..ad5bdfa3f5 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-gcp", "description": "A Backstage catalog backend module that helps integrate towards GCP", - "version": "0.1.8-next.0", + "version": "0.1.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-gcp/src/module/catalogModuleGcpGkeEntityProvider.ts b/plugins/catalog-backend-module-gcp/src/module/catalogModuleGcpGkeEntityProvider.ts index 7f93bddbfe..acc216e1f6 100644 --- a/plugins/catalog-backend-module-gcp/src/module/catalogModuleGcpGkeEntityProvider.ts +++ b/plugins/catalog-backend-module-gcp/src/module/catalogModuleGcpGkeEntityProvider.ts @@ -29,7 +29,7 @@ import { GkeEntityProvider } from '../providers/GkeEntityProvider'; */ export const catalogModuleGcpGkeEntityProvider = createBackendModule({ pluginId: 'catalog', - moduleId: 'gcpGkeEntityProvider', + moduleId: 'gcp-gke-entity-provider', register(env) { env.registerInit({ deps: { diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index b0f9d60904..2b965a3267 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,127 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.25 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.1.25-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.24 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.24-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.1.24-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## 0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.1.24-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/alpha-api-report.md b/plugins/catalog-backend-module-gerrit/api-report-alpha.md similarity index 100% rename from plugins/catalog-backend-module-gerrit/alpha-api-report.md rename to plugins/catalog-backend-module-gerrit/api-report-alpha.md diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 04e3457389..91097235d4 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.1.24-next.0", + "version": "0.1.26-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -44,7 +44,6 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", - "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", diff --git a/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.ts b/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.ts index bb29ce6eef..af870d095f 100644 --- a/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.ts +++ b/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.ts @@ -27,7 +27,7 @@ import { GerritEntityProvider } from '../providers/GerritEntityProvider'; */ export const catalogModuleGerritEntityProvider = createBackendModule({ pluginId: 'catalog', - moduleId: 'gerritEntityProvider', + moduleId: 'gerrit-entity-provider', register(env) { env.registerInit({ deps: { diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index cd9731e46c..a8448fe5af 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,114 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-backend-module-github@0.4.8-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-catalog-backend-module-github@0.4.7 + - @backstage/config@1.1.1 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-backend-module-github@0.4.7-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-catalog-backend-module-github@0.4.7-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-backend-module-github@0.4.7-next.0 + +## 0.1.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-catalog-backend-module-github@0.4.6 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + +## 0.1.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-backend-module-github@0.4.6-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.1.2-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-catalog-backend-module-github@0.4.6-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/config@1.1.1 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-backend-module-github@0.4.6-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.1.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index a1abbd3207..585f032f05 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", "description": "The github-org backend module for the catalog plugin.", - "version": "0.1.2-next.0", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-github-org/src/module.ts b/plugins/catalog-backend-module-github-org/src/module.ts index e510464bb7..d666f78bde 100644 --- a/plugins/catalog-backend-module-github-org/src/module.ts +++ b/plugins/catalog-backend-module-github-org/src/module.ts @@ -68,7 +68,7 @@ export const githubOrgEntityProviderTransformsExtensionPoint = */ export const catalogModuleGithubOrgEntityProvider = createBackendModule({ pluginId: 'catalog', - moduleId: 'githubOrgEntityProvider', + moduleId: 'github-org-entity-provider', register(env) { let userTransformer: UserTransformer | undefined; let teamTransformer: TeamTransformer | undefined; diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 535430f03b..b00dfda365 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,155 @@ # @backstage/plugin-catalog-backend-module-github +## 0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-events-node@0.2.19-next.0 + +## 0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-catalog-backend@1.16.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-events-node@0.2.18 + +## 0.4.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## 0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-events-node@0.2.18-next.0 + +## 0.4.6 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-events-node@0.2.17 + +## 0.4.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + +## 0.4.6-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-events-node@0.2.17-next.2 + +## 0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + ## 0.4.6-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/alpha-api-report.md b/plugins/catalog-backend-module-github/api-report-alpha.md similarity index 100% rename from plugins/catalog-backend-module-github/alpha-api-report.md rename to plugins/catalog-backend-module-github/api-report-alpha.md diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index e94b98e427..10c95c6c44 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-github", "description": "A Backstage catalog backend module that helps integrate towards GitHub", - "version": "0.4.6-next.0", + "version": "0.4.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.ts index 8e052bd0a0..bfa8cf2a4e 100644 --- a/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/module/catalogModuleGithubEntityProvider.ts @@ -29,7 +29,7 @@ import { GithubEntityProvider } from '../providers/GithubEntityProvider'; */ export const catalogModuleGithubEntityProvider = createBackendModule({ pluginId: 'catalog', - moduleId: 'githubEntityProvider', + moduleId: 'github-entity-provider', register(env) { env.registerInit({ deps: { diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 5d98bdd8c2..b78079c4e8 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,122 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.3.7-next.0 + +### Patch Changes + +- 60e4c2a: Added the option to provide custom `groupTransformer`, `userTransformer` and `groupNameTransformer` to allow custom transformations of groups and users +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + +## 0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + +## 0.3.5 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## 0.3.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.3.5-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/alpha-api-report.md b/plugins/catalog-backend-module-gitlab/api-report-alpha.md similarity index 100% rename from plugins/catalog-backend-module-gitlab/alpha-api-report.md rename to plugins/catalog-backend-module-gitlab/api-report-alpha.md diff --git a/plugins/catalog-backend-module-gitlab/api-report.md b/plugins/catalog-backend-module-gitlab/api-report.md index 6620f6445f..bb52d8908a 100644 --- a/plugins/catalog-backend-module-gitlab/api-report.md +++ b/plugins/catalog-backend-module-gitlab/api-report.md @@ -8,10 +8,14 @@ import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; +import { GitLabIntegrationConfig } from '@backstage/integration'; +import { GroupEntity } from '@backstage/catalog-model'; import { LocationSpec } from '@backstage/plugin-catalog-node'; import { Logger } from 'winston'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { UserEntity } from '@backstage/catalog-model'; // @public export class GitlabDiscoveryEntityProvider implements EntityProvider { @@ -53,6 +57,20 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { ): Promise; } +// @public +export type GitLabGroup = { + id: number; + name: string; + full_path: string; + description?: string; + parent_id?: number; +}; + +// @public (undocumented) +export type GitLabGroupSamlIdentity = { + extern_uid: string; +}; + // @public export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { // (undocumented) @@ -64,9 +82,84 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { logger: Logger; schedule?: TaskRunner; scheduler?: PluginTaskScheduler; + userTransformer?: UserTransformer; + groupEntitiesTransformer?: GroupTransformer; + groupNameTransformer?: GroupNameTransformer; }, ): GitlabOrgDiscoveryEntityProvider[]; // (undocumented) getProviderName(): string; } + +// @public +export type GitlabProviderConfig = { + host: string; + group: string; + id: string; + branch?: string; + fallbackBranch: string; + catalogFile: string; + projectPattern: RegExp; + userPattern: RegExp; + groupPattern: RegExp; + orgEnabled?: boolean; + schedule?: TaskScheduleDefinition; + skipForkedRepos?: boolean; +}; + +// @public +export type GitLabUser = { + id: number; + username: string; + email?: string; + name: string; + state: string; + web_url: string; + avatar_url: string; + groups?: GitLabGroup[]; + group_saml_identity?: GitLabGroupSamlIdentity; +}; + +// @public +export type GroupNameTransformer = ( + options: GroupNameTransformerOptions, +) => string; + +// @public +export interface GroupNameTransformerOptions { + // (undocumented) + group: GitLabGroup; + // (undocumented) + providerConfig: GitlabProviderConfig; +} + +// @public +export type GroupTransformer = ( + options: GroupTransformerOptions, +) => GroupEntity[]; + +// @public +export interface GroupTransformerOptions { + // (undocumented) + groupNameTransformer: GroupNameTransformer; + // (undocumented) + groups: GitLabGroup[]; + // (undocumented) + providerConfig: GitlabProviderConfig; +} + +// @public +export type UserTransformer = (options: UserTransformerOptions) => UserEntity; + +// @public +export interface UserTransformerOptions { + // (undocumented) + groupNameTransformer: GroupNameTransformer; + // (undocumented) + integrationConfig: GitLabIntegrationConfig; + // (undocumented) + providerConfig: GitlabProviderConfig; + // (undocumented) + user: GitLabUser; +} ``` diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index d42961b665..a364ddb584 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", "description": "A Backstage catalog backend module that helps integrate towards GitLab", - "version": "0.3.5-next.0", + "version": "0.3.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-gitlab/src/index.ts b/plugins/catalog-backend-module-gitlab/src/index.ts index 2dd19c041d..cedcb828d6 100644 --- a/plugins/catalog-backend-module-gitlab/src/index.ts +++ b/plugins/catalog-backend-module-gitlab/src/index.ts @@ -25,3 +25,15 @@ export { GitlabDiscoveryEntityProvider, GitlabOrgDiscoveryEntityProvider, } from './providers'; +export type { + GitLabUser, + GitLabGroup, + GitlabProviderConfig, + GitLabGroupSamlIdentity, + GroupNameTransformer, + GroupNameTransformerOptions, + GroupTransformer, + GroupTransformerOptions, + UserTransformer, + UserTransformerOptions, +} from './lib'; diff --git a/plugins/catalog-backend-module-gitlab/src/lib/defaultTransformers.ts b/plugins/catalog-backend-module-gitlab/src/lib/defaultTransformers.ts new file mode 100644 index 0000000000..d7a6bf76e9 --- /dev/null +++ b/plugins/catalog-backend-module-gitlab/src/lib/defaultTransformers.ts @@ -0,0 +1,150 @@ +/* + * 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 { GroupEntity, UserEntity } from '@backstage/catalog-model'; +import { + GitLabGroup, + GroupNameTransformerOptions, + GroupTransformerOptions, + UserTransformerOptions, +} from './types'; + +export function defaultGroupNameTransformer( + options: GroupNameTransformerOptions, +): string { + if ( + options.providerConfig.group && + options.group.full_path.startsWith(`${options.providerConfig.group}/`) + ) { + return options.group.full_path + .replace(`${options.providerConfig.group}/`, '') + .replaceAll('/', '-'); + } + return options.group.full_path.replaceAll('/', '-'); +} + +export function defaultGroupEntitiesTransformer( + options: GroupTransformerOptions, +): GroupEntity[] { + const idMapped: { [groupId: number]: GitLabGroup } = {}; + const entities: GroupEntity[] = []; + + for (const group of options.groups) { + idMapped[group.id] = group; + } + + for (const group of options.groups) { + const annotations: { [annotationName: string]: string } = {}; + + annotations[`${options.providerConfig.host}/team-path`] = group.full_path; + + const entity: GroupEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: options.groupNameTransformer({ + group, + providerConfig: options.providerConfig, + }), + annotations: annotations, + }, + spec: { + type: 'team', + children: [], + profile: { + displayName: group.name, + }, + }, + }; + + if (group.description) { + entity.metadata.description = group.description; + } + + if (group.parent_id && idMapped.hasOwnProperty(group.parent_id)) { + entity.spec.parent = options.groupNameTransformer({ + group: idMapped[group.parent_id], + providerConfig: options.providerConfig, + }); + } + + entities.push(entity); + } + + return entities; +} + +/** + * The default implementation of the transformation from a graph user entry to + * a User entity. + * + * @public + */ +export function defaultUserTransformer( + options: UserTransformerOptions, +): UserEntity { + const annotations: { [annotationName: string]: string } = {}; + + annotations[`${options.integrationConfig.host}/user-login`] = + options.user.web_url; + if (options.user?.group_saml_identity?.extern_uid) { + annotations[`${options.integrationConfig.host}/saml-external-uid`] = + options.user.group_saml_identity.extern_uid; + } + + const entity: UserEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: options.user.username, + annotations: annotations, + }, + spec: { + profile: { + displayName: options.user.name || undefined, + picture: options.user.avatar_url || undefined, + }, + memberOf: [], + }, + }; + + if (options.user.email) { + if (!entity.spec) { + entity.spec = {}; + } + + if (!entity.spec.profile) { + entity.spec.profile = {}; + } + + entity.spec.profile.email = options.user.email; + } + + if (options.user.groups) { + for (const group of options.user.groups) { + if (!entity.spec.memberOf) { + entity.spec.memberOf = []; + } + entity.spec.memberOf.push( + options.groupNameTransformer({ + group, + providerConfig: options.providerConfig, + }), + ); + } + } + + return entity; +} diff --git a/plugins/catalog-backend-module-gitlab/src/lib/index.ts b/plugins/catalog-backend-module-gitlab/src/lib/index.ts index 53fad07994..1eee268862 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/index.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/index.ts @@ -16,8 +16,17 @@ export { GitLabClient, paginated } from './client'; export type { + GitLabUser, + GitLabGroup, + GitLabGroupSamlIdentity, GitLabProject, GitlabProviderConfig, GitlabGroupDescription, + GroupNameTransformer, + GroupNameTransformerOptions, + GroupTransformer, + GroupTransformerOptions, + UserTransformer, + UserTransformerOptions, } from './types'; export { readGitlabConfigs } from '../providers/config'; diff --git a/plugins/catalog-backend-module-gitlab/src/lib/types.ts b/plugins/catalog-backend-module-gitlab/src/lib/types.ts index fa2eb73da6..25c9a4a232 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/types.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { GroupEntity, UserEntity } from '@backstage/catalog-model'; +import { GitLabIntegrationConfig } from '@backstage/integration'; export type PagedResponse = { items: T[]; @@ -40,6 +42,11 @@ export type GitLabProject = { forked_from_project?: GitlabProjectForkedFrom; }; +/** + * Representation of a GitLab user in the GitLab API + * + * @public + */ export type GitLabUser = { id: number; username: string; @@ -52,10 +59,18 @@ export type GitLabUser = { group_saml_identity?: GitLabGroupSamlIdentity; }; +/** + * @public + */ export type GitLabGroupSamlIdentity = { extern_uid: string; }; +/** + * Representation of a GitLab group in the GitLab API + * + * @public + */ export type GitLabGroup = { id: number; name: string; @@ -113,10 +128,25 @@ export type GitLabDescendantGroupsResponse = { }; }; }; - +/** + * The configuration parameters for the GitlabProvider + * + * @public + */ export type GitlabProviderConfig = { + /** + * Identifies one of the hosts set up in the integrations + */ host: string; + /** + * Required for gitlab.com when `orgEnabled: true`. + * Optional for self managed. Must not end with slash. + * Accepts only groups under the provided path (which will be stripped) + */ group: string; + /** + * ??? + */ id: string; /** * The name of the branch to be used, to discover catalog files. @@ -128,11 +158,85 @@ export type GitlabProviderConfig = { * Defaults to: `master` */ fallbackBranch: string; + /** + * Defaults to `catalog-info.yaml` + */ catalogFile: string; + /** + * Filters found projects based on provided patter. + * Defaults to `[\s\S]*`, which means to not filter anything + */ projectPattern: RegExp; + /** + * Filters found users based on provided patter. + * Defaults to `[\s\S]*`, which means to not filter anything + */ userPattern: RegExp; + /** + * Filters found groups based on provided patter. + * Defaults to `[\s\S]*`, which means to not filter anything + */ groupPattern: RegExp; + orgEnabled?: boolean; schedule?: TaskScheduleDefinition; + /** + * If the project is a fork, skip repository + */ skipForkedRepos?: boolean; }; + +/** + * Customize how group names are generated + * + * @public + */ +export type GroupNameTransformer = ( + options: GroupNameTransformerOptions, +) => string; + +/** + * The GroupTransformerOptions + * + * @public + */ +export interface GroupNameTransformerOptions { + group: GitLabGroup; + providerConfig: GitlabProviderConfig; +} +/** + * Customize the ingested User entity + * + * @public + */ +export type UserTransformer = (options: UserTransformerOptions) => UserEntity; +/** + * The UserTransformerOptions + * + * @public + */ +export interface UserTransformerOptions { + user: GitLabUser; + integrationConfig: GitLabIntegrationConfig; + providerConfig: GitlabProviderConfig; + groupNameTransformer: GroupNameTransformer; +} + +/** + * Customize the ingested Group entity + * + * @public + */ +export type GroupTransformer = ( + options: GroupTransformerOptions, +) => GroupEntity[]; +/** + * The GroupTransformer options + * + * @public + */ +export interface GroupTransformerOptions { + groups: GitLabGroup[]; + providerConfig: GitlabProviderConfig; + groupNameTransformer: GroupNameTransformer; +} diff --git a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.ts index d192e8f3a5..9979d0862e 100644 --- a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.ts @@ -29,7 +29,7 @@ import { GitlabDiscoveryEntityProvider } from '../providers'; */ export const catalogModuleGitlabDiscoveryEntityProvider = createBackendModule({ pluginId: 'catalog', - moduleId: 'gitlabDiscoveryEntityProvider', + moduleId: 'gitlab-discovery-entity-provider', register(env) { env.registerInit({ deps: { diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts index affa8a41cf..1157a2f1f3 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts @@ -18,8 +18,6 @@ import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, Entity, - GroupEntity, - UserEntity, } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { GitLabIntegration, ScmIntegrations } from '@backstage/integration'; @@ -37,7 +35,19 @@ import { paginated, readGitlabConfigs, } from '../lib'; -import { GitLabGroup, GitLabUser, PagedResponse } from '../lib/types'; +import { + GitLabGroup, + GitLabUser, + PagedResponse, + UserTransformer, + GroupTransformer as GroupEntitiesTransformer, + GroupNameTransformer, +} from '../lib/types'; +import { + defaultGroupNameTransformer, + defaultGroupEntitiesTransformer, + defaultUserTransformer, +} from '../lib/defaultTransformers'; type Result = { scanned: number; @@ -59,6 +69,9 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { private readonly logger: Logger; private readonly scheduleFn: () => Promise; private connection?: EntityProviderConnection; + private userTransformer: UserTransformer; + private groupEntitiesTransformer: GroupEntitiesTransformer; + private groupNameTransformer: GroupNameTransformer; static fromConfig( config: Config, @@ -66,6 +79,9 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { logger: Logger; schedule?: TaskRunner; scheduler?: PluginTaskScheduler; + userTransformer?: UserTransformer; + groupEntitiesTransformer?: GroupEntitiesTransformer; + groupNameTransformer?: GroupNameTransformer; }, ): GitlabOrgDiscoveryEntityProvider[] { if (!options.schedule && !options.scheduler) { @@ -122,6 +138,9 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { integration: GitLabIntegration; logger: Logger; taskRunner: TaskRunner; + userTransformer?: UserTransformer; + groupEntitiesTransformer?: GroupEntitiesTransformer; + groupNameTransformer?: GroupNameTransformer; }) { this.config = options.config; this.integration = options.integration; @@ -129,6 +148,11 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { target: this.getProviderName(), }); this.scheduleFn = this.createScheduleFn(options.taskRunner); + this.userTransformer = options.userTransformer ?? defaultUserTransformer; + this.groupEntitiesTransformer = + options.groupEntitiesTransformer ?? defaultGroupEntitiesTransformer; + this.groupNameTransformer = + options.groupNameTransformer ?? defaultGroupNameTransformer; } getProviderName(): string { @@ -271,13 +295,20 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { }); const userEntities = res.matches.map(p => - this.createUserEntity(p, this.integration.config.host), - ); - const groupEntities = this.createGroupEntities( - groupsWithUsers, - this.integration.config.host, + this.userTransformer({ + user: p, + integrationConfig: this.integration.config, + providerConfig: this.config, + groupNameTransformer: this.groupNameTransformer, + }), ); + const groupEntities = this.groupEntitiesTransformer({ + groups: groupsWithUsers, + providerConfig: this.config, + groupNameTransformer: this.groupNameTransformer, + }); + await this.connection.applyMutation({ type: 'full', entities: [...userEntities, ...groupEntities].map(entity => ({ @@ -291,32 +322,6 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { }); } - private createGroupEntities( - groupResult: GitLabGroup[], - host: string, - ): GroupEntity[] { - const idMapped: { [groupId: number]: GitLabGroup } = {}; - const entities: GroupEntity[] = []; - - for (const group of groupResult) { - idMapped[group.id] = group; - } - - for (const group of groupResult) { - const entity = this.createGroupEntity(group, host); - - if (group.parent_id && idMapped.hasOwnProperty(group.parent_id)) { - entity.spec.parent = this.groupName( - idMapped[group.parent_id].full_path, - ); - } - - entities.push(entity); - } - - return entities; - } - private withLocations(host: string, baseUrl: string, entity: Entity): Entity { const location = entity.kind === 'Group' @@ -334,90 +339,4 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { entity, ) as Entity; } - - private createUserEntity(user: GitLabUser, host: string): UserEntity { - const annotations: { [annotationName: string]: string } = {}; - - annotations[`${host}/user-login`] = user.web_url; - if (user?.group_saml_identity?.extern_uid) { - annotations[`${host}/saml-external-uid`] = - user.group_saml_identity.extern_uid; - } - - const entity: UserEntity = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'User', - metadata: { - name: user.username, - annotations: annotations, - }, - spec: { - profile: { - displayName: user.name || undefined, - picture: user.avatar_url || undefined, - }, - memberOf: [], - }, - }; - - if (user.email) { - if (!entity.spec) { - entity.spec = {}; - } - - if (!entity.spec.profile) { - entity.spec.profile = {}; - } - - entity.spec.profile.email = user.email; - } - - if (user.groups) { - for (const group of user.groups) { - if (!entity.spec.memberOf) { - entity.spec.memberOf = []; - } - entity.spec.memberOf.push(this.groupName(group.full_path)); - } - } - - return entity; - } - - private groupName(full_path: string): string { - if (this.config.group && full_path.startsWith(`${this.config.group}/`)) { - return full_path - .replace(`${this.config.group}/`, '') - .replaceAll('/', '-'); - } - return full_path.replaceAll('/', '-'); - } - - private createGroupEntity(group: GitLabGroup, host: string): GroupEntity { - const annotations: { [annotationName: string]: string } = {}; - - annotations[`${host}/team-path`] = group.full_path; - - const entity: GroupEntity = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: this.groupName(group.full_path), - annotations: annotations, - }, - spec: { - type: 'team', - children: [], - profile: { - displayName: group.name, - }, - }, - }; - - if (group.description) { - entity.metadata.description = group.description; - } - - return entity; - } } diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index f2b342aae6..042dfc6bb1 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,149 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.4.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-events-node@0.2.19-next.0 + - @backstage/plugin-permission-common@0.7.12 + +## 0.4.13 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-catalog-backend@1.16.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-events-node@0.2.18 + +## 0.4.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + - @backstage/plugin-permission-common@0.7.11 + +## 0.4.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + - @backstage/plugin-permission-common@0.7.11 + +## 0.4.12 + +### Patch Changes + +- 43b2eb8: Ensure that cursors always come back as JSON on sqlite too +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-events-node@0.2.17 + +## 0.4.12-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + - @backstage/plugin-permission-common@0.7.10 + +## 0.4.12-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-events-node@0.2.17-next.2 + - @backstage/plugin-permission-common@0.7.10 + +## 0.4.12-next.1 + +### Patch Changes + +- 43b2eb8f70: Ensure that cursors always come back as JSON on sqlite too +- Updated dependencies + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + - @backstage/plugin-permission-common@0.7.10 + ## 0.4.12-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/alpha-api-report.md b/plugins/catalog-backend-module-incremental-ingestion/api-report-alpha.md similarity index 100% rename from plugins/catalog-backend-module-incremental-ingestion/alpha-api-report.md rename to plugins/catalog-backend-module-incremental-ingestion/api-report-alpha.md diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 53677fdec2..96ccb49130 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", "description": "An entity provider for streaming large asset sources into the catalog", - "version": "0.4.12-next.0", + "version": "0.4.14-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts new file mode 100644 index 0000000000..a8862cfc2a --- /dev/null +++ b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts @@ -0,0 +1,79 @@ +/* + * 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 { TestDatabases } from '@backstage/backend-test-utils'; +import { IncrementalIngestionDatabaseManager } from './IncrementalIngestionDatabaseManager'; +import { v4 as uuid } from 'uuid'; + +const migrationsDir = `${__dirname}/../../migrations`; + +jest.setTimeout(60_000); + +describe('IncrementalIngestionDatabaseManager', () => { + const databases = TestDatabases.create({ + ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], + }); + + it.each(databases.eachSupportedId())( + 'stores and retrieves marks, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await knex.migrate.latest({ directory: migrationsDir }); + + const manager = new IncrementalIngestionDatabaseManager({ client: knex }); + const { ingestionId } = (await manager.createProviderIngestionRecord( + 'myProvider', + ))!; + + const cursorId = uuid(); + + await manager.createMark({ + record: { + id: cursorId, + ingestion_id: ingestionId, + sequence: 1, + cursor: { data: 1 }, + }, + }); + + await expect(manager.getFirstMark(ingestionId)).resolves.toEqual({ + created_at: expect.anything(), + cursor: { data: 1 }, + id: cursorId, + ingestion_id: ingestionId, + sequence: 1, + }); + + await expect(manager.getLastMark(ingestionId)).resolves.toEqual({ + created_at: expect.anything(), + cursor: { data: 1 }, + id: cursorId, + ingestion_id: ingestionId, + sequence: 1, + }); + + await expect(manager.getAllMarks(ingestionId)).resolves.toEqual([ + { + created_at: expect.anything(), + cursor: { data: 1 }, + id: cursorId, + ingestion_id: ingestionId, + sequence: 1, + }, + ]); + }, + ); +}); diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.ts b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.ts index 527d345706..e47954f47e 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.ts @@ -542,7 +542,7 @@ export class IncrementalIngestionDatabaseManager { .where('ingestion_id', ingestionId) .orderBy('sequence', 'desc') .first(); - return mark; + return this.#decodeMark(this.client, mark); }); } @@ -557,7 +557,7 @@ export class IncrementalIngestionDatabaseManager { .where('ingestion_id', ingestionId) .orderBy('sequence', 'asc') .first(); - return mark; + return this.#decodeMark(this.client, mark); }); } @@ -566,7 +566,7 @@ export class IncrementalIngestionDatabaseManager { const marks = await tx('ingestion_marks') .where('ingestion_id', ingestionId) .orderBy('sequence', 'desc'); - return marks; + return marks.map(m => this.#decodeMark(this.client, m)); }); } @@ -580,6 +580,19 @@ export class IncrementalIngestionDatabaseManager { await tx('ingestion_marks').insert(record); }); } + + // Handles the fact that sqlite does not support json columns; they just + // persist the stringified data instead + #decodeMark(knex: Knex, record: T): T { + if (record && knex.client.config.client.includes('sqlite3')) { + return { + ...record, + cursor: JSON.parse(record.cursor as string), + }; + } + return record; + } + /** * Performs an upsert to the `ingestion_mark_entities` table for all deferred entities. * @param markId - string diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/database/tables.ts b/plugins/catalog-backend-module-incremental-ingestion/src/database/tables.ts index 00d40fb4a1..e1a47c040c 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/database/tables.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/database/tables.ts @@ -89,7 +89,7 @@ export interface MarkRecord { id: string; sequence: number; ingestion_id: string; - cursor: string; + cursor: unknown; created_at: string; } diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts index 7d138e4812..9416fa27eb 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts @@ -26,7 +26,7 @@ jest.setTimeout(60_000); describe('WrapperProviders', () => { const applyDatabaseMigrations = jest.fn(); const databases = TestDatabases.create({ - ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3', 'MYSQL_8'], + ids: ['POSTGRES_16', 'POSTGRES_12', 'SQLITE_3', 'MYSQL_8'], }); const config = new ConfigReader({}); const logger = getVoidLogger(); diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.test.ts index 7d76ddf480..316a4be7eb 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.test.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.test.ts @@ -48,7 +48,7 @@ describe('catalogModuleIncrementalIngestionEntityProvider', () => { catalogModuleIncrementalIngestionEntityProvider(), createBackendModule({ pluginId: 'catalog', - moduleId: 'incrementalTest', + moduleId: 'incremental-test', register(env) { env.registerInit({ deps: { extension: incrementalIngestionProvidersExtensionPoint }, diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.ts index 4e94088ef6..adb5ab6a04 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.ts @@ -49,7 +49,7 @@ export interface IncrementalIngestionProviderExtensionPoint { * ```ts * backend.add(createBackendModule({ * pluginId: 'catalog', - * moduleId: 'myIncrementalProvider', + * moduleId: 'my-incremental-provider', * register(env) { * env.registerInit({ * deps: { @@ -85,7 +85,7 @@ export const incrementalIngestionProvidersExtensionPoint = export const catalogModuleIncrementalIngestionEntityProvider = createBackendModule({ pluginId: 'catalog', - moduleId: 'incrementalIngestionEntityProvider', + moduleId: 'incremental-ingestion-entity-provider', register(env) { const addedProviders = new Array<{ provider: IncrementalEntityProvider; diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/run.ts b/plugins/catalog-backend-module-incremental-ingestion/src/run.ts index c24deb6adb..5af31482a6 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/run.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/run.ts @@ -67,7 +67,7 @@ async function main() { backend.add( createBackendModule({ pluginId: 'catalog', - moduleId: 'incrementalTestProvider', + moduleId: 'incremental-test-provider', register(reg) { reg.registerInit({ deps: { extension: incrementalIngestionProvidersExtensionPoint }, diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 987005f0a3..b6f74f007c 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,117 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.5.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.5.24 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.5.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.5.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.5.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.5.23 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.5.23-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.5.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + +## 0.5.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.5.23-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index fc1325c991..c2ad4a8aa3 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", "description": "A Backstage catalog backend module that helps integrate towards LDAP", - "version": "0.5.23-next.0", + "version": "0.5.25-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index e4d73b6745..99e9e39a76 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,121 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.5.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.5.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.5.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.5.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.5.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.5.15 + +### Patch Changes + +- 99fb541: Updated dependency `@azure/identity` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.5.15-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.5.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + +## 0.5.15-next.1 + +### Patch Changes + +- 99fb54183b: Updated dependency `@azure/identity` to `^4.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.5.15-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/alpha-api-report.md b/plugins/catalog-backend-module-msgraph/api-report-alpha.md similarity index 100% rename from plugins/catalog-backend-module-msgraph/alpha-api-report.md rename to plugins/catalog-backend-module-msgraph/api-report-alpha.md diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 356ca19642..56cf638689 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", - "version": "0.5.15-next.0", + "version": "0.5.17-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -45,7 +45,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@azure/identity": "^3.2.1", + "@azure/identity": "^4.0.0", "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index 8b7743c39f..498291b002 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,139 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-catalog-backend@1.17.0-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.1.26 + +### Patch Changes + +- 4ebf99b: Add support for the new backend system. + + A new backend module for the catalog backend + was added and exported as `default`. + + You can use it with the new backend system like + + ```ts title="packages/backend/src/index.ts" + backend.add(import('@backstage/plugin-catalog-backend-module-openapi')); + ``` + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-catalog-backend@1.16.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.1.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-backend@1.16.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + +## 0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-catalog-backend@1.16.1-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-backend@1.16.1-next.0 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.1.25 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/plugin-catalog-backend@1.16.0 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.1.25-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-backend@1.16.0-next.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.1.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + +## 0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.1.25-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/README.md b/plugins/catalog-backend-module-openapi/README.md index 6b988e15f5..6a8028895d 100644 --- a/plugins/catalog-backend-module-openapi/README.md +++ b/plugins/catalog-backend-module-openapi/README.md @@ -17,6 +17,33 @@ yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-openapi ### Adding the plugin to your `packages/backend` +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend-module-openapi')); +``` + +This will add the `jsonSchemaRefPlaceholderResolver` for +the placeholder resolver keys `asyncapi` and `openapi`. + +This allows you to use the `$openapi` placeholder when referencing your OpenAPI specification and `$asyncapi` when referencing your AsyncAPI specifications. This will then resolve all `$ref` instances in your specification. + +You can also use this resolver for other kind of yaml files to resolve $ref pointer. + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: example + description: Example API +spec: + type: openapi + lifecycle: production + owner: team + definition: + $openapi: ./spec/openapi.yaml # by using $openapi Backstage will now resolve all $ref instances +``` + +### Adding the plugin to your `packages/backend` (old backend system) + #### **jsonSchemaRefPlaceholderResolver** The placeholder resolver can be added by importing `jsonSchemaRefPlaceholderResolver` in `src/plugins/catalog.ts` in your `backend` package and adding the following. diff --git a/plugins/catalog-backend-module-openapi/api-report.md b/plugins/catalog-backend-module-openapi/api-report.md index 455816645a..206cfac291 100644 --- a/plugins/catalog-backend-module-openapi/api-report.md +++ b/plugins/catalog-backend-module-openapi/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; import { Entity } from '@backstage/catalog-model'; @@ -13,6 +14,10 @@ import { PlaceholderResolverParams } from '@backstage/plugin-catalog-backend'; import { ScmIntegrations } from '@backstage/integration'; import { UrlReader } from '@backstage/backend-common'; +// @public +const catalogModuleJsonSchemaRefPlaceholderResolver: () => BackendFeature; +export default catalogModuleJsonSchemaRefPlaceholderResolver; + // @public (undocumented) export function jsonSchemaRefPlaceholderResolver( params: PlaceholderResolverParams, diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 85e8cedb06..92e7bdf618 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", - "version": "0.1.25-next.0", + "version": "0.1.27-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,6 +34,7 @@ "dependencies": { "@apidevtools/json-schema-ref-parser": "^9.0.6", "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/integration": "workspace:^", diff --git a/plugins/catalog-backend-module-openapi/src/index.ts b/plugins/catalog-backend-module-openapi/src/index.ts index 170c925a7f..70ab88c2ff 100644 --- a/plugins/catalog-backend-module-openapi/src/index.ts +++ b/plugins/catalog-backend-module-openapi/src/index.ts @@ -22,3 +22,6 @@ export { jsonSchemaRefPlaceholderResolver } from './jsonSchemaRefPlaceholderReso * @deprecated replaced by jsonSchemaRefPlaceholderResolver */ export const openApiPlaceholderResolver = jsonSchemaRefPlaceholderResolver; + +export * from './module'; +export { default } from './module'; diff --git a/plugins/catalog-backend-module-openapi/src/module/catalogModuleJsonSchemaRefPlaceholderResolver.test.ts b/plugins/catalog-backend-module-openapi/src/module/catalogModuleJsonSchemaRefPlaceholderResolver.test.ts new file mode 100644 index 0000000000..cfea25341e --- /dev/null +++ b/plugins/catalog-backend-module-openapi/src/module/catalogModuleJsonSchemaRefPlaceholderResolver.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright 2024 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 { startTestBackend } from '@backstage/backend-test-utils'; +import { PlaceholderResolver } from '@backstage/plugin-catalog-node'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { catalogModuleJsonSchemaRefPlaceholderResolver } from './catalogModuleJsonSchemaRefPlaceholderResolver'; +import { jsonSchemaRefPlaceholderResolver } from '../jsonSchemaRefPlaceholderResolver'; + +describe('catalogModuleJsonSchemaRefPlaceholderResolver', () => { + it('should register provider at the catalog extension point', async () => { + const registeredPlaceholderResolvers: { + [key: string]: PlaceholderResolver; + } = {}; + + const extensionPoint = { + addPlaceholderResolver: ( + key: string, + resolver: PlaceholderResolver, + ): void => { + registeredPlaceholderResolvers[key] = resolver; + }, + }; + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + features: [catalogModuleJsonSchemaRefPlaceholderResolver()], + }); + + expect(Object.keys(registeredPlaceholderResolvers)).toEqual([ + 'asyncapi', + 'openapi', + ]); + expect(registeredPlaceholderResolvers.asyncapi).toBe( + jsonSchemaRefPlaceholderResolver, + ); + expect(registeredPlaceholderResolvers.openapi).toBe( + jsonSchemaRefPlaceholderResolver, + ); + }); +}); diff --git a/plugins/catalog-backend-module-openapi/src/module/catalogModuleJsonSchemaRefPlaceholderResolver.ts b/plugins/catalog-backend-module-openapi/src/module/catalogModuleJsonSchemaRefPlaceholderResolver.ts new file mode 100644 index 0000000000..053951866a --- /dev/null +++ b/plugins/catalog-backend-module-openapi/src/module/catalogModuleJsonSchemaRefPlaceholderResolver.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2024 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 { createBackendModule } from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; +import { jsonSchemaRefPlaceholderResolver } from '../jsonSchemaRefPlaceholderResolver'; + +/** + * Registers the jsonSchemaRefPlaceholderResolver + * as placeholder resolver for `$asyncapi` and `$openapi`. + * + * @public + */ +export const catalogModuleJsonSchemaRefPlaceholderResolver = + createBackendModule({ + pluginId: 'catalog', + moduleId: 'json-schema-ref-placeholder-resolver', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + }, + async init({ catalog }) { + catalog.addPlaceholderResolver( + 'asyncapi', + jsonSchemaRefPlaceholderResolver, + ); + catalog.addPlaceholderResolver( + 'openapi', + jsonSchemaRefPlaceholderResolver, + ); + }, + }); + }, + }); diff --git a/plugins/catalog-backend-module-openapi/src/module/index.ts b/plugins/catalog-backend-module-openapi/src/module/index.ts new file mode 100644 index 0000000000..b914472c8d --- /dev/null +++ b/plugins/catalog-backend-module-openapi/src/module/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 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 { catalogModuleJsonSchemaRefPlaceholderResolver as default } from './catalogModuleJsonSchemaRefPlaceholderResolver'; diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index 1a2febb565..b2dd64e934 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,129 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.1.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.1.13 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.1.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + +## 0.1.13-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + ## 0.1.13-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/alpha-api-report.md b/plugins/catalog-backend-module-puppetdb/api-report-alpha.md similarity index 100% rename from plugins/catalog-backend-module-puppetdb/alpha-api-report.md rename to plugins/catalog-backend-module-puppetdb/api-report-alpha.md diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 751c37c16c..4aaf20ba8b 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", - "version": "0.1.13-next.0", + "version": "0.1.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.ts b/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.ts index 5546b5283b..aad28eb47a 100644 --- a/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.ts +++ b/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.ts @@ -29,7 +29,7 @@ import { PuppetDbEntityProvider } from '../providers/PuppetDbEntityProvider'; */ export const catalogModulePuppetDbEntityProvider = createBackendModule({ pluginId: 'catalog', - moduleId: 'puppetDbEntityProvider', + moduleId: 'puppetdb-entity-provider', register(env) { env.registerInit({ deps: { diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index f122b093e0..de3ff6553a 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,109 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 0.1.5 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 0.1.5-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-scaffolder-common@1.4.3 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index 638aa6d104..66d3c3d26f 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model", "description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.", - "version": "0.1.5-next.0", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,7 +32,6 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/src/module.ts b/plugins/catalog-backend-module-scaffolder-entity-model/src/module.ts index 8ed763d32c..c11bc7f7d2 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/src/module.ts +++ b/plugins/catalog-backend-module-scaffolder-entity-model/src/module.ts @@ -16,7 +16,7 @@ import { createBackendModule } from '@backstage/backend-plugin-api'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; -import { ScaffolderEntitiesProcessor } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; +import { ScaffolderEntitiesProcessor } from './processor'; /** * Registers support for the scaffolder specific entity model (e.g. the Template @@ -26,7 +26,7 @@ import { ScaffolderEntitiesProcessor } from '@backstage/plugin-catalog-backend-m */ export const catalogModuleScaffolderEntityModel = createBackendModule({ pluginId: 'catalog', - moduleId: 'scaffolderEntityModel', + moduleId: 'scaffolder-entity-model', register(env) { env.registerInit({ deps: { diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md index 45dbc74091..3c34f951b5 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,87 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + +## 0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.3.5 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + +## 0.3.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.3.5-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 9e0f29285e..22786152c9 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-unprocessed", "description": "Backstage Catalog module to view unprocessed entities", - "version": "0.3.5-next.0", + "version": "0.3.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-unprocessed/src/module.ts b/plugins/catalog-backend-module-unprocessed/src/module.ts index 1db4fd70b0..8c724d432d 100644 --- a/plugins/catalog-backend-module-unprocessed/src/module.ts +++ b/plugins/catalog-backend-module-unprocessed/src/module.ts @@ -27,7 +27,7 @@ import { UnprocessedEntitiesModule } from './UnprocessedEntitiesModule'; */ export const catalogModuleUnprocessedEntities = createBackendModule({ pluginId: 'catalog', - moduleId: 'catalogModuleUnprocessedEntities', + moduleId: 'catalog-module-unprocessed-entities', register(env) { env.registerInit({ deps: { diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index cb721208a3..fcbad7031b 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,227 @@ # @backstage/plugin-catalog-backend +## 1.17.0-next.0 + +### Minor Changes + +- 126c2f9: Updates the OpenAPI spec to use plugin as `info.title` instead of package name. +- 04907c3: Updates the OpenAPI specification title to plugin ID instead of package name. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-openapi-utils@0.1.3-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-events-node@0.2.19-next.0 + - @backstage/plugin-permission-common@0.7.12 + +## 1.16.1 + +### Patch Changes + +- c3249d6: Parse the URL using a different method rather than `git-url-parse` to support wildcards for URLs which are not VCS providers +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-search-backend-module-catalog@0.1.13 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-openapi-utils@0.1.2 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-events-node@0.2.18 + +## 1.16.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/backend-openapi-utils@0.1.2-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 1.16.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.1 + - @backstage/backend-openapi-utils@0.1.2-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + - @backstage/plugin-permission-common@0.7.11 + +## 1.16.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.13-next.0 + - @backstage/backend-openapi-utils@0.1.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-events-node@0.2.18-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## 1.16.0 + +### Minor Changes + +- 7804597: Permission rules can now be added for the Catalog plugin through the `CatalogPermissionExtensionPoint` interface. + +### Patch Changes + +- 3834067: Update the OpenAPI spec to support the use of `openapi-generator`. +- 50ee804: Wrap single `pipelineLoop` of TaskPipeline in a span for better traces +- 7123c58: Updated dependency `@types/glob` to `^8.0.0`. +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- a168507: Deprecated `EntitiesSearchFilter` and `EntityFilter`, which can now be imported from `@backstage/plugin-catalog-node` instead +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-openapi-utils@0.1.1 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/plugin-search-backend-module-catalog@0.1.12 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-events-node@0.2.17 + +## 1.16.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-openapi-utils@0.1.1-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.3 + +## 1.16.0-next.2 + +### Minor Changes + +- 7804597: Permission rules can now be added for the Catalog plugin through the `CatalogPermissionExtensionPoint` interface. + +### Patch Changes + +- 50ee804: Wrap single `pipelineLoop` of TaskPipeline in a span for better traces +- a168507: Deprecated `EntitiesSearchFilter` and `EntityFilter`, which can now be imported from `@backstage/plugin-catalog-node` instead +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.2 + - @backstage/backend-openapi-utils@0.1.1-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-events-node@0.2.17-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## 1.15.1-next.1 + +### Patch Changes + +- 38340678c3: Update the OpenAPI spec to support the use of `openapi-generator`. +- 7123c58b3d: Updated dependency `@types/glob` to `^8.0.0`. +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-openapi-utils@0.1.1-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.12-next.1 + ## 1.15.1-next.0 ### Patch Changes diff --git a/plugins/catalog-backend/alpha-api-report.md b/plugins/catalog-backend/api-report-alpha.md similarity index 97% rename from plugins/catalog-backend/alpha-api-report.md rename to plugins/catalog-backend/api-report-alpha.md index 1682d1cbae..51a2ec6114 100644 --- a/plugins/catalog-backend/alpha-api-report.md +++ b/plugins/catalog-backend/api-report-alpha.md @@ -6,6 +6,7 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Conditions } from '@backstage/plugin-permission-node'; +import { EntitiesSearchFilter } from '@backstage/plugin-catalog-node'; import { Entity } from '@backstage/catalog-model'; import { PermissionCondition } from '@backstage/plugin-permission-common'; import { PermissionCriteria } from '@backstage/plugin-permission-common'; @@ -90,12 +91,6 @@ export const createCatalogPermissionRule: < rule: PermissionRule, ) => PermissionRule; -// @public -export type EntitiesSearchFilter = { - key: string; - values?: string[]; -}; - // @alpha export const permissionRules: { hasAnnotation: PermissionRule< diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 1aed0ea8b0..8b7565bd1f 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -28,7 +28,9 @@ import { Config } from '@backstage/config'; import { DefaultCatalogCollatorFactory as DefaultCatalogCollatorFactory_2 } from '@backstage/plugin-search-backend-module-catalog'; import type { DefaultCatalogCollatorFactoryOptions as DefaultCatalogCollatorFactoryOptions_2 } from '@backstage/plugin-search-backend-module-catalog'; import { DeferredEntity as DeferredEntity_2 } from '@backstage/plugin-catalog-node'; +import { EntitiesSearchFilter as EntitiesSearchFilter_2 } from '@backstage/plugin-catalog-node'; import { Entity } from '@backstage/catalog-model'; +import { EntityFilter as EntityFilter_2 } from '@backstage/plugin-catalog-node'; import { EntityPolicy } from '@backstage/catalog-model'; import { EntityProvider as EntityProvider_2 } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection as EntityProviderConnection_2 } from '@backstage/plugin-catalog-node'; @@ -191,7 +193,7 @@ export type CatalogEnvironment = { // @public export type CatalogPermissionRuleInput< TParams extends PermissionRuleParams = PermissionRuleParams, -> = PermissionRule; +> = PermissionRule; // @public export interface CatalogProcessingEngine { @@ -313,24 +315,11 @@ export type DefaultCatalogCollatorFactoryOptions = // @public @deprecated (undocumented) export type DeferredEntity = DeferredEntity_2; -// @public -export type EntitiesSearchFilter = { - key: string; - values?: string[]; -}; +// @public @deprecated (undocumented) +export type EntitiesSearchFilter = EntitiesSearchFilter_2; -// @public -export type EntityFilter = - | { - allOf: EntityFilter[]; - } - | { - anyOf: EntityFilter[]; - } - | { - not: EntityFilter; - } - | EntitiesSearchFilter; +// @public @deprecated (undocumented) +export type EntityFilter = EntityFilter_2; // @public @deprecated (undocumented) export type EntityProvider = EntityProvider_2; diff --git a/plugins/catalog-backend/optic.yml b/plugins/catalog-backend/optic.yml index 8b0c050516..dbbdefcf68 100644 --- a/plugins/catalog-backend/optic.yml +++ b/plugins/catalog-backend/optic.yml @@ -12,4 +12,4 @@ capture: # ℹ️ Requests should be sent to the Optic proxy, the address of which is injected into 'run.command's env as OPTIC_PROXY (or the value of 'run.proxy_variable'). run: # 🔧 Specify a command that will generate traffic - command: yarn backstage-cli package test --no-watch src/service/router.test.ts src/service/createRouter.test.ts + command: yarn backstage-cli package test --no-watch "src/service/router.test.ts" "src/service/createRouter.test.ts" diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 507a924959..d9ba73fe9e 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend", "description": "The Backstage backend plugin that provides the Backstage catalog", - "version": "1.15.1-next.0", + "version": "1.17.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -82,7 +82,7 @@ "winston": "^3.2.1", "yaml": "^2.0.0", "yn": "^4.0.0", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", @@ -90,6 +90,7 @@ "@backstage/plugin-permission-common": "workspace:^", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", + "@types/glob": "^8.0.0", "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/catalog-backend/src/alpha.ts b/plugins/catalog-backend/src/alpha.ts index 06c9571bfa..f85ef02c80 100644 --- a/plugins/catalog-backend/src/alpha.ts +++ b/plugins/catalog-backend/src/alpha.ts @@ -14,11 +14,5 @@ * limitations under the License. */ -// TODO(Rugvip): Re-exported for alpha types as the API report will otherwise -// produce warnings due to the indirect dependency. Would be nice to avoid. -import type { EntitiesSearchFilter } from './catalog/types'; - -export type { /** @alpha */ EntitiesSearchFilter }; - export * from './permissions'; export { catalogPlugin as default } from './service/CatalogPlugin'; diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index 156b8aa64d..e9f5f154c2 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -15,19 +15,7 @@ */ import { Entity } from '@backstage/catalog-model'; - -/** - * A filter expression for entities. - * - * Any (at least one) of the outer sets must match, within which all of the - * individual filters must match. - * @public - */ -export type EntityFilter = - | { allOf: EntityFilter[] } - | { anyOf: EntityFilter[] } - | { not: EntityFilter } - | EntitiesSearchFilter; +import { EntityFilter } from '@backstage/plugin-catalog-node'; /** * A pagination rule for entities. @@ -46,27 +34,6 @@ export type EntityOrder = { order: 'asc' | 'desc'; }; -/** - * Matches rows in the search table. - * @public - */ -export type EntitiesSearchFilter = { - /** - * The key to match on. - * - * Matches are always case insensitive. - */ - key: string; - - /** - * Match on plain equality of values. - * - * Match on values that are equal to any of the given array items. Matches are - * always case insensitive. - */ - values?: string[]; -}; - export type PageInfo = | { hasNextPage: false; diff --git a/plugins/catalog-backend/src/database/DefaultCatalogDatabase.test.ts b/plugins/catalog-backend/src/database/DefaultCatalogDatabase.test.ts index bcc20816a4..ebcdc77287 100644 --- a/plugins/catalog-backend/src/database/DefaultCatalogDatabase.test.ts +++ b/plugins/catalog-backend/src/database/DefaultCatalogDatabase.test.ts @@ -25,9 +25,7 @@ jest.setTimeout(60_000); describe('DefaultCatalogDatabase', () => { const defaultLogger = getVoidLogger(); - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); async function createDatabase( databaseId: TestDatabaseId, diff --git a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.test.ts b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.test.ts index db5153a8ba..080958da9b 100644 --- a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.test.ts +++ b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.test.ts @@ -37,9 +37,7 @@ jest.setTimeout(60_000); describe('DefaultProcessingDatabase', () => { const defaultLogger = getVoidLogger(); - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); async function createDatabase( databaseId: TestDatabaseId, diff --git a/plugins/catalog-backend/src/database/DefaultProviderDatabase.test.ts b/plugins/catalog-backend/src/database/DefaultProviderDatabase.test.ts index 19093b542b..0d124b6d33 100644 --- a/plugins/catalog-backend/src/database/DefaultProviderDatabase.test.ts +++ b/plugins/catalog-backend/src/database/DefaultProviderDatabase.test.ts @@ -28,9 +28,7 @@ jest.setTimeout(60_000); describe('DefaultProviderDatabase', () => { const defaultLogger = getVoidLogger(); - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); async function createDatabase( databaseId: TestDatabaseId, diff --git a/plugins/catalog-backend/src/database/operations/provider/deleteWithEagerPruningOfChildren.test.ts b/plugins/catalog-backend/src/database/operations/provider/deleteWithEagerPruningOfChildren.test.ts index 830cc20480..451f7bc1ac 100644 --- a/plugins/catalog-backend/src/database/operations/provider/deleteWithEagerPruningOfChildren.test.ts +++ b/plugins/catalog-backend/src/database/operations/provider/deleteWithEagerPruningOfChildren.test.ts @@ -28,9 +28,7 @@ import { deleteWithEagerPruningOfChildren } from './deleteWithEagerPruningOfChil jest.setTimeout(60_000); describe('deleteWithEagerPruningOfChildren', () => { - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); async function createDatabase(databaseId: TestDatabaseId) { const knex = await databases.init(databaseId); diff --git a/plugins/catalog-backend/src/database/operations/stitcher/getDeferredStitchableEntities.test.ts b/plugins/catalog-backend/src/database/operations/stitcher/getDeferredStitchableEntities.test.ts index a909515f14..b66dc74ef2 100644 --- a/plugins/catalog-backend/src/database/operations/stitcher/getDeferredStitchableEntities.test.ts +++ b/plugins/catalog-backend/src/database/operations/stitcher/getDeferredStitchableEntities.test.ts @@ -21,9 +21,7 @@ import { getDeferredStitchableEntities } from './getDeferredStitchableEntities'; jest.setTimeout(60_000); describe('getDeferredStitchableEntities', () => { - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); it.each(databases.eachSupportedId())( 'selects the right rows %p', diff --git a/plugins/catalog-backend/src/database/operations/stitcher/markDeferredStitchCompleted.test.ts b/plugins/catalog-backend/src/database/operations/stitcher/markDeferredStitchCompleted.test.ts index 76a6169cf4..b0957e0ec9 100644 --- a/plugins/catalog-backend/src/database/operations/stitcher/markDeferredStitchCompleted.test.ts +++ b/plugins/catalog-backend/src/database/operations/stitcher/markDeferredStitchCompleted.test.ts @@ -22,9 +22,7 @@ import { DbRefreshStateRow } from '../../tables'; jest.setTimeout(60_000); describe('markDeferredStitchCompleted', () => { - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); it.each(databases.eachSupportedId())( 'completes only if unchanged %p', diff --git a/plugins/catalog-backend/src/database/operations/stitcher/markForStitching.test.ts b/plugins/catalog-backend/src/database/operations/stitcher/markForStitching.test.ts index 2cae76040b..011fe54e83 100644 --- a/plugins/catalog-backend/src/database/operations/stitcher/markForStitching.test.ts +++ b/plugins/catalog-backend/src/database/operations/stitcher/markForStitching.test.ts @@ -22,9 +22,7 @@ import { DbFinalEntitiesRow, DbRefreshStateRow } from '../../tables'; jest.setTimeout(60_000); describe('markForStitching', () => { - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); it.each(databases.eachSupportedId())( 'marks the right rows in deferred mode %p', diff --git a/plugins/catalog-backend/src/database/operations/stitcher/performStitching.test.ts b/plugins/catalog-backend/src/database/operations/stitcher/performStitching.test.ts index a471f1dce2..e43424b361 100644 --- a/plugins/catalog-backend/src/database/operations/stitcher/performStitching.test.ts +++ b/plugins/catalog-backend/src/database/operations/stitcher/performStitching.test.ts @@ -30,9 +30,7 @@ import { performStitching } from './performStitching'; jest.setTimeout(60_000); describe('performStitching', () => { - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); const logger = getVoidLogger(); // NOTE(freben): Testing the deferred path since it's a superset of the immediate one diff --git a/plugins/catalog-backend/src/database/operations/util/deleteOrphanedEntities.test.ts b/plugins/catalog-backend/src/database/operations/util/deleteOrphanedEntities.test.ts index 3dacc8f09b..1dc8a42d5f 100644 --- a/plugins/catalog-backend/src/database/operations/util/deleteOrphanedEntities.test.ts +++ b/plugins/catalog-backend/src/database/operations/util/deleteOrphanedEntities.test.ts @@ -29,9 +29,7 @@ import { deleteOrphanedEntities } from './deleteOrphanedEntities'; jest.setTimeout(60_000); describe('deleteOrphanedEntities', () => { - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); async function createDatabase(databaseId: TestDatabaseId) { const knex = await databases.init(databaseId); diff --git a/plugins/catalog-backend/src/deprecated.ts b/plugins/catalog-backend/src/deprecated.ts index f3c199cbd3..2201bbbab4 100644 --- a/plugins/catalog-backend/src/deprecated.ts +++ b/plugins/catalog-backend/src/deprecated.ts @@ -18,6 +18,8 @@ import { locationSpecToMetadataName as _locationSpecToMetadataName, locationSpecToLocationEntity as _locationSpecToLocationEntity, processingResult as _processingResult, + type EntitiesSearchFilter as _EntitiesSearchFilter, + type EntityFilter as _EntityFilter, type DeferredEntity as _DeferredEntity, type EntityRelationSpec as _EntityRelationSpec, type CatalogProcessor as _CatalogProcessor, @@ -53,6 +55,16 @@ export const locationSpecToLocationEntity = _locationSpecToLocationEntity; * @deprecated import from `@backstage/plugin-catalog-node` instead */ export const processingResult = _processingResult; +/** + * @public + * @deprecated import from `@backstage/plugin-catalog-node` instead + */ +export type EntitiesSearchFilter = _EntitiesSearchFilter; +/** + * @public + * @deprecated import from `@backstage/plugin-catalog-node` instead + */ +export type EntityFilter = _EntityFilter; /** * @public * @deprecated import from `@backstage/plugin-catalog-node` instead diff --git a/plugins/catalog-backend/src/index.ts b/plugins/catalog-backend/src/index.ts index 55c1c672d3..d2f20d24d8 100644 --- a/plugins/catalog-backend/src/index.ts +++ b/plugins/catalog-backend/src/index.ts @@ -20,7 +20,6 @@ * @packageDocumentation */ -export * from './catalog'; export * from './ingestion'; export * from './modules'; export * from './processing'; diff --git a/plugins/catalog-backend/src/modules/core/DefaultLocationStore.test.ts b/plugins/catalog-backend/src/modules/core/DefaultLocationStore.test.ts index fca7faacf8..61eef2b8e5 100644 --- a/plugins/catalog-backend/src/modules/core/DefaultLocationStore.test.ts +++ b/plugins/catalog-backend/src/modules/core/DefaultLocationStore.test.ts @@ -22,9 +22,7 @@ import { DefaultLocationStore } from './DefaultLocationStore'; jest.setTimeout(60_000); describe('DefaultLocationStore', () => { - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); async function createLocationStore(databaseId: TestDatabaseId) { const knex = await databases.init(databaseId); diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts index c9575ce5cd..e2489df4b4 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts @@ -213,4 +213,34 @@ describe('UrlReaderProcessor', () => { expect(reader.search).toHaveBeenCalledTimes(1); }); + + it('parser return valid URL with wildcard *', async () => { + const logger = getVoidLogger(); + + const reader: jest.Mocked = { + readUrl: jest.fn(), + readTree: jest.fn(), + search: jest.fn().mockImplementation(async () => []), + }; + + const processor = new UrlReaderProcessor({ reader, logger }); + + const emit = jest.fn(); + + await processor.readLocation( + { + type: 'url', + target: 'https://storage.cloud.google.com/ah-backstage-poc-catalog/*', + }, + false, + emit, + defaultEntityDataParser, + mockCache, + ); + + expect(reader.search).toHaveBeenCalledWith( + 'https://storage.cloud.google.com/ah-backstage-poc-catalog/*', + { etag: undefined }, + ); + }); }); diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts index 5e31dcbd1a..c568f03a81 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts @@ -17,7 +17,6 @@ import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { assertError } from '@backstage/errors'; -import parseGitUrl from 'git-url-parse'; import limiterFactory from 'p-limit'; import { Logger } from 'winston'; import { LocationSpec } from '@backstage/plugin-catalog-common'; @@ -124,7 +123,8 @@ export class UrlReaderProcessor implements CatalogProcessor { ): Promise<{ response: { data: Buffer; url: string }[]; etag?: string }> { // Does it contain globs? I.e. does it contain asterisks or question marks // (no curly braces for now) - const { filepath } = parseGitUrl(location); + + const { pathname: filepath } = new URL(location); if (filepath?.match(/[*?]/)) { const limiter = limiterFactory(5); const response = await this.options.reader.search(location, { etag }); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index 8ebad5521c..568aae4838 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common/alpha'; import { z } from 'zod'; -import { EntitiesSearchFilter } from '../../catalog/types'; import { createCatalogPermissionRule } from './util'; /** @@ -36,7 +36,7 @@ export const isEntityKind = createCatalogPermissionRule({ const resourceKind = resource.kind.toLocaleLowerCase('en-US'); return kinds.some(kind => kind.toLocaleLowerCase('en-US') === resourceKind); }, - toQuery({ kinds }): EntitiesSearchFilter { + toQuery({ kinds }) { return { key: 'kind', values: kinds.map(kind => kind.toLocaleLowerCase('en-US')), diff --git a/plugins/catalog-backend/src/permissions/rules/util.ts b/plugins/catalog-backend/src/permissions/rules/util.ts index 466f5ce70b..c3e642490c 100644 --- a/plugins/catalog-backend/src/permissions/rules/util.ts +++ b/plugins/catalog-backend/src/permissions/rules/util.ts @@ -16,12 +16,12 @@ import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common/alpha'; +import { EntitiesSearchFilter } from '@backstage/plugin-catalog-node'; import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { makeCreatePermissionRule, PermissionRule, } from '@backstage/plugin-permission-node'; -import { EntitiesSearchFilter } from '../../catalog/types'; /** * Convenience type for {@link @backstage/plugin-permission-node#PermissionRule} diff --git a/plugins/catalog-backend/src/processing/TaskPipeline.ts b/plugins/catalog-backend/src/processing/TaskPipeline.ts index 44030be9e0..b808e41ce4 100644 --- a/plugins/catalog-backend/src/processing/TaskPipeline.ts +++ b/plugins/catalog-backend/src/processing/TaskPipeline.ts @@ -14,7 +14,11 @@ * limitations under the License. */ +import { TRACER_ID, withActiveSpan } from '../util/opentelemetry'; +import { trace } from '@opentelemetry/api'; + const DEFAULT_POLLING_INTERVAL_MS = 1000; +const tracer = trace.getTracer(TRACER_ID); type Options = { /** @@ -86,33 +90,35 @@ export function startTaskPipeline(options: Options) { async function pipelineLoop() { while (!abortSignal.aborted) { if (state.inFlightCount <= lowWatermark) { - const loadCount = highWatermark - state.inFlightCount; - const loadedItems = await Promise.resolve() - .then(() => loadTasks(loadCount)) - .catch(() => { - // Silently swallow errors and go back to sleep to try again; we - // delegate to the loadTasks function itself to catch errors and log - // if it so desires - return []; - }); - if (loadedItems.length && !abortSignal.aborted) { - state.inFlightCount += loadedItems.length; - for (const item of loadedItems) { - Promise.resolve() - .then(() => processTask(item)) - .catch(() => { - // Silently swallow errors and go back to sleep to try again; we - // delegate to the processTask function itself to catch errors - // and log if it so desires - }) - .finally(() => { - state.inFlightCount -= 1; - barrier.release(); - }); + await withActiveSpan(tracer, 'TaskPipelineLoop', async span => { + const loadCount = highWatermark - state.inFlightCount; + const loadedItems = await Promise.resolve() + .then(() => loadTasks(loadCount)) + .catch(() => { + // Silently swallow errors and go back to sleep to try again; we + // delegate to the loadTasks function itself to catch errors and log + // if it so desires + return []; + }); + span.setAttribute('itemCount', loadedItems.length); + if (loadedItems.length && !abortSignal.aborted) { + state.inFlightCount += loadedItems.length; + for (const item of loadedItems) { + Promise.resolve() + .then(() => processTask(item)) + .catch(() => { + // Silently swallow errors and go back to sleep to try again; we + // delegate to the processTask function itself to catch errors + // and log if it so desires + }) + .finally(() => { + state.inFlightCount -= 1; + barrier.release(); + }); + } } - } + }); } - await barrier.wait(); } } diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index a31615dfc6..c29dd78ec6 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -22,7 +22,7 @@ import { createValidatedOpenApiRouter } from '@backstage/backend-openapi-utils'; export const spec = { openapi: '3.0.3', info: { - title: '@backstage/plugin-catalog-backend', + title: 'catalog', version: '1', description: 'The Backstage backend plugin that provides the Backstage catalog', @@ -105,6 +105,7 @@ export const spec = { description: 'Restrict to just these fields in the response.', required: false, allowReserved: true, + explode: false, schema: { type: 'array', items: { @@ -248,12 +249,13 @@ export const spec = { }, }, required: ['error', 'response'], + additionalProperties: {}, }, JsonObject: { type: 'object', properties: {}, description: 'A type representing all allowed JSON object values.', - additionalProperties: true, + additionalProperties: {}, }, MapStringString: { type: 'object', @@ -291,70 +293,62 @@ export const spec = { additionalProperties: false, }, EntityMeta: { - allOf: [ - { - $ref: '#/components/schemas/JsonObject', - }, - { - type: 'object', - properties: { - links: { - type: 'array', - items: { - $ref: '#/components/schemas/EntityLink', - }, - description: - 'A list of external hyperlinks related to the entity.', - }, - tags: { - type: 'array', - items: { - type: 'string', - }, - description: - 'A list of single-valued strings, to for example classify catalog entities in\nvarious ways.', - }, - annotations: { - $ref: '#/components/schemas/MapStringString', - }, - labels: { - $ref: '#/components/schemas/MapStringString', - }, - description: { - type: 'string', - description: - 'A short (typically relatively few words, on one line) description of the\nentity.', - }, - title: { - type: 'string', - description: - 'A display name of the entity, to be presented in user interfaces instead\nof the `name` property above, when available.\nThis field is sometimes useful when the `name` is cumbersome or ends up\nbeing perceived as overly technical. The title generally does not have\nas stringent format requirements on it, so it may contain special\ncharacters and be more explanatory. Do keep it very short though, and\navoid situations where a title can be confused with the name of another\nentity, or where two entities share a title.\nNote that this is only for display purposes, and may be ignored by some\nparts of the code. Entity references still always make use of the `name`\nproperty, not the title.', - }, - namespace: { - type: 'string', - description: 'The namespace that the entity belongs to.', - }, - name: { - type: 'string', - description: - 'The name of the entity.\nMust be unique within the catalog at any given point in time, for any\ngiven namespace + kind pair. This value is part of the technical\nidentifier of the entity, and as such it will appear in URLs, database\ntables, entity references, and similar. It is subject to restrictions\nregarding what characters are allowed.\nIf you want to use a different, more human readable string with fewer\nrestrictions on it in user interfaces, see the `title` field below.', - }, - etag: { - type: 'string', - description: - 'An opaque string that changes for each update operation to any part of\nthe entity, including metadata.\nThis field can not be set by the user at creation time, and the server\nwill reject an attempt to do so. The field will be populated in read\noperations. The field can (optionally) be specified when performing\nupdate or delete operations, and the server will then reject the\noperation if it does not match the current stored value.', - }, - uid: { - type: 'string', - description: - 'A globally unique ID for the entity.\nThis field can not be set by the user at creation time, and the server\nwill reject an attempt to do so. The field will be populated in read\noperations. The field can (optionally) be specified when performing\nupdate or delete operations, but the server is free to reject requests\nthat do so in such a way that it breaks semantics.', - }, + type: 'object', + properties: { + links: { + type: 'array', + items: { + $ref: '#/components/schemas/EntityLink', }, - required: ['name'], + description: 'A list of external hyperlinks related to the entity.', }, - ], + tags: { + type: 'array', + items: { + type: 'string', + }, + description: + 'A list of single-valued strings, to for example classify catalog entities in\nvarious ways.', + }, + annotations: { + $ref: '#/components/schemas/MapStringString', + }, + labels: { + $ref: '#/components/schemas/MapStringString', + }, + description: { + type: 'string', + description: + 'A short (typically relatively few words, on one line) description of the\nentity.', + }, + title: { + type: 'string', + description: + 'A display name of the entity, to be presented in user interfaces instead\nof the `name` property above, when available.\nThis field is sometimes useful when the `name` is cumbersome or ends up\nbeing perceived as overly technical. The title generally does not have\nas stringent format requirements on it, so it may contain special\ncharacters and be more explanatory. Do keep it very short though, and\navoid situations where a title can be confused with the name of another\nentity, or where two entities share a title.\nNote that this is only for display purposes, and may be ignored by some\nparts of the code. Entity references still always make use of the `name`\nproperty, not the title.', + }, + namespace: { + type: 'string', + description: 'The namespace that the entity belongs to.', + }, + name: { + type: 'string', + description: + 'The name of the entity.\nMust be unique within the catalog at any given point in time, for any\ngiven namespace + kind pair. This value is part of the technical\nidentifier of the entity, and as such it will appear in URLs, database\ntables, entity references, and similar. It is subject to restrictions\nregarding what characters are allowed.\nIf you want to use a different, more human readable string with fewer\nrestrictions on it in user interfaces, see the `title` field below.', + }, + etag: { + type: 'string', + description: + 'An opaque string that changes for each update operation to any part of\nthe entity, including metadata.\nThis field can not be set by the user at creation time, and the server\nwill reject an attempt to do so. The field will be populated in read\noperations. The field can (optionally) be specified when performing\nupdate or delete operations, and the server will then reject the\noperation if it does not match the current stored value.', + }, + uid: { + type: 'string', + description: + 'A globally unique ID for the entity.\nThis field can not be set by the user at creation time, and the server\nwill reject an attempt to do so. The field will be populated in read\noperations. The field can (optionally) be specified when performing\nupdate or delete operations, but the server is free to reject requests\nthat do so in such a way that it breaks semantics.', + }, + }, + required: ['name'], description: 'Metadata fields common to all versions/kinds of entity.', - additionalProperties: true, + additionalProperties: {}, }, EntityRelation: { type: 'object', @@ -403,7 +397,6 @@ export const spec = { required: ['metadata', 'kind', 'apiVersion'], description: "The parts of the format that's common to all versions/kinds of entity.", - additionalProperties: true, }, NullableEntity: { type: 'object', @@ -435,7 +428,6 @@ export const spec = { required: ['metadata', 'kind', 'apiVersion'], description: "The parts of the format that's common to all versions/kinds of entity.", - additionalProperties: true, nullable: true, }, EntityAncestryResponse: { @@ -472,11 +464,7 @@ export const spec = { items: { type: 'array', items: { - anyOf: [ - { - $ref: '#/components/schemas/NullableEntity', - }, - ], + $ref: '#/components/schemas/NullableEntity', }, description: 'The list of entities, in the same order as the refs in the request. Entries\nthat are null signify that no entity existed with that ref.', @@ -495,6 +483,7 @@ export const spec = { type: 'number', }, }, + required: ['value', 'count'], additionalProperties: false, }, EntityFacetsResponse: { @@ -782,6 +771,7 @@ export const spec = { }, }, }, + required: ['items', 'totalItems', 'pageInfo'], additionalProperties: false, }, }, @@ -1076,11 +1066,6 @@ export const spec = { JWT: [], }, ], - parameters: [ - { - $ref: '#/components/parameters/fields', - }, - ], requestBody: { required: false, content: { @@ -1257,8 +1242,8 @@ export const spec = { operationId: 'CreateLocation', description: 'Create a location for a given target.', responses: { - '200': { - description: 'Ok', + '201': { + description: 'Created', content: { 'application/json': { schema: { @@ -1282,38 +1267,6 @@ export const spec = { }, }, }, - '201': { - description: '201 response', - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - location: { - type: 'object', - properties: { - id: { - type: 'string', - }, - type: { - type: 'string', - }, - target: { - type: 'string', - }, - }, - required: ['id', 'type', 'target'], - }, - entities: { - type: 'array', - items: {}, - }, - }, - required: ['location', 'entities'], - }, - }, - }, - }, '400': { $ref: '#/components/responses/ErrorResponse', }, @@ -1524,7 +1477,7 @@ export const spec = { description: 'Ok', }, '400': { - description: '400 response', + description: 'Validation errors.', content: { 'application/json; charset=utf-8': { schema: { @@ -1543,6 +1496,7 @@ export const spec = { }, }, required: ['name', 'message'], + additionalProperties: {}, }, }, }, @@ -1571,7 +1525,7 @@ export const spec = { }, entity: { type: 'object', - additionalProperties: true, + additionalProperties: {}, }, }, required: ['location', 'entity'], diff --git a/plugins/catalog-backend/src/schema/openapi.yaml b/plugins/catalog-backend/src/schema/openapi.yaml index 3375f7fe15..51283d5fbc 100644 --- a/plugins/catalog-backend/src/schema/openapi.yaml +++ b/plugins/catalog-backend/src/schema/openapi.yaml @@ -1,6 +1,6 @@ openapi: 3.0.3 info: - title: '@backstage/plugin-catalog-backend' + title: catalog version: '1' description: The Backstage backend plugin that provides the Backstage catalog license: @@ -65,6 +65,7 @@ components: description: Restrict to just these fields in the response. required: false allowReserved: true + explode: false schema: type: array items: @@ -180,11 +181,12 @@ components: required: - error - response + additionalProperties: {} JsonObject: type: object properties: {} description: A type representing all allowed JSON object values. - additionalProperties: true + additionalProperties: {} MapStringString: type: object properties: {} @@ -211,82 +213,80 @@ components: description: A link to external information that is related to the entity. additionalProperties: false EntityMeta: - allOf: - - $ref: '#/components/schemas/JsonObject' - - type: object - properties: - links: - type: array - items: - $ref: '#/components/schemas/EntityLink' - description: A list of external hyperlinks related to the entity. - tags: - type: array - items: - type: string - description: |- - A list of single-valued strings, to for example classify catalog entities in - various ways. - annotations: - $ref: '#/components/schemas/MapStringString' - labels: - $ref: '#/components/schemas/MapStringString' - description: - type: string - description: |- - A short (typically relatively few words, on one line) description of the - entity. - title: - type: string - description: |- - A display name of the entity, to be presented in user interfaces instead - of the `name` property above, when available. - This field is sometimes useful when the `name` is cumbersome or ends up - being perceived as overly technical. The title generally does not have - as stringent format requirements on it, so it may contain special - characters and be more explanatory. Do keep it very short though, and - avoid situations where a title can be confused with the name of another - entity, or where two entities share a title. - Note that this is only for display purposes, and may be ignored by some - parts of the code. Entity references still always make use of the `name` - property, not the title. - namespace: - type: string - description: The namespace that the entity belongs to. - name: - type: string - description: |- - The name of the entity. - Must be unique within the catalog at any given point in time, for any - given namespace + kind pair. This value is part of the technical - identifier of the entity, and as such it will appear in URLs, database - tables, entity references, and similar. It is subject to restrictions - regarding what characters are allowed. - If you want to use a different, more human readable string with fewer - restrictions on it in user interfaces, see the `title` field below. - etag: - type: string - description: |- - An opaque string that changes for each update operation to any part of - the entity, including metadata. - This field can not be set by the user at creation time, and the server - will reject an attempt to do so. The field will be populated in read - operations. The field can (optionally) be specified when performing - update or delete operations, and the server will then reject the - operation if it does not match the current stored value. - uid: - type: string - description: |- - A globally unique ID for the entity. - This field can not be set by the user at creation time, and the server - will reject an attempt to do so. The field will be populated in read - operations. The field can (optionally) be specified when performing - update or delete operations, but the server is free to reject requests - that do so in such a way that it breaks semantics. - required: - - name + type: object + properties: + links: + type: array + items: + $ref: '#/components/schemas/EntityLink' + description: A list of external hyperlinks related to the entity. + tags: + type: array + items: + type: string + description: |- + A list of single-valued strings, to for example classify catalog entities in + various ways. + annotations: + $ref: '#/components/schemas/MapStringString' + labels: + $ref: '#/components/schemas/MapStringString' + description: + type: string + description: |- + A short (typically relatively few words, on one line) description of the + entity. + title: + type: string + description: |- + A display name of the entity, to be presented in user interfaces instead + of the `name` property above, when available. + This field is sometimes useful when the `name` is cumbersome or ends up + being perceived as overly technical. The title generally does not have + as stringent format requirements on it, so it may contain special + characters and be more explanatory. Do keep it very short though, and + avoid situations where a title can be confused with the name of another + entity, or where two entities share a title. + Note that this is only for display purposes, and may be ignored by some + parts of the code. Entity references still always make use of the `name` + property, not the title. + namespace: + type: string + description: The namespace that the entity belongs to. + name: + type: string + description: |- + The name of the entity. + Must be unique within the catalog at any given point in time, for any + given namespace + kind pair. This value is part of the technical + identifier of the entity, and as such it will appear in URLs, database + tables, entity references, and similar. It is subject to restrictions + regarding what characters are allowed. + If you want to use a different, more human readable string with fewer + restrictions on it in user interfaces, see the `title` field below. + etag: + type: string + description: |- + An opaque string that changes for each update operation to any part of + the entity, including metadata. + This field can not be set by the user at creation time, and the server + will reject an attempt to do so. The field will be populated in read + operations. The field can (optionally) be specified when performing + update or delete operations, and the server will then reject the + operation if it does not match the current stored value. + uid: + type: string + description: |- + A globally unique ID for the entity. + This field can not be set by the user at creation time, and the server + will reject an attempt to do so. The field will be populated in read + operations. The field can (optionally) be specified when performing + update or delete operations, but the server is free to reject requests + that do so in such a way that it breaks semantics. + required: + - name description: Metadata fields common to all versions/kinds of entity. - additionalProperties: true + additionalProperties: {} EntityRelation: type: object properties: @@ -326,7 +326,6 @@ components: - kind - apiVersion description: The parts of the format that's common to all versions/kinds of entity. - additionalProperties: true NullableEntity: type: object properties: @@ -352,7 +351,6 @@ components: - kind - apiVersion description: The parts of the format that's common to all versions/kinds of entity. - additionalProperties: true nullable: true EntityAncestryResponse: type: object @@ -383,8 +381,7 @@ components: items: type: array items: - anyOf: - - $ref: '#/components/schemas/NullableEntity' + $ref: '#/components/schemas/NullableEntity' description: |- The list of entities, in the same order as the refs in the request. Entries that are null signify that no entity existed with that ref. @@ -398,6 +395,9 @@ components: type: string count: type: number + required: + - value + - count additionalProperties: false EntityFacetsResponse: type: object @@ -660,6 +660,10 @@ components: prevCursor: type: string description: The cursor for the previous batch of entities. + required: + - items + - totalItems + - pageInfo additionalProperties: false securitySchemes: JWT: @@ -829,8 +833,6 @@ paths: security: - {} - JWT: [] - parameters: - - $ref: '#/components/parameters/fields' requestBody: required: false content: @@ -942,8 +944,8 @@ paths: operationId: CreateLocation description: Create a location for a given target. responses: - '200': - description: Ok + '201': + description: Created content: application/json: schema: @@ -960,32 +962,6 @@ paths: required: - entities - location - '201': - description: 201 response - content: - application/json: - schema: - type: object - properties: - location: - type: object - properties: - id: - type: string - type: - type: string - target: - type: string - required: - - id - - type - - target - entities: - type: array - items: {} - required: - - location - - entities '400': $ref: '#/components/responses/ErrorResponse' default: @@ -1120,7 +1096,7 @@ paths: '200': description: Ok '400': - description: 400 response + description: Validation errors. content: application/json; charset=utf-8: schema: @@ -1138,6 +1114,7 @@ paths: required: - name - message + additionalProperties: {} required: - errors security: @@ -1155,7 +1132,7 @@ paths: type: string entity: type: object - additionalProperties: true + additionalProperties: {} required: - location - entity diff --git a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts index 2ee47dae19..ce9221e16e 100644 --- a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts @@ -20,8 +20,9 @@ import { createConditionTransformer } from '@backstage/plugin-permission-node'; import { isEntityKind } from '../permissions/rules/isEntityKind'; import { CatalogPermissionRule } from '../permissions/rules'; import { AuthorizedEntitiesCatalog } from './AuthorizedEntitiesCatalog'; -import { Cursor, EntityFilter, QueryEntitiesResponse } from '../catalog/types'; +import { Cursor, QueryEntitiesResponse } from '../catalog/types'; import { Entity } from '@backstage/catalog-model'; +import { EntityFilter } from '@backstage/plugin-catalog-node'; describe('AuthorizedEntitiesCatalog', () => { const fakeCatalog = { diff --git a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.ts b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.ts index 0ce1ee87e7..6a04c4d7a1 100644 --- a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.ts @@ -35,12 +35,12 @@ import { EntityAncestryResponse, EntityFacetsRequest, EntityFacetsResponse, - EntityFilter, QueryEntitiesRequest, QueryEntitiesResponse, } from '../catalog/types'; import { basicEntityFilter } from './request/basicEntityFilter'; import { isQueryEntitiesCursorRequest } from './util'; +import { EntityFilter } from '@backstage/plugin-catalog-node'; export class AuthorizedEntitiesCatalog implements EntitiesCatalog { constructor( diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 5cf76ee1ac..f8ed166118 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -37,6 +37,7 @@ import lodash, { keyBy } from 'lodash'; import { CatalogProcessor, CatalogProcessorParser, + EntitiesSearchFilter, EntityProvider, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-node'; @@ -79,7 +80,6 @@ import { Config, readDurationFromConfig } from '@backstage/config'; import { Logger } from 'winston'; import { connectEntityProviders } from '../processing/connectEntityProviders'; import { PermissionRuleParams } from '@backstage/plugin-permission-common'; -import { EntitiesSearchFilter } from '../catalog/types'; import { permissionRules as catalogPermissionRules } from '../permissions/rules'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index fa53ef510b..0800d41340 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -17,12 +17,14 @@ import { createBackendPlugin, coreServices, } from '@backstage/backend-plugin-api'; -import { CatalogBuilder } from './CatalogBuilder'; +import { CatalogBuilder, CatalogPermissionRuleInput } from './CatalogBuilder'; import { CatalogAnalysisExtensionPoint, catalogAnalysisExtensionPoint, CatalogProcessingExtensionPoint, catalogProcessingExtensionPoint, + CatalogPermissionExtensionPoint, + catalogPermissionExtensionPoint, } from '@backstage/plugin-catalog-node/alpha'; import { CatalogProcessor, @@ -86,6 +88,24 @@ class CatalogAnalysisExtensionPointImpl } } +class CatalogPermissionExtensionPointImpl + implements CatalogPermissionExtensionPoint +{ + #permissionRules = new Array(); + + addPermissionRules( + ...rules: Array< + CatalogPermissionRuleInput | Array + > + ): void { + this.#permissionRules.push(...rules.flat()); + } + + get permissionRules() { + return this.#permissionRules; + } +} + /** * Catalog plugin * @alpha @@ -106,6 +126,12 @@ export const catalogPlugin = createBackendPlugin({ analysisExtensions, ); + const permissionExtensions = new CatalogPermissionExtensionPointImpl(); + env.registerExtensionPoint( + catalogPermissionExtensionPoint, + permissionExtensions, + ); + env.registerInit({ deps: { logger: coreServices.logger, @@ -142,6 +168,7 @@ export const catalogPlugin = createBackendPlugin({ ([key, resolver]) => builder.setPlaceholderResolver(key, resolver), ); builder.addLocationAnalyzers(...analysisExtensions.locationAnalyzers); + builder.addPermissionRules(...permissionExtensions.permissionRules); const { processingEngine, router } = await builder.build(); diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index fde3408c4b..fe7d57c89a 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -44,9 +44,7 @@ describe('DefaultEntitiesCatalog', () => { await knex.destroy(); }); - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); const stitch = jest.fn(); const stitcher: Stitcher = { stitch } as any; diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index 24f0432419..0245fe1d4e 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -31,11 +31,9 @@ import { EntitiesCatalog, EntitiesRequest, EntitiesResponse, - EntitiesSearchFilter, EntityAncestryResponse, EntityFacetsRequest, EntityFacetsResponse, - EntityFilter, EntityPagination, QueryEntitiesRequest, QueryEntitiesResponse, @@ -55,6 +53,10 @@ import { isQueryEntitiesCursorRequest, isQueryEntitiesInitialRequest, } from './util'; +import { + EntitiesSearchFilter, + EntityFilter, +} from '@backstage/plugin-catalog-node'; const defaultSortField: EntityOrder = { field: 'metadata.uid', diff --git a/plugins/catalog-backend/src/service/DefaultRefreshService.test.ts b/plugins/catalog-backend/src/service/DefaultRefreshService.test.ts index 5b29bb34d5..e1b775cce6 100644 --- a/plugins/catalog-backend/src/service/DefaultRefreshService.test.ts +++ b/plugins/catalog-backend/src/service/DefaultRefreshService.test.ts @@ -39,9 +39,7 @@ jest.setTimeout(60_000); describe('DefaultRefreshService', () => { const defaultLogger = getVoidLogger(); - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); async function createDatabase( databaseId: TestDatabaseId, diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 700e35edc1..05ebf6e1a9 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -198,6 +198,45 @@ describe('createRouter readonly disabled', () => { }); }); + it('parses encoded params request', async () => { + entitiesCatalog.queryEntities.mockResolvedValueOnce({ + items: [], + pageInfo: {}, + totalItems: 0, + }); + const response = await request(app).get( + `/entities/by-query?filter=${encodeURIComponent( + 'a=1,a=2,b=3', + )}&filter=c=4&orderField=${encodeURIComponent( + 'metadata.name,asc', + )}&orderField=metadata.uid,desc`, + ); + + expect(response.status).toEqual(200); + expect(entitiesCatalog.queryEntities).toHaveBeenCalledTimes(1); + expect(entitiesCatalog.queryEntities).toHaveBeenCalledWith({ + filter: { + anyOf: [ + { + allOf: [ + { key: 'a', values: ['1', '2'] }, + { key: 'b', values: ['3'] }, + ], + }, + { allOf: [{ key: 'c', values: ['4'] }] }, + ], + }, + orderFields: [ + { field: 'metadata.name', order: 'asc' }, + { field: 'metadata.uid', order: 'desc' }, + ], + fullTextFilter: { + fields: undefined, + term: '', + }, + }); + }); + it('parses cursor request', async () => { const items: Entity[] = [ { apiVersion: 'a', kind: 'b', metadata: { name: 'n' } }, diff --git a/plugins/catalog-backend/src/service/request/basicEntityFilter.ts b/plugins/catalog-backend/src/service/request/basicEntityFilter.ts index 36448e0304..43824160a2 100644 --- a/plugins/catalog-backend/src/service/request/basicEntityFilter.ts +++ b/plugins/catalog-backend/src/service/request/basicEntityFilter.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { EntitiesSearchFilter, EntityFilter } from '../../catalog'; +import { + EntitiesSearchFilter, + EntityFilter, +} from '@backstage/plugin-catalog-node'; /** * Forms a full EntityFilter based on a single key-value(s) object. diff --git a/plugins/catalog-backend/src/service/request/parseEntityFilterParams.ts b/plugins/catalog-backend/src/service/request/parseEntityFilterParams.ts index 767a36ce71..cfd9aa0aa5 100644 --- a/plugins/catalog-backend/src/service/request/parseEntityFilterParams.ts +++ b/plugins/catalog-backend/src/service/request/parseEntityFilterParams.ts @@ -15,8 +15,11 @@ */ import { InputError } from '@backstage/errors'; -import { EntitiesSearchFilter, EntityFilter } from '../../catalog'; import { parseStringsParam } from './common'; +import { + EntitiesSearchFilter, + EntityFilter, +} from '@backstage/plugin-catalog-node'; /** * Parses the filtering part of a query, like diff --git a/plugins/catalog-backend/src/service/util.ts b/plugins/catalog-backend/src/service/util.ts index 699bca2fc9..af1279a59a 100644 --- a/plugins/catalog-backend/src/service/util.ts +++ b/plugins/catalog-backend/src/service/util.ts @@ -20,11 +20,11 @@ import lodash from 'lodash'; import { z } from 'zod'; import { Cursor, - EntityFilter, QueryEntitiesCursorRequest, QueryEntitiesInitialRequest, QueryEntitiesRequest, } from '../catalog/types'; +import { EntityFilter } from '@backstage/plugin-catalog-node'; export async function requireRequestBody(req: Request): Promise { const contentType = req.header('content-type'); diff --git a/plugins/catalog-backend/src/setupTests.ts b/plugins/catalog-backend/src/setupTests.ts index d3232290a7..f7c56ef27d 100644 --- a/plugins/catalog-backend/src/setupTests.ts +++ b/plugins/catalog-backend/src/setupTests.ts @@ -14,4 +14,10 @@ * limitations under the License. */ +import { TestDatabases } from '@backstage/backend-test-utils'; + export {}; + +TestDatabases.setDefaults({ + ids: ['MYSQL_8', 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'], +}); diff --git a/plugins/catalog-backend/src/stitching/DefaultStitcher.test.ts b/plugins/catalog-backend/src/stitching/DefaultStitcher.test.ts index 5b33fc424c..5e7ba7ef71 100644 --- a/plugins/catalog-backend/src/stitching/DefaultStitcher.test.ts +++ b/plugins/catalog-backend/src/stitching/DefaultStitcher.test.ts @@ -30,9 +30,7 @@ import { DefaultStitcher } from './DefaultStitcher'; jest.setTimeout(60_000); describe('Stitcher', () => { - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); const logger = getVoidLogger(); it.each(databases.eachSupportedId())( diff --git a/plugins/catalog-backend/src/tests/migrations.test.ts b/plugins/catalog-backend/src/tests/migrations.test.ts index aa305598aa..d961427f5e 100644 --- a/plugins/catalog-backend/src/tests/migrations.test.ts +++ b/plugins/catalog-backend/src/tests/migrations.test.ts @@ -42,9 +42,7 @@ async function migrateUntilBefore(knex: Knex, target: string): Promise { jest.setTimeout(60_000); describe('migrations', () => { - const databases = TestDatabases.create({ - ids: ['MYSQL_8', 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], - }); + const databases = TestDatabases.create(); it.each(databases.eachSupportedId())( 'latest version correctly cascades deletions, %p', diff --git a/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts b/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts index ad659ce845..e0d3f87124 100644 --- a/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts +++ b/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts @@ -155,7 +155,7 @@ class Tracker { describePerformanceTest('stitchingPerformance', () => { const databases = TestDatabases.create({ - ids: [/* 'MYSQL_8', */ 'POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], + ids: [/* 'MYSQL_8', */ 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'], }); it.each(databases.eachSupportedId())( @@ -188,8 +188,8 @@ describePerformanceTest('stitchingPerformance', () => { factory: () => ({ getClient: async () => knex }), }), createBackendModule({ - moduleId: 'syntheticLoadEntities', pluginId: 'catalog', + moduleId: 'synthetic-load-entities', register(reg) { reg.registerInit({ deps: { @@ -245,8 +245,8 @@ describePerformanceTest('stitchingPerformance', () => { factory: () => ({ getClient: async () => knex }), }), createBackendModule({ - moduleId: 'syntheticLoadEntities', pluginId: 'catalog', + moduleId: 'synthetic-load-entities', register(reg) { reg.registerInit({ deps: { diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index 15582a68fc..9f2b511676 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-common +## 1.0.20 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-search-common@1.2.10 + +## 1.0.19 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-search-common@1.2.9 + ## 1.0.18 ### Patch Changes diff --git a/plugins/catalog-common/alpha-api-report.md b/plugins/catalog-common/api-report-alpha.md similarity index 100% rename from plugins/catalog-common/alpha-api-report.md rename to plugins/catalog-common/api-report-alpha.md diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 45720ee32a..9642746fd7 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-common", "description": "Common functionalities for the catalog plugin", - "version": "1.0.18", + "version": "1.0.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 7c5667aa3b..a377f5ca68 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,115 @@ # @backstage/plugin-catalog-graph +## 0.3.4-next.0 + +### Patch Changes + +- 916da47: Change default icon for unknown entities to nothing instead of the help icon. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/types@1.1.1 + +## 0.3.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## 0.3.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/types@1.1.1 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## 0.3.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + ## 0.3.2-next.0 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index a8be8619a4..709e764531 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.3.2-next.0", + "version": "0.3.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,7 +34,6 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.tsx index 88de8df686..d55f4c015b 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ import { DependencyGraphTypes } from '@backstage/core-components'; +import { useApp } from '@backstage/core-plugin-api'; import { humanizeEntityRef } from '@backstage/plugin-catalog-react'; import { makeStyles } from '@material-ui/core/styles'; import classNames from 'classnames'; @@ -63,6 +64,7 @@ export function DefaultRenderNode({ const classes = useStyles(); const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); + const app = useApp(); const idRef = useRef(null); useLayoutEffect(() => { @@ -85,9 +87,12 @@ export function DefaultRenderNode({ metadata: { name, namespace = DEFAULT_NAMESPACE, title }, } = entity; + const hasKindIcon = app.getSystemIcon( + `kind:${kind.toLocaleLowerCase('en-US')}`, + ); const padding = 10; const iconSize = height; - const paddedIconWidth = kind ? iconSize + padding : 0; + const paddedIconWidth = hasKindIcon ? iconSize + padding : 0; const paddedWidth = paddedIconWidth + width + padding * 2; const paddedHeight = height + padding * 2; @@ -109,7 +114,7 @@ export function DefaultRenderNode({ height={paddedHeight} rx={10} /> - {kind && ( + {hasKindIcon && ( ; } diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index b5e55fa324..b77f3faf2b 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,166 @@ # @backstage/plugin-catalog-import +## 0.10.6-next.0 + +### Patch Changes + +- 916da47: Change default icon for unknown entities to nothing instead of the help icon. +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.23 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.10.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.10.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## 0.10.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.10.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.10.4 + +### Patch Changes + +- 03d0b6d: The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/catalog-client@1.5.0 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.10.4-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.10.4-next.3 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-catalog-common@1.0.18 + +## 0.10.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.18 + ## 0.10.4-next.1 ### Patch Changes diff --git a/plugins/catalog-import/alpha-api-report.md b/plugins/catalog-import/api-report-alpha.md similarity index 100% rename from plugins/catalog-import/alpha-api-report.md rename to plugins/catalog-import/api-report-alpha.md diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 23e0ea6755..066a073c48 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-import", "description": "A Backstage plugin the helps you import entities into your catalog", - "version": "0.10.4-next.1", + "version": "0.10.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 18ddda2ceb..304ee3f272 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -20,7 +20,10 @@ import { discoveryApiRef, identityApiRef, } from '@backstage/core-plugin-api'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; import { createApiExtension, createPageExtension, @@ -37,14 +40,16 @@ import { catalogApiRef } from '@backstage/plugin-catalog-react'; // TODO: It's currently possible to override the import page with a custom one. We need to decide // whether this type of override is typically done with an input or by overriding the entire extension. -const CatalogImportPageExtension = createPageExtension({ - id: 'plugin.catalog-import.page', +const catalogImportPage = createPageExtension({ defaultPath: '/catalog-import', routeRef: convertLegacyRouteRef(rootRouteRef), - loader: () => import('./components/ImportPage').then(m => ), + loader: () => + import('./components/ImportPage').then(m => + compatWrapper(), + ), }); -const CatalogImportService = createApiExtension({ +const catalogImportApi = createApiExtension({ factory: createApiFactory({ api: catalogImportApiRef, deps: { @@ -77,7 +82,7 @@ const CatalogImportService = createApiExtension({ /** @alpha */ export default createPlugin({ id: 'catalog-import', - extensions: [CatalogImportService, CatalogImportPageExtension], + extensions: [catalogImportApi, catalogImportPage], routes: { importPage: convertLegacyRouteRef(rootRouteRef), }, diff --git a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx index dac83a4817..86030758a7 100644 --- a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx +++ b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx @@ -32,7 +32,6 @@ import { import { makeStyles } from '@material-ui/core/styles'; import ExpandLessIcon from '@material-ui/icons/ExpandLess'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import WorkIcon from '@material-ui/icons/Work'; import React, { useState } from 'react'; const useStyles = makeStyles(theme => ({ @@ -127,10 +126,9 @@ export const EntityListComponent = (props: EntityListComponentProps) => { > {sortEntities(r.entities).map(entity => { - const Icon = - app.getSystemIcon( - `kind:${entity.kind.toLocaleLowerCase('en-US')}`, - ) ?? WorkIcon; + const Icon = app.getSystemIcon( + `kind:${entity.kind.toLocaleLowerCase('en-US')}`, + ); return ( { } : {})} > - - - + {Icon && } ); diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index 20e5462e95..7327411bf2 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,135 @@ # @backstage/plugin-catalog-node +## 1.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-common@0.7.12 + +## 1.6.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## 1.6.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## 1.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + +## 1.6.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## 1.6.0 + +### Minor Changes + +- a168507: Added `EntitiesSearchFilter` and `EntityFilter` from `@backstage/plugin-catalog-backend`, for reuse +- 7804597: Permission rules can now be added for the Catalog plugin through the `CatalogPermissionExtensionPoint` interface. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + +## 1.6.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## 1.6.0-next.2 + +### Minor Changes + +- a168507: Added `EntitiesSearchFilter` and `EntityFilter` from `@backstage/plugin-catalog-backend`, for reuse +- 7804597: Permission rules can now be added for the Catalog plugin through the `CatalogPermissionExtensionPoint` interface. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## 1.5.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + ## 1.5.1-next.0 ### Patch Changes diff --git a/plugins/catalog-node/alpha-api-report.md b/plugins/catalog-node/api-report-alpha.md similarity index 65% rename from plugins/catalog-node/alpha-api-report.md rename to plugins/catalog-node/api-report-alpha.md index c4dc6b12e1..74339b1bc9 100644 --- a/plugins/catalog-node/alpha-api-report.md +++ b/plugins/catalog-node/api-report-alpha.md @@ -5,8 +5,12 @@ ```ts import { CatalogApi } from '@backstage/catalog-client'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; +import { EntitiesSearchFilter } from '@backstage/plugin-catalog-node'; +import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { PermissionRule } from '@backstage/plugin-permission-node'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PlaceholderResolver } from '@backstage/plugin-catalog-node'; import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-node'; import { ServiceRef } from '@backstage/backend-plugin-api'; @@ -20,6 +24,24 @@ export interface CatalogAnalysisExtensionPoint { // @alpha (undocumented) export const catalogAnalysisExtensionPoint: ExtensionPoint; +// @alpha (undocumented) +export interface CatalogPermissionExtensionPoint { + // (undocumented) + addPermissionRules( + ...rules: Array< + CatalogPermissionRuleInput | Array + > + ): void; +} + +// @alpha (undocumented) +export const catalogPermissionExtensionPoint: ExtensionPoint; + +// @alpha (undocumented) +export type CatalogPermissionRuleInput< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule; + // @alpha (undocumented) export interface CatalogProcessingExtensionPoint { // (undocumented) diff --git a/plugins/catalog-node/api-report.md b/plugins/catalog-node/api-report.md index ef2c09e61f..a4ba95073f 100644 --- a/plugins/catalog-node/api-report.md +++ b/plugins/catalog-node/api-report.md @@ -105,6 +105,25 @@ export type DeferredEntity = { locationKey?: string; }; +// @public +export type EntitiesSearchFilter = { + key: string; + values?: string[]; +}; + +// @public +export type EntityFilter = + | { + allOf: EntityFilter[]; + } + | { + anyOf: EntityFilter[]; + } + | { + not: EntityFilter; + } + | EntitiesSearchFilter; + // @public export interface EntityProvider { connect(connection: EntityProviderConnection): Promise; diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index f07ffd2c91..944474bb9b 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-node", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", - "version": "1.5.1-next.0", + "version": "1.6.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -47,10 +47,11 @@ "@backstage/catalog-model": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", + "@backstage/plugin-permission-node": "workspace:^", "@backstage/types": "workspace:^" }, "devDependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^" }, diff --git a/plugins/catalog-node/src/alpha.ts b/plugins/catalog-node/src/alpha.ts index cec1a0eedb..3e958d1df1 100644 --- a/plugins/catalog-node/src/alpha.ts +++ b/plugins/catalog-node/src/alpha.ts @@ -19,3 +19,6 @@ export type { CatalogProcessingExtensionPoint } from './extensions'; export { catalogProcessingExtensionPoint } from './extensions'; export type { CatalogAnalysisExtensionPoint } from './extensions'; export { catalogAnalysisExtensionPoint } from './extensions'; +export type { CatalogPermissionRuleInput } from './extensions'; +export type { CatalogPermissionExtensionPoint } from './extensions'; +export { catalogPermissionExtensionPoint } from './extensions'; diff --git a/plugins/catalog-node/src/api/common.ts b/plugins/catalog-node/src/api/common.ts index c91aeca2d9..7f9bd3eabf 100644 --- a/plugins/catalog-node/src/api/common.ts +++ b/plugins/catalog-node/src/api/common.ts @@ -52,3 +52,36 @@ export type EntityRelationSpec = { */ target: CompoundEntityRef; }; + +/** + * A filter expression for entities. + * + * @public + */ +export type EntityFilter = + | { allOf: EntityFilter[] } + | { anyOf: EntityFilter[] } + | { not: EntityFilter } + | EntitiesSearchFilter; + +/** + * Matches rows in the search table. + * + * @public + */ +export type EntitiesSearchFilter = { + /** + * The key to match on. + * + * Matches are always case insensitive. + */ + key: string; + + /** + * Match on plain equality of values. + * + * Match on values that are equal to any of the given array items. Matches are + * always case insensitive. + */ + values?: string[]; +}; diff --git a/plugins/catalog-node/src/api/index.ts b/plugins/catalog-node/src/api/index.ts index 66e8d6bd21..292a6cd872 100644 --- a/plugins/catalog-node/src/api/index.ts +++ b/plugins/catalog-node/src/api/index.ts @@ -15,7 +15,12 @@ */ export { processingResult } from './processingResult'; -export type { EntityRelationSpec, LocationSpec } from './common'; +export type { + EntityRelationSpec, + LocationSpec, + EntitiesSearchFilter, + EntityFilter, +} from './common'; export type { CatalogProcessor, CatalogProcessorParser, diff --git a/plugins/catalog-node/src/extensions.ts b/plugins/catalog-node/src/extensions.ts index 247ef142ad..7aa9264030 100644 --- a/plugins/catalog-node/src/extensions.ts +++ b/plugins/catalog-node/src/extensions.ts @@ -13,13 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { createExtensionPoint } from '@backstage/backend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; import { - EntityProvider, CatalogProcessor, + EntitiesSearchFilter, + EntityProvider, PlaceholderResolver, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-node'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; +import { PermissionRule } from '@backstage/plugin-permission-node'; /** * @alpha @@ -56,3 +61,29 @@ export const catalogAnalysisExtensionPoint = createExtensionPoint({ id: 'catalog.analysis', }); + +/** + * @alpha + */ +export type CatalogPermissionRuleInput< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule; + +/** + * @alpha + */ +export interface CatalogPermissionExtensionPoint { + addPermissionRules( + ...rules: Array< + CatalogPermissionRuleInput | Array + > + ): void; +} + +/** + * @alpha + */ +export const catalogPermissionExtensionPoint = + createExtensionPoint({ + id: 'catalog.permission', + }); diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 7bbbc00716..4bd71d05fe 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,188 @@ # @backstage/plugin-catalog-react +## 1.9.4-next.0 + +### Patch Changes + +- 916da47: Change default icon for unknown entities to nothing instead of the help icon. +- 71c6d7a: Overflowing labels in OwnerPicker (Catalog) are now truncated. Hovering over them shows the full label +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration-react@1.1.23 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-react@0.4.19 + +## 1.9.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.20 + +## 1.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## 1.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + +## 1.9.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + +## 1.9.2 + +### Patch Changes + +- 8587f06: Added pagination support to `EntityListProvider`. +- 5360097: Ensure that passed-in icons are taken advantage of in the presentation API +- fd9863c: Grouped all `/alpha` extension data reference exports under `catalogExtensionData`. +- 08d9e67: Add default icon for kind resource. +- aaa6fb3: Minor updates for TypeScript 5.2.2+ compatibility +- a5a0473: Internal refactor of alpha exports due to a change in how extension factories are defined. +- 4d9e3b3: Register component overrides in the global `OverrideComponentNameToClassKeys` provided by `@backstage/theme`. This will in turn will provide component style override types for `createUnifiedTheme`. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- e223f22: Breaking alpha-API change to entity visibility filter functions to accept a bare entity as their first argument, instead of an object with an entity property. + + Functions that accept such filters now also support the string expression form of filters. + +- eee0ff2: Fixed a issue where `CatalogPage` wasn't using the chosen `initiallySelectedFilter` as intended. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.19 + +## 1.9.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## 1.9.2-next.2 + +### Patch Changes + +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## 1.9.2-next.1 + +### Patch Changes + +- 53600976bb: Ensure that passed-in icons are taken advantage of in the presentation API +- 08d9e67199: Add default icon for kind resource. +- a5a04739e1: Internal refactor of alpha exports due to a change in how extension factories are defined. +- e223f2264d: Breaking alpha-API change to entity visibility filter functions to accept a bare entity as their first argument, instead of an object with an entity property. + + Functions that accept such filters now also support the string expression form of filters. + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + ## 1.9.2-next.0 ### Patch Changes diff --git a/plugins/catalog-react/alpha-api-report.md b/plugins/catalog-react/api-report-alpha.md similarity index 64% rename from plugins/catalog-react/alpha-api-report.md rename to plugins/catalog-react/api-report-alpha.md index ed851f810a..cf9fdc6c3a 100644 --- a/plugins/catalog-react/alpha-api-report.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -8,40 +8,49 @@ import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; -import { Extension } from '@backstage/frontend-plugin-api'; -import { ExtensionInputValues } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ResolvedExtensionInputs } from '@backstage/frontend-plugin-api'; import { ResourcePermission } from '@backstage/plugin-permission-common'; import { RouteRef } from '@backstage/frontend-plugin-api'; +// @alpha (undocumented) +export const catalogExtensionData: { + entityContentTitle: ConfigurableExtensionDataRef; + entityFilterFunction: ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + {} + >; + entityFilterExpression: ConfigurableExtensionDataRef; +}; + // @alpha (undocumented) export function createEntityCardExtension< TInputs extends AnyExtensionInputMap, >(options: { - id: string; + namespace?: string; + name?: string; attachTo?: { id: string; input: string; }; disabled?: boolean; inputs?: TInputs; - filter?: (ctx: { entity: Entity }) => boolean; - loader: (options: { - inputs: Expand>; - }) => Promise; -}): Extension<{ filter?: - | { - isKind?: string | undefined; - isType?: string | undefined; - }[] - | undefined; + | typeof catalogExtensionData.entityFilterFunction.T + | typeof catalogExtensionData.entityFilterExpression.T; + loader: (options: { + inputs: Expand>; + }) => Promise; +}): ExtensionDefinition<{ + filter?: string | undefined; }>; // @alpha (undocumented) export function createEntityContentExtension< TInputs extends AnyExtensionInputMap, >(options: { - id: string; + namespace?: string; + name?: string; attachTo?: { id: string; input: string; @@ -51,33 +60,18 @@ export function createEntityContentExtension< routeRef?: RouteRef; defaultPath: string; defaultTitle: string; - filter?: (ctx: { entity: Entity }) => boolean; + filter?: + | typeof catalogExtensionData.entityFilterFunction.T + | typeof catalogExtensionData.entityFilterExpression.T; loader: (options: { - inputs: Expand>; + inputs: Expand>; }) => Promise; -}): Extension<{ +}): ExtensionDefinition<{ title: string; path: string; - filter?: - | { - isKind?: string | undefined; - isType?: string | undefined; - }[] - | undefined; + filter?: string | undefined; }>; -// @alpha (undocumented) -export const entityContentTitleExtensionDataRef: ConfigurableExtensionDataRef< - string, - {} ->; - -// @alpha (undocumented) -export const entityFilterExtensionDataRef: ConfigurableExtensionDataRef< - (ctx: { entity: Entity }) => boolean, - {} ->; - // @alpha export function isOwnerOf(owner: Entity, entity: Entity): boolean; diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 1017cbf70b..d4114efc59 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "1.9.2-next.0", + "version": "1.9.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -56,7 +56,6 @@ "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", - "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.12.2", diff --git a/plugins/catalog-react/src/alpha.tsx b/plugins/catalog-react/src/alpha.tsx index 8d0436e4be..330cd573cd 100644 --- a/plugins/catalog-react/src/alpha.tsx +++ b/plugins/catalog-react/src/alpha.tsx @@ -14,106 +14,75 @@ * limitations under the License. */ -import React, { lazy } from 'react'; import { AnyExtensionInputMap, ExtensionBoundary, - ExtensionInputValues, + ResolvedExtensionInputs, RouteRef, coreExtensionData, createExtension, createExtensionDataRef, createSchemaFromZod, } from '@backstage/frontend-plugin-api'; +import React, { lazy } from 'react'; +import { Entity } from '@backstage/catalog-model'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { Expand } from '../../../packages/frontend-plugin-api/src/types'; -import { Entity } from '@backstage/catalog-model'; -export { isOwnerOf } from './utils'; export { useEntityPermission } from './hooks/useEntityPermission'; +export { isOwnerOf } from './utils'; /** @alpha */ -export const entityContentTitleExtensionDataRef = - createExtensionDataRef('plugin.catalog.entity.content.title'); - -/** @alpha */ -export const entityFilterExtensionDataRef = createExtensionDataRef< - (ctx: { entity: Entity }) => boolean ->('plugin.catalog.entity.filter'); - -function applyFilter(a?: string, b?: string): boolean { - if (!a) { - return true; - } - return a.toLocaleLowerCase('en-US') === b?.toLocaleLowerCase('en-US'); -} - -// TODO: Only two hardcoded isKind and isType filters are available for now -// This is just an initial config filter implementation and needs to be revisited -function buildFilter( - config: { filter?: { isKind?: string; isType?: string }[] }, - filterFunc?: (ctx: { entity: Entity }) => boolean, -) { - return (ctx: { entity: Entity }) => { - const configuredFilterMatch = config.filter?.some(filter => { - const kindMatch = applyFilter(filter.isKind, ctx.entity.kind); - const typeMatch = applyFilter( - filter.isType, - ctx.entity.spec?.type?.toString(), - ); - return kindMatch && typeMatch; - }); - if (configuredFilterMatch) { - return true; - } - if (filterFunc) { - return filterFunc(ctx); - } - return true; - }; -} +export const catalogExtensionData = { + entityContentTitle: createExtensionDataRef( + 'catalog.entity-content-title', + ), + entityFilterFunction: createExtensionDataRef<(entity: Entity) => boolean>( + 'catalog.entity-filter-function', + ), + entityFilterExpression: createExtensionDataRef( + 'catalog.entity-filter-expression', + ), +}; // TODO: Figure out how to merge with provided config schema /** @alpha */ export function createEntityCardExtension< TInputs extends AnyExtensionInputMap, >(options: { - id: string; + namespace?: string; + name?: string; attachTo?: { id: string; input: string }; disabled?: boolean; inputs?: TInputs; - filter?: (ctx: { entity: Entity }) => boolean; + filter?: + | typeof catalogExtensionData.entityFilterFunction.T + | typeof catalogExtensionData.entityFilterExpression.T; loader: (options: { - inputs: Expand>; + inputs: Expand>; }) => Promise; }) { - const id = `entity.cards.${options.id}`; - return createExtension({ - id, + kind: 'entity-card', + namespace: options.namespace, + name: options.name, attachTo: options.attachTo ?? { - id: 'entity.content.overview', + id: 'entity-content:catalog/overview', input: 'cards', }, disabled: options.disabled ?? true, output: { element: coreExtensionData.reactElement, - filter: entityFilterExtensionDataRef, + filterFunction: catalogExtensionData.entityFilterFunction.optional(), + filterExpression: catalogExtensionData.entityFilterExpression.optional(), }, inputs: options.inputs, configSchema: createSchemaFromZod(z => z.object({ - filter: z - .array( - z.object({ - isKind: z.string().optional(), - isType: z.string().optional(), - }), - ) - .optional(), + filter: z.string().optional(), }), ), - factory({ config, inputs, source }) { + factory({ config, inputs, node }) { const ExtensionComponent = lazy(() => options .loader({ inputs }) @@ -122,11 +91,11 @@ export function createEntityCardExtension< return { element: ( - + ), - filter: buildFilter(config, options.filter), + ...mergeFilters({ config, options }), }; }, }); @@ -136,24 +105,27 @@ export function createEntityCardExtension< export function createEntityContentExtension< TInputs extends AnyExtensionInputMap, >(options: { - id: string; + namespace?: string; + name?: string; attachTo?: { id: string; input: string }; disabled?: boolean; inputs?: TInputs; routeRef?: RouteRef; defaultPath: string; defaultTitle: string; - filter?: (ctx: { entity: Entity }) => boolean; + filter?: + | typeof catalogExtensionData.entityFilterFunction.T + | typeof catalogExtensionData.entityFilterExpression.T; loader: (options: { - inputs: Expand>; + inputs: Expand>; }) => Promise; }) { - const id = `entity.content.${options.id}`; - return createExtension({ - id, + kind: 'entity-content', + namespace: options.namespace, + name: options.name, attachTo: options.attachTo ?? { - id: 'plugin.catalog.page.entity', + id: 'page:catalog/entity', input: 'contents', }, disabled: options.disabled ?? true, @@ -161,25 +133,19 @@ export function createEntityContentExtension< element: coreExtensionData.reactElement, path: coreExtensionData.routePath, routeRef: coreExtensionData.routeRef.optional(), - title: entityContentTitleExtensionDataRef, - filter: entityFilterExtensionDataRef, + title: catalogExtensionData.entityContentTitle, + filterFunction: catalogExtensionData.entityFilterFunction.optional(), + filterExpression: catalogExtensionData.entityFilterExpression.optional(), }, inputs: options.inputs, configSchema: createSchemaFromZod(z => z.object({ path: z.string().default(options.defaultPath), title: z.string().default(options.defaultTitle), - filter: z - .array( - z.object({ - isKind: z.string().optional(), - isType: z.string().optional(), - }), - ) - .optional(), + filter: z.string().optional(), }), ), - factory({ config, inputs, source }) { + factory({ config, inputs, node }) { const ExtensionComponent = lazy(() => options .loader({ inputs }) @@ -191,12 +157,39 @@ export function createEntityContentExtension< title: config.title, routeRef: options.routeRef, element: ( - + ), - filter: buildFilter(config, options.filter), + ...mergeFilters({ config, options }), }; }, }); } + +/** + * Decides what filter outputs to produce, given some options and config + */ +function mergeFilters(inputs: { + options: { + filter?: + | typeof catalogExtensionData.entityFilterFunction.T + | typeof catalogExtensionData.entityFilterExpression.T; + }; + config: { + filter?: string; + }; +}): { + filterFunction?: typeof catalogExtensionData.entityFilterFunction.T; + filterExpression?: typeof catalogExtensionData.entityFilterExpression.T; +} { + const { options, config } = inputs; + if (config.filter) { + return { filterExpression: config.filter }; + } else if (typeof options.filter === 'string') { + return { filterExpression: options.filter }; + } else if (typeof options.filter === 'function') { + return { filterFunction: options.filter }; + } + return {}; +} diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.test.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.test.ts index ea755dfebc..5985a50b0d 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.test.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.test.ts @@ -37,7 +37,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'component:default/test', primaryTitle: 'test', secondaryTitle: 'component:default/test | type | desc', - Icon: expect.anything(), + Icon: undefined, }); expect( @@ -58,7 +58,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'component:default/test', primaryTitle: 'title', secondaryTitle: 'component:default/test | type | desc', - Icon: expect.anything(), + Icon: undefined, }); expect( @@ -82,7 +82,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'component:default/test', primaryTitle: 'displayName', secondaryTitle: 'component:default/test | type | desc', - Icon: expect.anything(), + Icon: undefined, }); }); @@ -96,7 +96,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'component:default/test', primaryTitle: 'test', secondaryTitle: 'component:default/test', - Icon: expect.anything(), + Icon: undefined, }); }); @@ -107,7 +107,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'unknown:default/unknown', primaryTitle: 'unknown', secondaryTitle: 'unknown:default/unknown', - Icon: expect.anything(), + Icon: undefined, }); }); }); @@ -118,7 +118,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'component:default/test', primaryTitle: 'test', secondaryTitle: 'component:default/test', - Icon: expect.anything(), + Icon: undefined, }); expect( @@ -129,7 +129,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'component:default/test', primaryTitle: 'component:test', secondaryTitle: 'component:default/test', - Icon: expect.anything(), + Icon: undefined, }); expect( @@ -140,7 +140,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'component:default/test', primaryTitle: 'default/test', secondaryTitle: 'component:default/test', - Icon: expect.anything(), + Icon: undefined, }); }); @@ -149,14 +149,14 @@ describe('defaultEntityPresentation', () => { entityRef: 'unknown:default/unknown', primaryTitle: 'unknown', secondaryTitle: 'unknown:default/unknown', - Icon: expect.anything(), + Icon: undefined, }); expect(defaultEntityPresentation('name')).toEqual({ entityRef: 'unknown:default/name', primaryTitle: 'name', secondaryTitle: 'unknown:default/name', - Icon: expect.anything(), + Icon: undefined, }); }); }); @@ -173,7 +173,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'component:default/test', primaryTitle: 'test', secondaryTitle: 'component:default/test', - Icon: expect.anything(), + Icon: undefined, }); expect( @@ -187,7 +187,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'component:default/test', primaryTitle: 'component:test', secondaryTitle: 'component:default/test', - Icon: expect.anything(), + Icon: undefined, }); expect( @@ -201,7 +201,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'component:default/test', primaryTitle: 'default/test', secondaryTitle: 'component:default/test', - Icon: expect.anything(), + Icon: undefined, }); }); @@ -217,14 +217,14 @@ describe('defaultEntityPresentation', () => { entityRef: 'component:default/test', primaryTitle: 'default/test', secondaryTitle: 'component:default/test', - Icon: expect.anything(), + Icon: undefined, }); expect(defaultEntityPresentation('')).toEqual({ entityRef: 'unknown:default/unknown', primaryTitle: 'unknown', secondaryTitle: 'unknown:default/unknown', - Icon: expect.anything(), + Icon: undefined, }); }); }); @@ -235,7 +235,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'unknown:default/unknown', primaryTitle: 'unknown', secondaryTitle: 'unknown:default/unknown', - Icon: expect.anything(), + Icon: undefined, }); expect(defaultEntityPresentation(undefined as unknown as Entity)).toEqual( @@ -243,7 +243,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'unknown:default/unknown', primaryTitle: 'unknown', secondaryTitle: 'unknown:default/unknown', - Icon: expect.anything(), + Icon: undefined, }, ); @@ -253,7 +253,7 @@ describe('defaultEntityPresentation', () => { entityRef: 'unknown:default/unknown', primaryTitle: 'unknown', secondaryTitle: 'unknown:default/unknown', - Icon: expect.anything(), + Icon: undefined, }); }); }); diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts index eacb659062..8d841f3c3c 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts @@ -20,32 +20,9 @@ import { Entity, stringifyEntityRef, } from '@backstage/catalog-model'; -import { IconComponent } from '@backstage/core-plugin-api'; -import ApartmentIcon from '@material-ui/icons/Apartment'; -import BusinessIcon from '@material-ui/icons/Business'; -import ExtensionIcon from '@material-ui/icons/Extension'; -import HelpIcon from '@material-ui/icons/Help'; -import LibraryAddIcon from '@material-ui/icons/LibraryAdd'; -import LocationOnIcon from '@material-ui/icons/LocationOn'; -import MemoryIcon from '@material-ui/icons/Memory'; -import PeopleIcon from '@material-ui/icons/People'; -import PersonIcon from '@material-ui/icons/Person'; import get from 'lodash/get'; import { EntityRefPresentationSnapshot } from './EntityPresentationApi'; -const UNKNOWN_KIND_ICON: IconComponent = HelpIcon; - -const DEFAULT_ICONS: Record = { - api: ExtensionIcon, - component: MemoryIcon, - system: BusinessIcon, - domain: ApartmentIcon, - location: LocationOnIcon, - user: PersonIcon, - group: PeopleIcon, - template: LibraryAddIcon, -}; - /** * This returns the default representation of an entity. * @@ -66,10 +43,6 @@ export function defaultEntityPresentation( const { kind, namespace, name, title, description, displayName, type } = getParts(entityOrRef); - const Icon = - (kind && DEFAULT_ICONS[kind.toLocaleLowerCase('en-US')]) || - UNKNOWN_KIND_ICON; - const entityRef: string = stringifyEntityRef({ kind: kind || 'unknown', namespace: namespace || DEFAULT_NAMESPACE, @@ -94,7 +67,7 @@ export function defaultEntityPresentation( entityRef, primaryTitle: primary, secondaryTitle: secondary || undefined, - Icon, + Icon: undefined, // leave it up to the presentation API to handle }; } diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx index ed22e126c5..5b5bd39310 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx @@ -26,6 +26,7 @@ import { TextField, Typography, makeStyles, + Tooltip, } from '@material-ui/core'; import CheckBoxIcon from '@material-ui/icons/CheckBox'; import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; @@ -39,6 +40,8 @@ import PersonIcon from '@material-ui/icons/Person'; import GroupIcon from '@material-ui/icons/Group'; import { humanizeEntity, humanizeEntityRef } from '../EntityRefLink/humanize'; import { useFetchEntities } from './useFetchEntities'; +import { withStyles } from '@material-ui/core/styles'; +import { useEntityPresentation } from '../../apis'; /** @public */ export type CatalogReactEntityOwnerPickerClassKey = 'input'; @@ -46,12 +49,30 @@ export type CatalogReactEntityOwnerPickerClassKey = 'input'; const useStyles = makeStyles( { input: {}, + fullWidth: { width: '100%' }, + boxLabel: { + width: '100%', + textOverflow: 'ellipsis', + overflow: 'hidden', + }, }, { name: 'CatalogReactEntityOwnerPicker', }, ); +const FixedWidthFormControlLabel = withStyles( + _theme => ({ + label: { + width: '100%', + }, + root: { + width: '90%', + }, + }), + { name: 'FixedWidthFormControlLabel' }, +)(FormControlLabel); + const icon = ; const checkedIcon = ; @@ -62,6 +83,42 @@ export type EntityOwnerPickerProps = { mode?: 'owners-only' | 'all'; }; +function RenderOptionLabel(props: { entity: Entity; isSelected: boolean }) { + const classes = useStyles(); + const isGroup = props.entity.kind.toLocaleLowerCase('en-US') === 'group'; + const { primaryTitle: title } = useEntityPresentation(props.entity); + return ( + + + } + onClick={event => event.preventDefault()} + label={ + + + {isGroup ? ( + + ) : ( + + )} +   + + {title} + + + + } + /> + + ); +} + /** @public */ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { const classes = useStyles(); @@ -153,41 +210,13 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { if (typeof e !== 'string') { cache.setEntity(e); } - return entityRef; }), ); }} filterOptions={x => x} renderOption={(entity, { selected }) => { - const isGroup = entity.kind.toLocaleLowerCase('en-US') === 'group'; - - return ( - - } - onClick={event => event.preventDefault()} - label={ - - {isGroup ? ( - - ) : ( - - )} -   - {humanizeEntity( - entity, - humanizeEntityRef(entity, { defaultKind: entity.kind }), - )} - - } - /> - ); + return ; }} size="small" popupIcon={} diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx index 90df738922..7148a051e8 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx @@ -26,7 +26,7 @@ import { Progress, ResponseErrorPanel, } from '@backstage/core-components'; -import { useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { useApi, useApp, useRouteRef } from '@backstage/core-plugin-api'; import { Box, DialogContentText, makeStyles } from '@material-ui/core'; import classNames from 'classnames'; import React, { useLayoutEffect, useRef, useState } from 'react'; @@ -107,6 +107,7 @@ function CustomNode({ node }: DependencyGraphTypes.RenderNodeProps) { const entityRoute = useRouteRef(entityRouteRef); const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); + const app = useApp(); const idRef = useRef(null); useLayoutEffect(() => { @@ -123,9 +124,12 @@ function CustomNode({ node }: DependencyGraphTypes.RenderNodeProps) { } }, [width, height]); + const hasKindIcon = app.getSystemIcon( + `kind:${node.kind.toLocaleLowerCase('en-US')}`, + ); const padding = 10; const iconSize = height; - const paddedIconWidth = iconSize + padding; + const paddedIconWidth = hasKindIcon ? iconSize + padding : 0; const paddedWidth = paddedIconWidth + width + padding * 2; const paddedHeight = height + padding * 2; @@ -160,17 +164,19 @@ function CustomNode({ node }: DependencyGraphTypes.RenderNodeProps) { height={paddedHeight} rx={10} /> - + {hasKindIcon && ( + + )} } {props.entities.map(entity => ( - - - } /> ))} diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/EntityKindIcon.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/EntityKindIcon.tsx index 4bb41c91c1..7fc95613e9 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/EntityKindIcon.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/EntityKindIcon.tsx @@ -16,10 +16,10 @@ import { parseEntityRef } from '@backstage/catalog-model'; import { useApp } from '@backstage/core-plugin-api'; -import WorkIcon from '@material-ui/icons/Work'; import React from 'react'; +import SvgIcon from '@material-ui/core/SvgIcon'; -const DEFAULT_ICON = WorkIcon; +const DEFAULT_ICON = SvgIcon; function getKind( kind: string | undefined, diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx index 8b3b69a55a..f4bd8d9f25 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx @@ -36,7 +36,6 @@ import { ListItemText, ListSubheader, } from './common'; -import { EntityKindIcon } from './EntityKindIcon'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { CopyTextButton } from '@backstage/core-components'; @@ -155,9 +154,6 @@ export function OverviewPage(props: { entity: AlphaEntity }) { {type}}> {groupRelations.map(group => ( - - - diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index 44fcaa4626..9a2be34228 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,81 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.1.7 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.7-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.1.6-next.0 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index d1e466d27c..d3b037788a 100644 --- a/plugins/catalog-unprocessed-entities/package.json +++ b/plugins/catalog-unprocessed-entities/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities", - "version": "0.1.6-next.0", + "version": "0.1.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,7 +33,6 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.60", @@ -47,13 +46,9 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 95bbf0bfb6..4283ae0a74 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,244 @@ # @backstage/plugin-catalog +## 1.17.0-next.0 + +### Minor Changes + +- e541c0e: Exported `CatalogTable.defaultColumnsFunc` for defining the columns in `` of some Kinds while using the default columns for the others. + +### Patch Changes + +- 916da47: Change default icon for unknown entities to nothing instead of the help icon. +- f899eec: Change default icon for `kind:resource` to the storage icon. +- 912ca7b: Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. +- 797a329: Fixed inconsistencies in icons used for System and Template +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration-react@1.1.23 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-search-common@1.2.10 + +## 1.16.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-search-react@1.7.5 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-search-common@1.2.10 + +## 1.16.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## 1.16.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + - @backstage/plugin-search-common@1.2.9 + +## 1.16.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + - @backstage/plugin-search-common@1.2.9 + +## 1.16.0 + +### Minor Changes + +- e223f22: Properly support both function- and string-form visibility filter expressions in the new extensions exported via `/alpha`. +- b8e1eb2: The `columns` prop can be an array or a function that returns an array in order to override the default columns of the `CatalogIndexPage`. + +### Patch Changes + +- bc7e6d3: Fix copy entity url function in http contexts. +- 5360097: Ensure that passed-in icons are taken advantage of in the presentation API +- 4785d05: Add permission check to catalog create and refresh button +- cd910c4: - Fixes bug where after unregistering an entity you are redirected to `/`. + - Adds an optional external route `unregisterRedirect` to override this behaviour to another route. +- 03d0b6d: The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. +- 2d708d8: Internal naming updates for `/alpha` exports. +- a5a0473: Internal refactor of alpha exports due to a change in how extension factories are defined. +- 4d9e3b3: Register component overrides in the global `OverrideComponentNameToClassKeys` provided by `@backstage/theme`. This will in turn will provide component style override types for `createUnifiedTheme`. +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 78a10bb: Adding in spec.type chip to search results for clarity +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- 8587f06: Added pagination support to `CatalogIndexPage` + + `CatalogIndexPage` now offers an optional pagination feature, designed to accommodate adopters managing extensive catalogs. This new capability allows for better handling of large amounts of data. + + To activate the pagination mode, simply update your `App.tsx` as follows: + + ```diff + const routes = ( + + ... + - } /> + + } /> + ... + ``` + + In case you have a custom catalog page and you want to enable pagination, you need to pass the `pagination` prop to `EntityListProvider` instead. + +- fb8f3bd: Updated alpha translation message keys to use nested format and camel case. +- 531e1a2: Updated alpha plugin to include the `unregisterRedirect` external route. +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-search-react@1.7.4 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + - @backstage/plugin-search-common@1.2.9 + +## 1.16.0-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-scaffolder-common@1.4.3 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + +## 1.16.0-next.3 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-scaffolder-common@1.4.3 + - @backstage/plugin-search-common@1.2.8 + +## 1.16.0-next.2 + +### Minor Changes + +- e223f2264d: Properly support both function- and string-form visibility filter expressions in the new extensions exported via `/alpha`. + +### Patch Changes + +- 53600976bb: Ensure that passed-in icons are taken advantage of in the presentation API +- a5a04739e1: Internal refactor of alpha exports due to a change in how extension factories are defined. +- 78a10bb085: Adding in spec.type chip to search results for clarity +- fb8f3bdbc2: Updated alpha translation message keys to use nested format and camel case. +- 531e1a2a79: Updated alpha plugin to include the `unregisterRedirect` external route. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-scaffolder-common@1.4.3 + - @backstage/plugin-search-common@1.2.8 + ## 1.16.0-next.1 ### Patch Changes diff --git a/plugins/catalog/alpha-api-report.md b/plugins/catalog/api-report-alpha.md similarity index 87% rename from plugins/catalog/alpha-api-report.md rename to plugins/catalog/api-report-alpha.md index 43e589bdc3..3226d10270 100644 --- a/plugins/catalog/alpha-api-report.md +++ b/plugins/catalog/api-report-alpha.md @@ -7,7 +7,7 @@ import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; -import { Extension } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { PortableSchema } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; @@ -17,11 +17,12 @@ export function createCatalogFilterExtension< TInputs extends AnyExtensionInputMap, TConfig = never, >(options: { - id: string; + namespace?: string; + name?: string; inputs?: TInputs; configSchema?: PortableSchema; loader: (options: { config: TConfig }) => Promise; -}): Extension; +}): ExtensionDefinition; // @alpha (undocumented) const _default: BackstagePlugin< @@ -50,6 +51,7 @@ const _default: BackstagePlugin< }, true >; + unregisterRedirect: ExternalRouteRef; } >; export default _default; diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index bef24b50d8..feda3fd70d 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -182,6 +182,7 @@ export const CatalogTable: { ): TableColumn; createNamespaceColumn(): TableColumn; }>; + defaultColumnsFunc: CatalogTableColumnsFunc; }; // @public diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 1ac4fe3b73..c30cddc08f 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog", "description": "The Backstage plugin for browsing the Backstage catalog", - "version": "1.16.0-next.1", + "version": "1.17.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -56,10 +56,10 @@ "@backstage/integration-react": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/plugin-permission-react": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^", "@backstage/plugin-search-react": "workspace:^", - "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -83,7 +83,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/plugin-permission-react": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", diff --git a/plugins/catalog/src/alpha/EntityOverviewPage.tsx b/plugins/catalog/src/alpha/EntityOverviewPage.tsx index baa93845ae..932e13d962 100644 --- a/plugins/catalog/src/alpha/EntityOverviewPage.tsx +++ b/plugins/catalog/src/alpha/EntityOverviewPage.tsx @@ -14,29 +14,95 @@ * limitations under the License. */ -import React from 'react'; -import { useEntity } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; +import { useEntity } from '@backstage/plugin-catalog-react'; import Grid from '@material-ui/core/Grid'; +import React, { useMemo } from 'react'; +import { parseFilterExpression } from './filter/parseFilterExpression'; interface EntityOverviewPageProps { cards: Array<{ element: React.JSX.Element; - filter: (ctx: { entity: Entity }) => boolean; + filterFunction?: (entity: Entity) => boolean; + filterExpression?: string; }>; } +// Keeps track of what filter expression strings that we've seen duplicates of +// with functions, or which emitted parsing errors for so far +const seenParseErrorExpressionStrings = new Set(); +const seenDuplicateExpressionStrings = new Set(); + +// Given an optional filter function and an optional filter expression, make +// sure that at most one of them was given, and return a filter function that +// does the right thing. +function buildFilterFn( + filterFunction?: (entity: Entity) => boolean, + filterExpression?: string, +): (entity: Entity) => boolean { + if ( + filterFunction && + filterExpression && + !seenDuplicateExpressionStrings.has(filterExpression) + ) { + // eslint-disable-next-line no-console + console.warn( + `Duplicate entity filter methods found, both '${filterExpression}' as well as a callback function, which is not permitted - using the callback`, + ); + seenDuplicateExpressionStrings.add(filterExpression); + } + + const filter = filterFunction || filterExpression; + if (!filter) { + return () => true; + } else if (typeof filter === 'function') { + return subject => filter(subject); + } + + const result = parseFilterExpression(filter); + if ( + result.expressionParseErrors.length && + !seenParseErrorExpressionStrings.has(filter) + ) { + // eslint-disable-next-line no-console + console.warn( + `Error(s) in entity filter expression '${filter}'`, + result.expressionParseErrors, + ); + seenParseErrorExpressionStrings.add(filter); + } + + return result.filterFn; +} + +// Handles the memoized parsing of filter expressions for each card +function CardWrapper(props: { + entity: Entity; + element: React.JSX.Element; + filterFunction?: (entity: Entity) => boolean; + filterExpression?: string; +}) { + const { entity, element, filterFunction, filterExpression } = props; + + const filterFn = useMemo( + () => buildFilterFn(filterFunction, filterExpression), + [filterFunction, filterExpression], + ); + + return filterFn(entity) ? ( + + {element} + + ) : null; +} + export function EntityOverviewPage(props: EntityOverviewPageProps) { const { entity } = useEntity(); return ( - {props.cards - .filter(card => card.filter({ entity })) - .map(card => ( - - {card.element} - - ))} + {props.cards.map((card, index) => ( + + ))} ); } diff --git a/plugins/catalog/src/alpha/apis.tsx b/plugins/catalog/src/alpha/apis.tsx index a887a33a04..ae8dcf9a9a 100644 --- a/plugins/catalog/src/alpha/apis.tsx +++ b/plugins/catalog/src/alpha/apis.tsx @@ -28,7 +28,7 @@ import { } from '@backstage/plugin-catalog-react'; import { DefaultStarredEntitiesApi } from '../apis'; -export const CatalogApi = createApiExtension({ +export const catalogApi = createApiExtension({ factory: createApiFactory({ api: catalogApiRef, deps: { @@ -40,7 +40,7 @@ export const CatalogApi = createApiExtension({ }), }); -export const StarredEntitiesApi = createApiExtension({ +export const catalogStarredEntitiesApi = createApiExtension({ factory: createApiFactory({ api: starredEntitiesApiRef, deps: { storageApi: storageApiRef }, @@ -48,4 +48,4 @@ export const StarredEntitiesApi = createApiExtension({ }), }); -export default [CatalogApi, StarredEntitiesApi]; +export default [catalogApi, catalogStarredEntitiesApi]; diff --git a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx b/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx index cf3dbd318a..48ee647b81 100644 --- a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx +++ b/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx @@ -28,22 +28,23 @@ export function createCatalogFilterExtension< TInputs extends AnyExtensionInputMap, TConfig = never, >(options: { - id: string; + namespace?: string; + name?: string; inputs?: TInputs; configSchema?: PortableSchema; loader: (options: { config: TConfig }) => Promise; }) { - const id = `catalog.filter.${options.id}`; - return createExtension({ - id, - attachTo: { id: 'plugin.catalog.page.index', input: 'filters' }, + kind: 'catalog-filter', + namespace: options.namespace, + name: options.name, + attachTo: { id: 'page:catalog', input: 'filters' }, inputs: options.inputs ?? {}, configSchema: options.configSchema, output: { element: coreExtensionData.reactElement, }, - factory({ config, source }) { + factory({ config, node }) { const ExtensionComponent = lazy(() => options .loader({ config }) @@ -52,7 +53,7 @@ export function createCatalogFilterExtension< return { element: ( - + ), diff --git a/plugins/catalog/src/alpha/entityCards.tsx b/plugins/catalog/src/alpha/entityCards.tsx index c7b75679a7..da6a6a669e 100644 --- a/plugins/catalog/src/alpha/entityCards.tsx +++ b/plugins/catalog/src/alpha/entityCards.tsx @@ -16,89 +16,90 @@ import React from 'react'; import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { compatWrapper } from '@backstage/core-compat-api'; -export const EntityAboutCard = createEntityCardExtension({ - id: 'about', +export const catalogAboutEntityCard = createEntityCardExtension({ + name: 'about', loader: async () => - import('../components/AboutCard').then(m => ( - - )), + import('../components/AboutCard').then(m => + compatWrapper(), + ), }); -export const EntityLinksCard = createEntityCardExtension({ - id: 'links', - filter: ({ entity }) => Boolean(entity.metadata.links), +export const catalogLinksEntityCard = createEntityCardExtension({ + name: 'links', + filter: 'has:links', loader: async () => - import('../components/EntityLinksCard').then(m => { - return ; - }), + import('../components/EntityLinksCard').then(m => + compatWrapper(), + ), }); -export const EntityLabelsCard = createEntityCardExtension({ - id: 'labels', - filter: ({ entity }) => Boolean(entity.metadata.labels), +export const catalogLabelsEntityCard = createEntityCardExtension({ + name: 'labels', + filter: 'has:labels', loader: async () => - import('../components/EntityLabelsCard').then(m => ( - - )), + import('../components/EntityLabelsCard').then(m => + compatWrapper(), + ), }); -export const EntityDependsOnComponentsCard = createEntityCardExtension({ - id: 'dependsOn.components', +export const catalogDependsOnComponentsEntityCard = createEntityCardExtension({ + name: 'depends-on-components', loader: async () => - import('../components/DependsOnComponentsCard').then(m => ( - - )), + import('../components/DependsOnComponentsCard').then(m => + compatWrapper(), + ), }); -export const EntityDependsOnResourcesCard = createEntityCardExtension({ - id: 'dependsOn.resources', +export const catalogDependsOnResourcesEntityCard = createEntityCardExtension({ + name: 'depends-on-resources', loader: async () => - import('../components/DependsOnResourcesCard').then(m => ( - - )), + import('../components/DependsOnResourcesCard').then(m => + compatWrapper(), + ), }); -export const EntityHasComponentsCard = createEntityCardExtension({ - id: 'has.components', +export const catalogHasComponentsEntityCard = createEntityCardExtension({ + name: 'has-components', loader: async () => - import('../components/HasComponentsCard').then(m => ( - - )), + import('../components/HasComponentsCard').then(m => + compatWrapper(), + ), }); -export const EntityHasResourcesCard = createEntityCardExtension({ - id: 'has.resources', +export const catalogHasResourcesEntityCard = createEntityCardExtension({ + name: 'has-resources', loader: async () => - import('../components/HasResourcesCard').then(m => ( - - )), + import('../components/HasResourcesCard').then(m => + compatWrapper(), + ), }); -export const EntityHasSubcomponentsCard = createEntityCardExtension({ - id: 'has.subcomponents', +export const catalogHasSubcomponentsEntityCard = createEntityCardExtension({ + name: 'has-subcomponents', loader: async () => - import('../components/HasSubcomponentsCard').then(m => ( - - )), + import('../components/HasSubcomponentsCard').then(m => + compatWrapper(), + ), }); -export const EntityHasSystemsCard = createEntityCardExtension({ - id: 'has.systems', +export const catalogHasSystemsEntityCard = createEntityCardExtension({ + name: 'has-systems', loader: async () => - import('../components/HasSystemsCard').then(m => ( - - )), + import('../components/HasSystemsCard').then(m => + compatWrapper(), + ), }); export default [ - EntityAboutCard, - EntityLinksCard, - EntityLabelsCard, - EntityDependsOnComponentsCard, - EntityDependsOnResourcesCard, - EntityHasComponentsCard, - EntityHasResourcesCard, - EntityHasSubcomponentsCard, - EntityHasSystemsCard, + catalogAboutEntityCard, + catalogLinksEntityCard, + catalogLabelsEntityCard, + catalogDependsOnComponentsEntityCard, + catalogDependsOnResourcesEntityCard, + catalogHasComponentsEntityCard, + catalogHasResourcesEntityCard, + catalogHasSubcomponentsEntityCard, + catalogHasSystemsEntityCard, ]; diff --git a/plugins/catalog/src/alpha/entityContents.tsx b/plugins/catalog/src/alpha/entityContents.tsx index 9bef4a5e0d..75c3fef49f 100644 --- a/plugins/catalog/src/alpha/entityContents.tsx +++ b/plugins/catalog/src/alpha/entityContents.tsx @@ -21,24 +21,25 @@ import { } from '@backstage/frontend-plugin-api'; import { createEntityContentExtension, - entityFilterExtensionDataRef, + catalogExtensionData, } from '@backstage/plugin-catalog-react/alpha'; -export const OverviewEntityContent = createEntityContentExtension({ - id: 'overview', +export const catalogOverviewEntityContent = createEntityContentExtension({ + name: 'overview', defaultPath: '/', defaultTitle: 'Overview', disabled: false, inputs: { cards: createExtensionInput({ element: coreExtensionData.reactElement, - filter: entityFilterExtensionDataRef, + filterFunction: catalogExtensionData.entityFilterFunction.optional(), + filterExpression: catalogExtensionData.entityFilterExpression.optional(), }), }, loader: async ({ inputs }) => import('./EntityOverviewPage').then(m => ( - + c.output)} /> )), }); -export default [OverviewEntityContent]; +export default [catalogOverviewEntityContent]; diff --git a/plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.test.ts b/plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.test.ts new file mode 100644 index 0000000000..53524d228e --- /dev/null +++ b/plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.test.ts @@ -0,0 +1,98 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { createHasMatcher } from './createHasMatcher'; + +describe('createHasMatcher', () => { + const empty1 = { + metadata: {}, + } as unknown as Entity; + const empty2 = { + metadata: { + labels: {}, + links: [], + }, + } as unknown as Entity; + const labels = { + metadata: { + labels: { a: 'b' }, + }, + } as unknown as Entity; + const links = { + metadata: { links: [{}] }, + } as unknown as Entity; + + const err = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('supports valid parameters', () => { + expect(createHasMatcher(['labels'], err)(empty1)).toBe(false); + expect(createHasMatcher(['labels'], err)(empty2)).toBe(false); + expect(createHasMatcher(['labels'], err)(labels)).toBe(true); + + expect(createHasMatcher(['links'], err)(empty1)).toBe(false); + expect(createHasMatcher(['links'], err)(empty2)).toBe(false); + expect(createHasMatcher(['links'], err)(links)).toBe(true); + + expect(err).not.toHaveBeenCalled(); + }); + + it('matches if ANY parameter matches', () => { + expect(createHasMatcher(['labels', 'links'], err)(empty1)).toBe(false); + expect(createHasMatcher(['labels', 'links'], err)(empty2)).toBe(false); + expect(createHasMatcher(['labels', 'links'], err)(labels)).toBe(true); + expect(createHasMatcher(['labels', 'links'], err)(links)).toBe(true); + + expect(err).not.toHaveBeenCalled(); + }); + + it('emits errors properly, and skips over bad parameters', () => { + // throw if callback throws + expect(() => + createHasMatcher(['labels', 'bar'], e => { + throw e; + }), + ).toThrowErrorMatchingInlineSnapshot( + `"'bar' is not a valid parameter for 'has' filter expressions, expected one of 'labels','links'"`, + ); + expect(err).not.toHaveBeenCalled(); + + // continue if callback does not throw, and skip over the bad parameter + let matcher = createHasMatcher(['labels', 'foo', 'bar'], err); + expect(err).toHaveBeenCalledTimes(2); + expect(err).toHaveBeenCalledWith( + expect.objectContaining({ + message: `'foo' is not a valid parameter for 'has' filter expressions, expected one of 'labels','links'`, + }), + ); + expect(err).toHaveBeenCalledWith( + expect.objectContaining({ + message: `'bar' is not a valid parameter for 'has' filter expressions, expected one of 'labels','links'`, + }), + ); + expect(matcher(empty1)).toBe(false); + expect(matcher(labels)).toBe(true); + + // when no parameters at all match, just return true + matcher = createHasMatcher(['foo', 'bar'], err); + expect(matcher(empty1)).toBe(true); + expect(matcher(labels)).toBe(true); + }); +}); diff --git a/plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.ts b/plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.ts new file mode 100644 index 0000000000..9fd393a561 --- /dev/null +++ b/plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.ts @@ -0,0 +1,52 @@ +/* + * 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 { InputError } from '@backstage/errors'; +import { EntityMatcherFn } from './types'; + +const allowedMatchers: Record = { + labels: entity => { + return Object.keys(entity.metadata.labels ?? {}).length > 0; + }, + links: entity => { + return (entity.metadata.links ?? []).length > 0; + }, +}; + +/** + * Matches on the non-empty presence of different parts of the entity + */ +export function createHasMatcher( + parameters: string[], + onParseError: (error: Error) => void, +): EntityMatcherFn { + const matchers = parameters.flatMap(parameter => { + const matcher = allowedMatchers[parameter.toLocaleLowerCase('en-US')]; + if (!matcher) { + const known = Object.keys(allowedMatchers).map(m => `'${m}'`); + onParseError( + new InputError( + `'${parameter}' is not a valid parameter for 'has' filter expressions, expected one of ${known}`, + ), + ); + return []; + } + return [matcher]; + }); + + return entity => + matchers.length ? matchers.some(matcher => matcher(entity)) : true; +} diff --git a/plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.test.ts b/plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.test.ts new file mode 100644 index 0000000000..4cb3094d62 --- /dev/null +++ b/plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.test.ts @@ -0,0 +1,75 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { createIsMatcher } from './createIsMatcher'; + +describe('createIsMatcher', () => { + const empty = { + metadata: {}, + } as unknown as Entity; + const orphan = { + metadata: { + annotations: { ['backstage.io/orphan']: 'true' }, + }, + } as unknown as Entity; + + const err = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('supports valid parameters', () => { + expect(createIsMatcher(['orphan'], err)(empty)).toBe(false); + expect(createIsMatcher(['orphan'], err)(orphan)).toBe(true); + + expect(err).not.toHaveBeenCalled(); + }); + + it('emits errors properly, and skips over bad parameters', () => { + // throw if callback throws + expect(() => + createIsMatcher(['orphan', 'foo'], e => { + throw e; + }), + ).toThrowErrorMatchingInlineSnapshot( + `"'foo' is not a valid parameter for 'is' filter expressions, expected one of 'orphan'"`, + ); + expect(err).not.toHaveBeenCalled(); + + // continue if callback does not throw, and skip over the bad parameter + let matcher = createIsMatcher(['orphan', 'foo', 'bar'], err); + expect(err).toHaveBeenCalledTimes(2); + expect(err).toHaveBeenCalledWith( + expect.objectContaining({ + message: `'foo' is not a valid parameter for 'is' filter expressions, expected one of 'orphan'`, + }), + ); + expect(err).toHaveBeenCalledWith( + expect.objectContaining({ + message: `'bar' is not a valid parameter for 'is' filter expressions, expected one of 'orphan'`, + }), + ); + expect(matcher(empty)).toBe(false); + expect(matcher(orphan)).toBe(true); + + // when no parameters at all match, just return true + matcher = createIsMatcher(['foo', 'bar'], err); + expect(matcher(empty)).toBe(true); + expect(matcher(orphan)).toBe(true); + }); +}); diff --git a/plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.ts b/plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.ts new file mode 100644 index 0000000000..49a54349d1 --- /dev/null +++ b/plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.ts @@ -0,0 +1,48 @@ +/* + * 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 { InputError } from '@backstage/errors'; +import { EntityMatcherFn } from './types'; + +const allowedMatchers: Record = { + orphan: entity => + Boolean(entity.metadata.annotations?.['backstage.io/orphan']), +}; + +/** + * Matches on different semantic properties of the entity + */ +export function createIsMatcher( + parameters: string[], + onParseError: (error: Error) => void, +): EntityMatcherFn { + const matchers = parameters.flatMap(parameter => { + const matcher = allowedMatchers[parameter.toLocaleLowerCase('en-US')]; + if (!matcher) { + const known = Object.keys(allowedMatchers).map(m => `'${m}'`); + onParseError( + new InputError( + `'${parameter}' is not a valid parameter for 'is' filter expressions, expected one of ${known}`, + ), + ); + return []; + } + return [matcher]; + }); + + return entity => + matchers.length ? matchers.some(matcher => matcher(entity)) : true; +} diff --git a/plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.test.ts b/plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.test.ts new file mode 100644 index 0000000000..f109acccd4 --- /dev/null +++ b/plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.test.ts @@ -0,0 +1,46 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { createKindMatcher } from './createKindMatcher'; + +describe('createKindMatcher', () => { + const component = { kind: 'Component' } as unknown as Entity; + const user = { kind: 'User' } as unknown as Entity; + const resource = { kind: 'Resource' } as unknown as Entity; + + const err = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('supports valid parameters', () => { + expect(createKindMatcher(['component'], err)(component)).toBe(true); + expect(createKindMatcher(['componenT'], err)(component)).toBe(true); + expect(createKindMatcher(['user'], err)(component)).toBe(false); + + expect(err).not.toHaveBeenCalled(); + }); + + it('matches if ANY parameter matches', () => { + expect(createKindMatcher(['user', 'component'], err)(user)).toBe(true); + expect(createKindMatcher(['user', 'component'], err)(component)).toBe(true); + expect(createKindMatcher(['user', 'component'], err)(resource)).toBe(false); + + expect(err).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.ts b/plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.ts new file mode 100644 index 0000000000..4f7c475ebe --- /dev/null +++ b/plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.ts @@ -0,0 +1,28 @@ +/* + * 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 { EntityMatcherFn } from './types'; + +/** + * Matches on kind + */ +export function createKindMatcher( + parameters: string[], + _onParseError: (error: Error) => void, +): EntityMatcherFn { + const items = parameters.map(p => p.toLocaleLowerCase('en-US')); + return entity => items.includes(entity.kind.toLocaleLowerCase('en-US')); +} diff --git a/plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.test.ts b/plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.test.ts new file mode 100644 index 0000000000..31c9af7e11 --- /dev/null +++ b/plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.test.ts @@ -0,0 +1,54 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { createTypeMatcher } from './createTypeMatcher'; + +describe('createTypeMatcher', () => { + const empty1 = {} as unknown as Entity; + const empty2 = { spec: {} } as unknown as Entity; + const service = { spec: { type: 'service' } } as unknown as Entity; + const website = { spec: { type: 'website' } } as unknown as Entity; + const pipeline = { spec: { type: 'pipeline' } } as unknown as Entity; + + const err = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('supports valid parameters', () => { + expect(createTypeMatcher(['service'], err)(empty1)).toBe(false); + expect(createTypeMatcher(['service'], err)(empty2)).toBe(false); + + expect(createTypeMatcher(['service'], err)(service)).toBe(true); + expect(createTypeMatcher(['sErViCe'], err)(service)).toBe(true); + expect(createTypeMatcher(['service'], err)(service)).toBe(true); + expect(createTypeMatcher(['website'], err)(service)).toBe(false); + + expect(err).not.toHaveBeenCalled(); + }); + + it('matches if ANY parameter matches', () => { + expect(createTypeMatcher(['service', 'website'], err)(service)).toBe(true); + expect(createTypeMatcher(['service', 'website'], err)(website)).toBe(true); + expect(createTypeMatcher(['service', 'website'], err)(pipeline)).toBe( + false, + ); + + expect(err).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.ts b/plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.ts new file mode 100644 index 0000000000..31f7a5a461 --- /dev/null +++ b/plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.ts @@ -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. + */ + +import { EntityMatcherFn } from './types'; + +/** + * Matches on spec.type + */ +export function createTypeMatcher( + parameters: string[], + _onParseError: (error: Error) => void, +): EntityMatcherFn { + const items = parameters.map(p => p.toLocaleLowerCase('en-US')); + return entity => { + const value = entity.spec?.type; + return ( + typeof value === 'string' && + items.includes(value.toLocaleLowerCase('en-US')) + ); + }; +} diff --git a/plugins/catalog/src/alpha/filter/matrchers/types.ts b/plugins/catalog/src/alpha/filter/matrchers/types.ts new file mode 100644 index 0000000000..0fff91b3d5 --- /dev/null +++ b/plugins/catalog/src/alpha/filter/matrchers/types.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. + */ + +import { Entity } from '@backstage/catalog-model'; + +export type EntityMatcherFn = (entity: Entity) => boolean; diff --git a/plugins/catalog/src/alpha/filter/parseFilterExpression.test.ts b/plugins/catalog/src/alpha/filter/parseFilterExpression.test.ts new file mode 100644 index 0000000000..616091739e --- /dev/null +++ b/plugins/catalog/src/alpha/filter/parseFilterExpression.test.ts @@ -0,0 +1,151 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { + parseFilterExpression, + splitFilterExpression, +} from './parseFilterExpression'; + +describe('parseFilterExpression', () => { + function run(expression: string) { + const result = parseFilterExpression(expression); + if (result.expressionParseErrors.length) { + throw result.expressionParseErrors[0]; + } + return result.filterFn; + } + + it('supports "kind" expressions', () => { + const component = { kind: 'Component' } as unknown as Entity; + const user = { kind: 'User' } as unknown as Entity; + const resource = { kind: 'Resource' } as unknown as Entity; + + expect(run('kind:user,component')(user)).toBe(true); + expect(run('kind:user,component')(component)).toBe(true); + expect(run('kind:user,component')(resource)).toBe(false); + }); + + it('supports "type" expressions', () => { + const empty = {} as unknown as Entity; + const service = { spec: { type: 'service' } } as unknown as Entity; + const website = { spec: { type: 'website' } } as unknown as Entity; + const pipeline = { spec: { type: 'pipeline' } } as unknown as Entity; + + expect(run('type:service,website')(empty)).toBe(false); + expect(run('type:service,website')(service)).toBe(true); + expect(run('type:service,website')(website)).toBe(true); + expect(run('type:service,website')(pipeline)).toBe(false); + }); + + it('supports "is" expressions', () => { + const empty = { + metadata: {}, + } as unknown as Entity; + const orphan = { + metadata: { + annotations: { ['backstage.io/orphan']: 'true' }, + }, + } as unknown as Entity; + + expect(run('is:orphan')(empty)).toBe(false); + expect(run('is:orphan')(orphan)).toBe(true); + + expect(() => run('is:orphan,bar')).toThrowErrorMatchingInlineSnapshot( + `"'bar' is not a valid parameter for 'is' filter expressions, expected one of 'orphan'"`, + ); + }); + + it('supports "has" expressions', () => { + const empty = { + metadata: {}, + } as unknown as Entity; + const labels = { + metadata: { labels: { a: 'b' } }, + } as unknown as Entity; + const annotations = { + metadata: { annotations: { a: 'b' } }, + } as unknown as Entity; + const links = { + metadata: { links: [{}] }, + } as unknown as Entity; + + expect(run('has:labels,links')(empty)).toBe(false); + expect(run('has:labels,links')(labels)).toBe(true); + expect(run('has:labels,links')(links)).toBe(true); + expect(run('has:labels,links')(annotations)).toBe(false); + + expect(() => run('has:labels,bar')).toThrowErrorMatchingInlineSnapshot( + `"'bar' is not a valid parameter for 'has' filter expressions, expected one of 'labels','links'"`, + ); + }); + + it('rejects unknown keys', () => { + expect(() => run('unknown:foo')).toThrowErrorMatchingInlineSnapshot( + `"'unknown' is not a valid filter expression key, expected one of 'kind','type','is','has'"`, + ); + }); + + it('rejects malformed inputs', () => { + expect(() => run(':')).toThrowErrorMatchingInlineSnapshot( + `"':' is not a valid filter expression, expected 'key:parameter' form"`, + ); + expect(() => run(':a')).toThrowErrorMatchingInlineSnapshot( + `"':a' is not a valid filter expression, expected 'key:parameter' form"`, + ); + expect(() => run('a:')).toThrowErrorMatchingInlineSnapshot( + `"'a:' is not a valid filter expression, expected 'key:parameter' form"`, + ); + }); +}); + +describe('splitFilterExpression', () => { + function run(expression: string) { + return splitFilterExpression(expression, e => { + throw e; + }); + } + + it('properly splits into expression atoms', () => { + expect(run('')).toEqual([]); + expect(run(' ')).toEqual([]); + expect(run('kind:component')).toEqual([ + { key: 'kind', parameters: ['component'] }, + ]); + expect(run('kind:component,user')).toEqual([ + { key: 'kind', parameters: ['component', 'user'] }, + ]); + expect(run('kind:component,user type:foo')).toEqual([ + { key: 'kind', parameters: ['component', 'user'] }, + { key: 'type', parameters: ['foo'] }, + ]); + expect(run('with:multiple:colons')).toEqual([ + { key: 'with', parameters: ['multiple:colons'] }, + ]); + }); + + it('rejects malformed inputs', () => { + expect(() => run(':')).toThrowErrorMatchingInlineSnapshot( + `"':' is not a valid filter expression, expected 'key:parameter' form"`, + ); + expect(() => run(':a')).toThrowErrorMatchingInlineSnapshot( + `"':a' is not a valid filter expression, expected 'key:parameter' form"`, + ); + expect(() => run('a:')).toThrowErrorMatchingInlineSnapshot( + `"'a:' is not a valid filter expression, expected 'key:parameter' form"`, + ); + }); +}); diff --git a/plugins/catalog/src/alpha/filter/parseFilterExpression.ts b/plugins/catalog/src/alpha/filter/parseFilterExpression.ts new file mode 100644 index 0000000000..a4c17cb8da --- /dev/null +++ b/plugins/catalog/src/alpha/filter/parseFilterExpression.ts @@ -0,0 +1,126 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { InputError } from '@backstage/errors'; +import { EntityMatcherFn } from './matrchers/types'; +import { createKindMatcher } from './matrchers/createKindMatcher'; +import { createTypeMatcher } from './matrchers/createTypeMatcher'; +import { createIsMatcher } from './matrchers/createIsMatcher'; +import { createHasMatcher } from './matrchers/createHasMatcher'; + +const rootMatcherFactories: Record< + string, + ( + parameters: string[], + onParseError: (error: Error) => void, + ) => EntityMatcherFn +> = { + kind: createKindMatcher, + type: createTypeMatcher, + is: createIsMatcher, + has: createHasMatcher, +}; + +/** + * Parses a filter expression that decides whether to render an entity component + * or not. Returns a function that matches entities based on that expression. + * + * @remarks + * + * Filter strings are on the form `kind:user,group is:orphan`. There's + * effectively an AND between the space separated parts, and an OR between comma + * separated parameters. So the example filter string semantically means + * "entities that are of either User or Group kind, and also are orphans". + * + * The `expressionParseErrors` array contains any errors that were encountered + * during initial parsing of the expression. Note that the parts of the input + * expression that had errors are ignored entirely and parsing continues as if + * they didn't exist. + */ +export function parseFilterExpression(expression: string): { + filterFn: (entity: Entity) => boolean; + expressionParseErrors: Error[]; +} { + const expressionParseErrors: Error[] = []; + + const parts = splitFilterExpression(expression, e => + expressionParseErrors.push(e), + ); + + const matchers = parts.flatMap(part => { + const factory = rootMatcherFactories[part.key]; + if (!factory) { + const known = Object.keys(rootMatcherFactories).map(m => `'${m}'`); + expressionParseErrors.push( + new InputError( + `'${part.key}' is not a valid filter expression key, expected one of ${known}`, + ), + ); + return []; + } + + const matcher = factory(part.parameters, e => + expressionParseErrors.push(e), + ); + return [matcher]; + }); + + const filterFn = (entity: Entity) => + matchers.every(matcher => { + try { + return matcher(entity); + } catch { + return false; + } + }); + + return { + filterFn, + expressionParseErrors, + }; +} + +export function splitFilterExpression( + expression: string, + onParseError: (error: Error) => void, +): Array<{ key: string; parameters: string[] }> { + const words = expression + .split(' ') + .map(w => w.trim()) + .filter(Boolean); + + const result = new Array<{ key: string; parameters: string[] }>(); + + for (const word of words) { + const match = word.match(/^([^:]+):(.+)$/); + if (!match) { + onParseError( + new InputError( + `'${word}' is not a valid filter expression, expected 'key:parameter' form`, + ), + ); + continue; + } + + const key = match[1]; + const parameters = match[2].split(',').filter(Boolean); // silently ignore double commas + + result.push({ key, parameters }); + } + + return result; +} diff --git a/plugins/catalog/src/alpha/filters.tsx b/plugins/catalog/src/alpha/filters.tsx index 7b73c2d14b..62cf9a1353 100644 --- a/plugins/catalog/src/alpha/filters.tsx +++ b/plugins/catalog/src/alpha/filters.tsx @@ -18,16 +18,16 @@ import React from 'react'; import { createCatalogFilterExtension } from './createCatalogFilterExtension'; import { createSchemaFromZod } from '@backstage/frontend-plugin-api'; -const CatalogEntityTagFilter = createCatalogFilterExtension({ - id: 'entity.tag', +const catalogTagCatalogFilter = createCatalogFilterExtension({ + name: 'tag', loader: async () => { const { EntityTagPicker } = await import('@backstage/plugin-catalog-react'); return ; }, }); -const CatalogEntityKindFilter = createCatalogFilterExtension({ - id: 'entity.kind', +const catalogKindCatalogFilter = createCatalogFilterExtension({ + name: 'kind', configSchema: createSchemaFromZod(z => z.object({ initialFilter: z.string().default('component'), @@ -41,8 +41,8 @@ const CatalogEntityKindFilter = createCatalogFilterExtension({ }, }); -const CatalogEntityTypeFilter = createCatalogFilterExtension({ - id: 'entity.type', +const catalogTypeCatalogFilter = createCatalogFilterExtension({ + name: 'type', loader: async () => { const { EntityTypePicker } = await import( '@backstage/plugin-catalog-react' @@ -51,8 +51,8 @@ const CatalogEntityTypeFilter = createCatalogFilterExtension({ }, }); -const CatalogEntityOwnerFilter = createCatalogFilterExtension({ - id: 'entity.mode', +const catalogModeCatalogFilter = createCatalogFilterExtension({ + name: 'mode', configSchema: createSchemaFromZod(z => z.object({ mode: z.enum(['owners-only', 'all']).optional(), @@ -66,8 +66,8 @@ const CatalogEntityOwnerFilter = createCatalogFilterExtension({ }, }); -const CatalogEntityNamespaceFilter = createCatalogFilterExtension({ - id: 'entity.namespace', +const catalogNamespaceCatalogFilter = createCatalogFilterExtension({ + name: 'namespace', loader: async () => { const { EntityNamespacePicker } = await import( '@backstage/plugin-catalog-react' @@ -76,8 +76,8 @@ const CatalogEntityNamespaceFilter = createCatalogFilterExtension({ }, }); -const CatalogEntityLifecycleFilter = createCatalogFilterExtension({ - id: 'entity.lifecycle', +const catalogLifecycleCatalogFilter = createCatalogFilterExtension({ + name: 'lifecycle', loader: async () => { const { EntityLifecyclePicker } = await import( '@backstage/plugin-catalog-react' @@ -86,8 +86,8 @@ const CatalogEntityLifecycleFilter = createCatalogFilterExtension({ }, }); -const CatalogEntityProcessingStatusFilter = createCatalogFilterExtension({ - id: 'entity.processing.status', +const catalogProcessingStatusCatalogFilter = createCatalogFilterExtension({ + name: 'processing-status', loader: async () => { const { EntityProcessingStatusPicker } = await import( '@backstage/plugin-catalog-react' @@ -96,8 +96,8 @@ const CatalogEntityProcessingStatusFilter = createCatalogFilterExtension({ }, }); -const CatalogUserListFilter = createCatalogFilterExtension({ - id: 'user.list', +const catalogListCatalogFilter = createCatalogFilterExtension({ + name: 'list', configSchema: createSchemaFromZod(z => z.object({ initialFilter: z.enum(['owned', 'starred', 'all']).default('owned'), @@ -110,12 +110,12 @@ const CatalogUserListFilter = createCatalogFilterExtension({ }); export default [ - CatalogEntityTagFilter, - CatalogEntityKindFilter, - CatalogEntityTypeFilter, - CatalogEntityOwnerFilter, - CatalogEntityNamespaceFilter, - CatalogEntityLifecycleFilter, - CatalogEntityProcessingStatusFilter, - CatalogUserListFilter, + catalogTagCatalogFilter, + catalogKindCatalogFilter, + catalogTypeCatalogFilter, + catalogModeCatalogFilter, + catalogNamespaceCatalogFilter, + catalogLifecycleCatalogFilter, + catalogProcessingStatusCatalogFilter, + catalogListCatalogFilter, ]; diff --git a/plugins/catalog/src/alpha/navItems.tsx b/plugins/catalog/src/alpha/navItems.tsx index 77ea394a97..c1360cc29f 100644 --- a/plugins/catalog/src/alpha/navItems.tsx +++ b/plugins/catalog/src/alpha/navItems.tsx @@ -19,11 +19,10 @@ import { convertLegacyRouteRef } from '@backstage/core-compat-api'; import { createNavItemExtension } from '@backstage/frontend-plugin-api'; import { rootRouteRef } from '../routes'; -export const CatalogIndexNavItem = createNavItemExtension({ - id: 'catalog.nav.index', +export const catalogNavItem = createNavItemExtension({ routeRef: convertLegacyRouteRef(rootRouteRef), title: 'Catalog', icon: HomeIcon, }); -export default [CatalogIndexNavItem]; +export default [catalogNavItem]; diff --git a/plugins/catalog/src/alpha/pages.tsx b/plugins/catalog/src/alpha/pages.tsx index 6324f53083..3f40796da9 100644 --- a/plugins/catalog/src/alpha/pages.tsx +++ b/plugins/catalog/src/alpha/pages.tsx @@ -15,7 +15,10 @@ */ import React from 'react'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; import { createPageExtension, coreExtensionData, @@ -25,12 +28,11 @@ import { AsyncEntityProvider, entityRouteRef, } from '@backstage/plugin-catalog-react'; -import { entityContentTitleExtensionDataRef } from '@backstage/plugin-catalog-react/alpha'; +import { catalogExtensionData } from '@backstage/plugin-catalog-react/alpha'; import { rootRouteRef } from '../routes'; import { useEntityFromUrl } from '../components/CatalogEntityPage/useEntityFromUrl'; -export const CatalogIndexPage = createPageExtension({ - id: 'plugin.catalog.page.index', +export const catalogPage = createPageExtension({ defaultPath: '/catalog', routeRef: convertLegacyRouteRef(rootRouteRef), inputs: { @@ -40,13 +42,13 @@ export const CatalogIndexPage = createPageExtension({ }, loader: async ({ inputs }) => { const { BaseCatalogPage } = await import('../components/CatalogPage'); - const filters = inputs.filters.map(filter => filter.element); - return {filters}} />; + const filters = inputs.filters.map(filter => filter.output.element); + return compatWrapper({filters}} />); }, }); -export const CatalogEntityPage = createPageExtension({ - id: 'plugin.catalog.page.entity', +export const catalogEntityPage = createPageExtension({ + name: 'entity', defaultPath: '/catalog/:namespace/:kind/:name', routeRef: convertLegacyRouteRef(entityRouteRef), inputs: { @@ -54,7 +56,7 @@ export const CatalogEntityPage = createPageExtension({ element: coreExtensionData.reactElement, path: coreExtensionData.routePath, routeRef: coreExtensionData.routeRef.optional(), - title: entityContentTitleExtensionDataRef, + title: catalogExtensionData.entityContentTitle, }), }, loader: async ({ inputs }) => { @@ -65,19 +67,19 @@ export const CatalogEntityPage = createPageExtension({ {inputs.contents.map(content => ( - {content.element} + {content.output.element} ))} ); }; - return ; + return compatWrapper(); }, }); -export default [CatalogIndexPage, CatalogEntityPage]; +export default [catalogPage, catalogEntityPage]; diff --git a/plugins/catalog/src/alpha/plugin.tsx b/plugins/catalog/src/alpha/plugin.tsx index 9b8494f1b0..9a964c4e46 100644 --- a/plugins/catalog/src/alpha/plugin.tsx +++ b/plugins/catalog/src/alpha/plugin.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { convertLegacyRouteRefs } from '@backstage/core-compat-api'; import { createPlugin } from '@backstage/frontend-plugin-api'; import { entityRouteRef } from '@backstage/plugin-catalog-react'; @@ -23,6 +23,7 @@ import { createComponentRouteRef, createFromTemplateRouteRef, rootRouteRef, + unregisterRedirectRouteRef, viewTechDocRouteRef, } from '../routes'; @@ -37,15 +38,16 @@ import searchResultItems from './searchResultItems'; /** @alpha */ export default createPlugin({ id: 'catalog', - routes: { - catalogIndex: convertLegacyRouteRef(rootRouteRef), - catalogEntity: convertLegacyRouteRef(entityRouteRef), - }, - externalRoutes: { - viewTechDoc: convertLegacyRouteRef(viewTechDocRouteRef), - createComponent: convertLegacyRouteRef(createComponentRouteRef), - createFromTemplate: convertLegacyRouteRef(createFromTemplateRouteRef), - }, + routes: convertLegacyRouteRefs({ + catalogIndex: rootRouteRef, + catalogEntity: entityRouteRef, + }), + externalRoutes: convertLegacyRouteRefs({ + viewTechDoc: viewTechDocRouteRef, + createComponent: createComponentRouteRef, + createFromTemplate: createFromTemplateRouteRef, + unregisterRedirect: unregisterRedirectRouteRef, + }), extensions: [ ...apis, ...pages, diff --git a/plugins/catalog/src/alpha/searchResultItems.tsx b/plugins/catalog/src/alpha/searchResultItems.tsx index f677869136..5233144173 100644 --- a/plugins/catalog/src/alpha/searchResultItems.tsx +++ b/plugins/catalog/src/alpha/searchResultItems.tsx @@ -16,14 +16,12 @@ import { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha'; -export const CatalogSearchResultListItemExtension = - createSearchResultListItemExtension({ - id: 'catalog', - predicate: result => result.type === 'software-catalog', - component: () => - import('../components/CatalogSearchResultListItem').then( - m => m.CatalogSearchResultListItem, - ), - }); +export const catalogSearchResultListItem = createSearchResultListItemExtension({ + predicate: result => result.type === 'software-catalog', + component: () => + import('../components/CatalogSearchResultListItem').then( + m => m.CatalogSearchResultListItem, + ), +}); -export default [CatalogSearchResultListItemExtension]; +export default [catalogSearchResultListItem]; diff --git a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts index 5f1119e598..82862be435 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts +++ b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts @@ -134,10 +134,7 @@ export interface DefaultEntityPresentationApiOptions { * @remarks * * The keys are kinds (case insensitive) that map to icon values to represent - * kinds by. - * - * If you do not supply a set of icons here, a set of fallback icons will be - * used. If you supply the empty object, no fallback icons will be used. + * kinds by. These are merged with the default set of icons. */ kindIcons?: Record; @@ -194,11 +191,12 @@ export class DefaultEntityPresentationApi implements EntityPresentationApi { const renderer = options.renderer ?? createDefaultRenderer({ async: true }); const kindIcons: Record = {}; - Object.entries(options.kindIcons ?? DEFAULT_ICONS).forEach( - ([kind, icon]) => { - kindIcons[kind.toLocaleLowerCase('en-US')] = icon; - }, - ); + Object.entries(DEFAULT_ICONS).forEach(([kind, icon]) => { + kindIcons[kind.toLocaleLowerCase('en-US')] = icon; + }); + Object.entries(options.kindIcons ?? {}).forEach(([kind, icon]) => { + kindIcons[kind.toLocaleLowerCase('en-US')] = icon; + }); if (renderer.async) { if (!options.catalogApi) { diff --git a/plugins/catalog/src/apis/EntityPresentationApi/defaults.tsx b/plugins/catalog/src/apis/EntityPresentationApi/defaults.tsx index afcd1f5464..34bfd64d63 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/defaults.tsx +++ b/plugins/catalog/src/apis/EntityPresentationApi/defaults.tsx @@ -18,31 +18,30 @@ import { IconComponent } from '@backstage/core-plugin-api'; import { defaultEntityPresentation } from '@backstage/plugin-catalog-react'; import { HumanDuration } from '@backstage/types'; import ApartmentIcon from '@material-ui/icons/Apartment'; -import BusinessIcon from '@material-ui/icons/Business'; +import CategoryIcon from '@material-ui/icons/Category'; import ExtensionIcon from '@material-ui/icons/Extension'; -import HelpIcon from '@material-ui/icons/Help'; -import LibraryAddIcon from '@material-ui/icons/LibraryAdd'; +import FeaturedPlayListIcon from '@material-ui/icons/FeaturedPlayList'; import LocationOnIcon from '@material-ui/icons/LocationOn'; import MemoryIcon from '@material-ui/icons/Memory'; import PeopleIcon from '@material-ui/icons/People'; import PersonIcon from '@material-ui/icons/Person'; +import StorageIcon from '@material-ui/icons/Storage'; import { DefaultEntityPresentationApiRenderer } from './DefaultEntityPresentationApi'; export const DEFAULT_CACHE_TTL: HumanDuration = { seconds: 10 }; export const DEFAULT_BATCH_DELAY: HumanDuration = { milliseconds: 50 }; -export const UNKNOWN_KIND_ICON: IconComponent = HelpIcon; - export const DEFAULT_ICONS: Record = { api: ExtensionIcon, component: MemoryIcon, - system: BusinessIcon, + system: CategoryIcon, + resource: StorageIcon, domain: ApartmentIcon, location: LocationOnIcon, user: PersonIcon, group: PeopleIcon, - template: LibraryAddIcon, + template: FeaturedPlayListIcon, }; export function createDefaultRenderer(options: { diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index 1679e2ddf5..e27de38913 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -33,6 +33,12 @@ import { RELATION_OWNED_BY } from '@backstage/catalog-model'; import React from 'react'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { permissionApiRef } from '@backstage/plugin-permission-react'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; + +const mockAuthorize = jest.fn(); + +const mockPermissionApi = { authorize: mockAuthorize }; describe('', () => { const catalogApi: jest.Mocked = { @@ -87,6 +93,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -143,6 +150,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -198,6 +206,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -240,6 +249,7 @@ describe('', () => { ScmIntegrationsApi.fromConfig(new ConfigReader({})), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -276,6 +286,10 @@ describe('', () => { }, }; + mockAuthorize.mockImplementation(async () => ({ + result: AuthorizeResult.ALLOW, + })); + await renderInTestApp( ', () => { ScmIntegrationsApi.fromConfig(new ConfigReader({})), ], [catalogApiRef, catalogApi], + [permissionApiRef, mockPermissionApi], ]} > @@ -308,6 +323,55 @@ describe('', () => { ); }); + it('should not render refresh button if the permission is DENY', async () => { + const entity = { + apiVersion: 'v1', + kind: 'Component', + metadata: { + annotations: { + 'backstage.io/managed-by-location': + 'url:https://backstage.io/catalog-info.yaml', + }, + name: 'software-deny', + }, + spec: { + owner: 'guest', + type: 'service', + lifecycle: 'production', + }, + }; + + mockAuthorize.mockImplementation(async () => ({ + result: AuthorizeResult.DENY, + })); + + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + expect( + screen.queryByTitle('Schedule entity refresh'), + ).not.toBeInTheDocument(); + }); + it('should not render refresh button if the location is not an url or file', async () => { const entity = { apiVersion: 'v1', @@ -330,6 +394,7 @@ describe('', () => { ScmIntegrationsApi.fromConfig(new ConfigReader({})), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -384,6 +449,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -440,6 +506,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -493,6 +560,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -546,6 +614,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -592,6 +661,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -659,6 +729,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > @@ -707,6 +778,7 @@ describe('', () => { ), ], [catalogApiRef, catalogApi], + [permissionApiRef, {}], ]} > diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index 34aa463c39..763f6fc094 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -60,6 +60,8 @@ import DocsIcon from '@material-ui/icons/Description'; import EditIcon from '@material-ui/icons/Edit'; import { isTemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { parseEntityRef } from '@backstage/catalog-model'; +import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha'; +import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha'; const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; @@ -108,6 +110,9 @@ export function AboutCard(props: AboutCardProps) { const errorApi = useApi(errorApiRef); const viewTechdocLink = useRouteRef(viewTechDocRouteRef); const templateRoute = useRouteRef(createFromTemplateRouteRef); + const { allowed: canRefresh } = useEntityPermission( + catalogEntityRefreshPermission, + ); const entitySourceLocation = getEntitySourceLocation( entity, @@ -215,7 +220,7 @@ export function AboutCard(props: AboutCardProps) { title="About" action={ <> - {allowRefresh && ( + {allowRefresh && canRefresh && ( { const origReplaceState = window.history.replaceState; @@ -168,6 +170,7 @@ describe('DefaultCatalogPage', () => { [identityApiRef, identityApi], [storageApiRef, storageApi], [starredEntitiesApiRef, new MockStarredEntitiesApi()], + [permissionApiRef, new MockPermissionApi()], ]} > {children} diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx index 1f724d10fc..779b347b48 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx @@ -45,6 +45,8 @@ import { catalogTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { CatalogTableColumnsFunc } from '../CatalogTable/types'; +import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; +import { usePermission } from '@backstage/plugin-permission-react'; /** @internal */ export type BaseCatalogPageProps = { @@ -60,15 +62,20 @@ export function BaseCatalogPage(props: BaseCatalogPageProps) { useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; const createComponentLink = useRouteRef(createComponentRouteRef); const { t } = useTranslationRef(catalogTranslationRef); + const { allowed } = usePermission({ + permission: catalogEntityCreatePermission, + }); return ( - + - + {allowed && ( + + )} All your software catalog entities diff --git a/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx index 06906aecc1..4c6b567d8a 100644 --- a/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx +++ b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx @@ -107,6 +107,7 @@ export function CatalogSearchResultListItem( /> {result.kind && } + {result.type && } {result.lifecycle && ( )} diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 006210bb1d..7da12d7138 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -47,6 +47,7 @@ import React, { ReactNode, useMemo } from 'react'; import { columnFactories } from './columns'; import { CatalogTableColumnsFunc, CatalogTableRow } from './types'; import { PaginatedCatalogTable } from './PaginatedCatalogTable'; +import { defaultCatalogTableColumnsFunc } from './defaultCatalogTableColumnsFunc'; /** * Props for {@link CatalogTable}. @@ -77,52 +78,10 @@ const refCompare = (a: Entity, b: Entity) => { return toRef(a).localeCompare(toRef(b)); }; -const defaultColumnsFunc: CatalogTableColumnsFunc = ({ filters, entities }) => { - const showTypeColumn = filters.type === undefined; - - return [ - columnFactories.createTitleColumn({ hidden: true }), - columnFactories.createNameColumn({ defaultKind: filters.kind?.value }), - ...createEntitySpecificColumns(), - columnFactories.createMetadataDescriptionColumn(), - columnFactories.createTagsColumn(), - ]; - - function createEntitySpecificColumns(): TableColumn[] { - const baseColumns = [ - columnFactories.createSystemColumn(), - columnFactories.createOwnerColumn(), - columnFactories.createSpecTypeColumn({ hidden: !showTypeColumn }), - columnFactories.createSpecLifecycleColumn(), - ]; - switch (filters.kind?.value) { - case 'user': - return []; - case 'domain': - case 'system': - return [columnFactories.createOwnerColumn()]; - case 'group': - case 'template': - return [ - columnFactories.createSpecTypeColumn({ hidden: !showTypeColumn }), - ]; - case 'location': - return [ - columnFactories.createSpecTypeColumn({ hidden: !showTypeColumn }), - columnFactories.createSpecTargetsColumn(), - ]; - default: - return entities.every(entity => entity.metadata.namespace === 'default') - ? baseColumns - : [...baseColumns, columnFactories.createNamespaceColumn()]; - } - } -}; - /** @public */ export const CatalogTable = (props: CatalogTableProps) => { const { - columns = defaultColumnsFunc, + columns = defaultCatalogTableColumnsFunc, tableOptions, subtitle, emptyContent, @@ -267,6 +226,7 @@ export const CatalogTable = (props: CatalogTableProps) => { }; CatalogTable.columns = columnFactories; +CatalogTable.defaultColumnsFunc = defaultCatalogTableColumnsFunc; function toEntityRow(entity: Entity) { const partOfSystemRelations = getEntityRelations(entity, RELATION_PART_OF, { diff --git a/plugins/catalog/src/components/CatalogTable/defaultCatalogTableColumnsFunc.tsx b/plugins/catalog/src/components/CatalogTable/defaultCatalogTableColumnsFunc.tsx new file mode 100644 index 0000000000..85f44d3f51 --- /dev/null +++ b/plugins/catalog/src/components/CatalogTable/defaultCatalogTableColumnsFunc.tsx @@ -0,0 +1,67 @@ +/* + * 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 { TableColumn } from '@backstage/core-components'; +import { columnFactories } from './columns'; +import { CatalogTableColumnsFunc, CatalogTableRow } from './types'; + +// The defaultCatalogTableColumnsFunc symbol is not directly exported, but through the +// CatalogTable.defaultColumnsFunc field. +/** @public */ +export const defaultCatalogTableColumnsFunc: CatalogTableColumnsFunc = ({ + filters, + entities, +}) => { + const showTypeColumn = filters.type === undefined; + + return [ + columnFactories.createTitleColumn({ hidden: true }), + columnFactories.createNameColumn({ defaultKind: filters.kind?.value }), + ...createEntitySpecificColumns(), + columnFactories.createMetadataDescriptionColumn(), + columnFactories.createTagsColumn(), + ]; + + function createEntitySpecificColumns(): TableColumn[] { + const baseColumns = [ + columnFactories.createSystemColumn(), + columnFactories.createOwnerColumn(), + columnFactories.createSpecTypeColumn({ hidden: !showTypeColumn }), + columnFactories.createSpecLifecycleColumn(), + ]; + switch (filters.kind?.value) { + case 'user': + return []; + case 'domain': + case 'system': + return [columnFactories.createOwnerColumn()]; + case 'group': + case 'template': + return [ + columnFactories.createSpecTypeColumn({ hidden: !showTypeColumn }), + ]; + case 'location': + return [ + columnFactories.createSpecTypeColumn({ hidden: !showTypeColumn }), + columnFactories.createSpecTargetsColumn(), + ]; + default: + return entities.every(entity => entity.metadata.namespace === 'default') + ? baseColumns + : [...baseColumns, columnFactories.createNamespaceColumn()]; + } + } +}; diff --git a/plugins/catalog/src/translation.ts b/plugins/catalog/src/translation.ts index 2d1e3f2c8a..bd27069fde 100644 --- a/plugins/catalog/src/translation.ts +++ b/plugins/catalog/src/translation.ts @@ -20,7 +20,9 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; export const catalogTranslationRef = createTranslationRef({ id: 'catalog', messages: { - catalog_page_title: `{{orgName}} Catalog`, - catalog_page_create_button_title: 'Create', + indexPage: { + title: `{{orgName}} Catalog`, + createButtonTitle: 'Create', + }, }, }); diff --git a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md index 06bce48963..1d134138ba 100644 --- a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md +++ b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md @@ -1,5 +1,84 @@ # @backstage/plugin-cicd-statistics-module-gitlab +## 0.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cicd-statistics@0.1.32-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.1.25 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-cicd-statistics@0.1.31 + +## 0.1.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cicd-statistics@0.1.31-next.2 + +## 0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-cicd-statistics@0.1.31-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-cicd-statistics@0.1.31-next.0 + +## 0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-cicd-statistics@0.1.30 + - @backstage/catalog-model@1.4.3 + +## 0.1.24-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-cicd-statistics@0.1.30-next.3 + +## 0.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-cicd-statistics@0.1.30-next.2 + +## 0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-cicd-statistics@0.1.30-next.1 + - @backstage/catalog-model@1.4.3 + ## 0.1.24-next.0 ### Patch Changes diff --git a/plugins/cicd-statistics-module-gitlab/package.json b/plugins/cicd-statistics-module-gitlab/package.json index 15c0602394..9ab05f6aac 100644 --- a/plugins/cicd-statistics-module-gitlab/package.json +++ b/plugins/cicd-statistics-module-gitlab/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cicd-statistics-module-gitlab", "description": "CI/CD Statistics plugin module; Gitlab CICD", - "version": "0.1.24-next.0", + "version": "0.1.26-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cicd-statistics/CHANGELOG.md b/plugins/cicd-statistics/CHANGELOG.md index 0cdd53dc87..d1099764d9 100644 --- a/plugins/cicd-statistics/CHANGELOG.md +++ b/plugins/cicd-statistics/CHANGELOG.md @@ -1,5 +1,84 @@ # @backstage/plugin-cicd-statistics +## 0.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.1.31 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.1.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.1.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/catalog-model@1.4.3 + +## 0.1.30-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.1.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + ## 0.1.30-next.0 ### Patch Changes diff --git a/plugins/cicd-statistics/package.json b/plugins/cicd-statistics/package.json index 35a44e3fd0..a989af1820 100644 --- a/plugins/cicd-statistics/package.json +++ b/plugins/cicd-statistics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cicd-statistics", "description": "A frontend plugin visualizing CI/CD pipeline statistics (build time)", - "version": "0.1.30-next.0", + "version": "0.1.32-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 79a0b71b57..413ae8b12f 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,100 @@ # @backstage/plugin-circleci +## 0.3.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.3.29 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.3.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.3.29-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.3.28 + +### Patch Changes + +- 375b6f7: CircelCI plugin moved permanently +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## 0.3.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.3.28-next.1 + +### Patch Changes + +- 375b6f7d68: CircelCI plugin moved permanently +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.3.28-next.0 ### Patch Changes diff --git a/plugins/circleci/README.md b/plugins/circleci/README.md index f468ab37e2..86af7081c7 100644 --- a/plugins/circleci/README.md +++ b/plugins/circleci/README.md @@ -1,6 +1,7 @@ # CircleCI Plugin -Website: [https://circleci.com/](https://circleci.com/) +> [!IMPORTANT] +> This plugin is now developed & maintained by CircleCI. Please refer to [their up-to-date documentation](https://github.com/CircleCI-Public/backstage-plugin) & [plugin repository](https://github.com/CircleCI-Public/backstage-plugin/) for help. ## Screenshots @@ -14,7 +15,7 @@ Website: [https://circleci.com/](https://circleci.com/) ```bash # From your Backstage root directory -yarn add --cwd packages/app @backstage/plugin-circleci +yarn add --cwd packages/app @circleci/backstage-plugin ``` 2. Add the `EntityCircleCIContent` extension to the entity page in your app: @@ -24,7 +25,7 @@ yarn add --cwd packages/app @backstage/plugin-circleci import { EntityCircleCIContent, isCircleCIAvailable, -} from '@backstage/plugin-circleci'; +} from '@circleci/backstage-plugin'; // For example in the CI/CD section const cicdContent = ( diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 7075a383a8..9eb43648af 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-circleci", "description": "A Backstage plugin that integrates towards Circle CI", - "version": "0.3.28-next.0", + "version": "0.3.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -38,10 +38,8 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "circleci-api": "^4.0.0", "humanize-duration": "^3.27.0", @@ -56,16 +54,8 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", - "@testing-library/dom": "^9.0.0", - "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "@types/humanize-duration": "^3.25.1", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@types/humanize-duration": "^3.25.1" }, "files": [ "dist" diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 32ea259d87..ff87a63e08 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,110 @@ # @backstage/plugin-cloudbuild +## 0.4.0-next.0 + +### Minor Changes + +- 0328d1b: Changed build list view to automatically filter builds based on repository name matching component-info's metadata.name. + Added optional `google.com/cloudbuild-repo-name` annotation which allows you to specify a different repository to filter on. + Added optional `google.com/cloudbuild-trigger-name` annotation which allows you to filter based on a trigger name instead of a repo name. + Updated the ReadMe with information about the filtering and some other minor verbiage updates. + Changed `substitutions.BRANCH_NAME` to `substitutions.REF_NAME` so that the Ref field is populated properly. + Added optional `google.com/cloudbuild-location` annotation which allows you to specify the Cloud Build location of your builds. Default is global scope. + Changed build list view to show builds in a specific location if the location annotation is used. + Updated ReadMe with information about the use of the location filtering. + +### Patch Changes + +- ef3cad4: Add telemetry HTTP header Google Cloud Platform +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.3.29 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.3.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.3.29-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## 0.3.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.3.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.3.28-next.0 ### Patch Changes diff --git a/plugins/cloudbuild/README.md b/plugins/cloudbuild/README.md index 07999c85a1..2677d02b13 100644 --- a/plugins/cloudbuild/README.md +++ b/plugins/cloudbuild/README.md @@ -1,4 +1,4 @@ -# Google Cloud Build +# Google Cloud Build Plugin ### Welcome to the Google Cloud Build plugin! @@ -47,7 +47,7 @@ const cicdContent = ( ##### OPTIONAL -If you don't use GitHub Actions, or don't want to show it on your CI/CD page, then you can remove the switch case for it +If you don't use GitHub Actions, or don't want to show it on your CI/CD page, then you can remove the switch case for it: ```diff // packages/app/src/components/catalog/EntityPage.tsx @@ -62,9 +62,9 @@ const cicdContent = ( - ``` -### Add annotation to your component-info.yaml file. +### Add annotation(s) to your component-info.yaml file -Any component, that you would like the Cloud Build Plugin to populate for, should include the following annotation: +Any component, that you would like the Cloud Build Plugin to populate for, should include the following `cloudbuild-project-slug` annotation. This annotation sets the GCP project name to be used for pulling the Cloud Build details from. ```diff // component-info.yaml @@ -79,3 +79,62 @@ spec: type: website lifecycle: development ``` + +By default, the cloud build results list is filtered by repository name equal to the name you have set in your component-info.yaml file. This is `metadata.name`. So if your metadata.name is `backstage` then it will only show builds matching the backstage repo name. + +Additionally, build results are pulled from the `global` region by default. + +#### Change Filtering + +If you need the page to be filtered on a different repository name, then you can use the following annotation: + +```diff +// component-info.yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage + description: Backstage application. + annotations: + google.com/cloudbuild-project-slug: your-project-name ++ google.com/cloudbuild-repo-name: my-backstage +spec: + type: website + lifecycle: development +``` + +You can also automatically filter the results based on trigger name instead of repository name. To do so, use the following annotation: + +```diff +// component-info.yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage + description: Backstage application. + annotations: + google.com/cloudbuild-project-slug: your-project-name ++ google.com/cloudbuild-trigger-name: my-trigger-name +spec: + type: website + lifecycle: development +``` + +`Note:` The `cloudbuild-repo-name` annotation takes precedence over the `cloudbuild-trigger-name` annotation. So if you happen to use both annotations, cloudbuild-repo-name will be used. It is recommended to use one or the other if required. + +If you need to pull Cloud Build results from a location or region other than the global scope, then use the following annotation: + +```diff +// component-info.yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage + description: Backstage application. + annotations: + google.com/cloudbuild-project-slug: your-project-name ++ google.com/cloudbuild-location: us-central1 +spec: + type: website + lifecycle: development +``` diff --git a/plugins/cloudbuild/api-report.md b/plugins/cloudbuild/api-report.md index 3fbb15000d..2ce42d5e34 100644 --- a/plugins/cloudbuild/api-report.md +++ b/plugins/cloudbuild/api-report.md @@ -58,17 +58,22 @@ export const CLOUDBUILD_ANNOTATION = 'google.com/cloudbuild-project-slug'; export type CloudbuildApi = { listWorkflowRuns: (options: { projectId: string; + location: string; + cloudBuildFilter: string; }) => Promise; getWorkflow: (options: { projectId: string; + location: string; id: string; }) => Promise; getWorkflowRun: (options: { projectId: string; + location: string; id: string; }) => Promise; reRunWorkflow: (options: { projectId: string; + location: string; runId: string; }) => Promise; }; @@ -84,19 +89,27 @@ export class CloudbuildClient implements CloudbuildApi { // (undocumented) getWorkflow(options: { projectId: string; + location: string; id: string; }): Promise; // (undocumented) getWorkflowRun(options: { projectId: string; + location: string; id: string; }): Promise; // (undocumented) listWorkflowRuns(options: { projectId: string; + location: string; + cloudBuildFilter: string; }): Promise; // (undocumented) - reRunWorkflow(options: { projectId: string; runId: string }): Promise; + reRunWorkflow(options: { + projectId: string; + location: string; + runId: string; + }): Promise; } // @public (undocumented) @@ -234,11 +247,11 @@ export interface StorageSource { // @public (undocumented) export interface Substitutions { - // (undocumented) - BRANCH_NAME: string; // (undocumented) COMMIT_SHA: string; // (undocumented) + REF_NAME: string; + // (undocumented) REPO_NAME: string; // (undocumented) REVISION_ID: string; diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 1caaf87edd..00f0a7ea11 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cloudbuild", "description": "A Backstage plugin that integrates towards Google Cloud Build", - "version": "0.3.28-next.0", + "version": "0.4.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -38,10 +38,8 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "luxon": "^3.0.0", "qs": "^6.9.4", @@ -54,15 +52,11 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/cloudbuild/src/api/CloudbuildApi.ts b/plugins/cloudbuild/src/api/CloudbuildApi.ts index cb29b49fbf..9e2d63fc01 100644 --- a/plugins/cloudbuild/src/api/CloudbuildApi.ts +++ b/plugins/cloudbuild/src/api/CloudbuildApi.ts @@ -29,17 +29,22 @@ export const cloudbuildApiRef = createApiRef({ export type CloudbuildApi = { listWorkflowRuns: (options: { projectId: string; + location: string; + cloudBuildFilter: string; }) => Promise; getWorkflow: (options: { projectId: string; + location: string; id: string; }) => Promise; getWorkflowRun: (options: { projectId: string; + location: string; id: string; }) => Promise; reRunWorkflow: (options: { projectId: string; + location: string; runId: string; }) => Promise; }; diff --git a/plugins/cloudbuild/src/api/CloudbuildClient.ts b/plugins/cloudbuild/src/api/CloudbuildClient.ts index cb6747e803..256e702cda 100644 --- a/plugins/cloudbuild/src/api/CloudbuildClient.ts +++ b/plugins/cloudbuild/src/api/CloudbuildClient.ts @@ -20,6 +20,7 @@ import { ActionsGetWorkflowResponseData, } from '../api/types'; import { OAuthApi } from '@backstage/core-plugin-api'; +import packageinfo from '../../package.json'; /** @public */ export class CloudbuildClient implements CloudbuildApi { @@ -27,35 +28,30 @@ export class CloudbuildClient implements CloudbuildApi { async reRunWorkflow(options: { projectId: string; + location: string; runId: string; }): Promise { - await fetch( + await this.request( `https://cloudbuild.googleapis.com/v1/projects/${encodeURIComponent( options.projectId, + )}/locations/${encodeURIComponent( + options.location, )}/builds/${encodeURIComponent(options.runId)}:retry`, - { - method: 'POST', - headers: new Headers({ - Accept: '*/*', - Authorization: `Bearer ${await this.getToken()}`, - }), - }, + 'POST', ); } async listWorkflowRuns(options: { projectId: string; + location: string; + cloudBuildFilter: string; }): Promise { - const workflowRuns = await fetch( + const workflowRuns = await this.request( `https://cloudbuild.googleapis.com/v1/projects/${encodeURIComponent( options.projectId, - )}/builds`, - { - headers: new Headers({ - Accept: '*/*', - Authorization: `Bearer ${await this.getToken()}`, - }), - }, + )}/locations/${encodeURIComponent( + options.location, + )}/builds?filter=${encodeURIComponent(options.cloudBuildFilter)}`, ); const builds: ActionsListWorkflowRunsForRepoResponseData = @@ -66,18 +62,15 @@ export class CloudbuildClient implements CloudbuildApi { async getWorkflow(options: { projectId: string; + location: string; id: string; }): Promise { - const workflow = await fetch( + const workflow = await this.request( `https://cloudbuild.googleapis.com/v1/projects/${encodeURIComponent( options.projectId, + )}/locations/${encodeURIComponent( + options.location, )}/builds/${encodeURIComponent(options.id)}`, - { - headers: new Headers({ - Accept: '*/*', - Authorization: `Bearer ${await this.getToken()}`, - }), - }, ); const build: ActionsGetWorkflowResponseData = await workflow.json(); @@ -87,18 +80,15 @@ export class CloudbuildClient implements CloudbuildApi { async getWorkflowRun(options: { projectId: string; + location: string; id: string; }): Promise { - const workflow = await fetch( + const workflow = await this.request( `https://cloudbuild.googleapis.com/v1/projects/${encodeURIComponent( options.projectId, + )}/locations/${encodeURIComponent( + options.location, )}/builds/${encodeURIComponent(options.id)}`, - { - headers: new Headers({ - Accept: '*/*', - Authorization: `Bearer ${await this.getToken()}`, - }), - }, ); const build: ActionsGetWorkflowResponseData = await workflow.json(); @@ -114,4 +104,23 @@ export class CloudbuildClient implements CloudbuildApi { 'https://www.googleapis.com/auth/cloud-platform', ); } + + private async request( + url: string, + method: string = 'GET', + ): Promise { + const requestHeaders = { + Accept: '*/*', + Authorization: `Bearer ${await this.getToken()}`, + ...(process.env.NODE_ENV === 'production' + ? { + 'X-Goog-Api-Client': `backstage/cloudbuild/${packageinfo.version}`, + } + : {}), + }; + return fetch(url, { + method, + headers: requestHeaders, + }); + } } diff --git a/plugins/cloudbuild/src/api/types.ts b/plugins/cloudbuild/src/api/types.ts index 1dd03265bc..b721e8dca1 100644 --- a/plugins/cloudbuild/src/api/types.ts +++ b/plugins/cloudbuild/src/api/types.ts @@ -81,7 +81,7 @@ export interface Options { export interface Substitutions { COMMIT_SHA: string; SHORT_SHA: string; - BRANCH_NAME: string; + REF_NAME: string; REPO_NAME: string; REVISION_ID: string; } diff --git a/plugins/cloudbuild/src/components/Cards/Cards.tsx b/plugins/cloudbuild/src/components/Cards/Cards.tsx index 11efe520e0..5d837cce68 100644 --- a/plugins/cloudbuild/src/components/Cards/Cards.tsx +++ b/plugins/cloudbuild/src/components/Cards/Cards.tsx @@ -22,6 +22,8 @@ import { WorkflowRunStatus } from '../WorkflowRunStatus'; import { makeStyles, LinearProgress } from '@material-ui/core'; import ExternalLinkIcon from '@material-ui/icons/Launch'; import { CLOUDBUILD_ANNOTATION } from '../useProjectName'; +import { getLocation } from '../useLocation'; +import { getCloudbuildFilter } from '../useCloudBuildFilter'; import { InfoCard, @@ -79,9 +81,13 @@ export const LatestWorkflowRunCard = (props: { branch: string }) => { const { entity } = useEntity(); const errorApi = useApi(errorApiRef); const projectId = entity?.metadata.annotations?.[CLOUDBUILD_ANNOTATION] || ''; + const location = getLocation(entity); + const cloudBuildFilter = getCloudbuildFilter(entity); const [{ runs, loading, error }] = useWorkflowRuns({ projectId, + location, + cloudBuildFilter, }); const lastRun = runs?.[0] ?? ({} as WorkflowRun); useEffect(() => { diff --git a/plugins/cloudbuild/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/cloudbuild/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx index f55d7957a3..1c9bdcbf5f 100644 --- a/plugins/cloudbuild/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx +++ b/plugins/cloudbuild/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -34,6 +34,7 @@ import { useProjectName } from '../useProjectName'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; import { useWorkflowRunsDetails } from './useWorkflowRunsDetails'; import { Breadcrumbs, Link, WarningPanel } from '@backstage/core-components'; +import { getLocation } from '../useLocation'; const useStyles = makeStyles(theme => ({ root: { @@ -63,8 +64,9 @@ const useStyles = makeStyles(theme => ({ export const WorkflowRunDetails = (props: { entity: Entity }) => { const { value: projectName, loading, error } = useProjectName(props.entity); const [projectId] = (projectName ?? '/').split('/'); + const location = getLocation(props.entity); - const details = useWorkflowRunsDetails(projectId); + const details = useWorkflowRunsDetails(projectId, location); const classes = useStyles(); if (error) { @@ -96,9 +98,9 @@ export const WorkflowRunDetails = (props: { entity: Entity }) => { - Branch + Ref - {details.value?.substitutions.BRANCH_NAME} + {details.value?.substitutions.REF_NAME} diff --git a/plugins/cloudbuild/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts b/plugins/cloudbuild/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts index 549ab1eaf2..abc146fb6c 100644 --- a/plugins/cloudbuild/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts +++ b/plugins/cloudbuild/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts @@ -18,13 +18,14 @@ import { cloudbuildApiRef } from '../../api'; import { useApi, useRouteRefParams } from '@backstage/core-plugin-api'; import { buildRouteRef } from '../../routes'; -export const useWorkflowRunsDetails = (projectId: string) => { +export const useWorkflowRunsDetails = (projectId: string, location: string) => { const api = useApi(cloudbuildApiRef); const { id } = useRouteRefParams(buildRouteRef); const details = useAsync(async () => { return projectId ? api.getWorkflowRun({ projectId, + location, id: id, }) : Promise.reject('No projectId provided'); diff --git a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.test.tsx b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.test.tsx index 0152f9d964..92a806bee7 100644 --- a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.test.tsx +++ b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.test.tsx @@ -35,7 +35,7 @@ describe('', () => { substitutions: { COMMIT_SHA: 'e3adasd2e3adasd2e3adasd2', SHORT_SHA: 'f12j1231', - BRANCH_NAME: 'main', + REF_NAME: 'main', REPO_NAME: 'backstage', REVISION_ID: 'g123123', }, diff --git a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index 294847a700..65b5ca149f 100644 --- a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -26,6 +26,8 @@ import { buildRouteRef } from '../../routes'; import { DateTime } from 'luxon'; import { Table, TableColumn, Link } from '@backstage/core-components'; import { useRouteRef } from '@backstage/core-plugin-api'; +import { getLocation } from '../useLocation'; +import { getCloudbuildFilter } from '../useCloudBuildFilter'; const generatedColumns: TableColumn[] = [ { @@ -71,7 +73,7 @@ const generatedColumns: TableColumn[] = [ title: 'Ref', render: (row: Partial) => ( - {row.substitutions?.BRANCH_NAME} + {row.substitutions?.REF_NAME} ), }, @@ -162,9 +164,12 @@ export const WorkflowRunsTableView = ({ export const WorkflowRunsTable = (props: { entity: Entity }) => { const { value: projectName, loading } = useProjectName(props.entity); const [projectId] = (projectName ?? '/').split('/'); - + const location = getLocation(props.entity); + const cloudBuildFilter = getCloudbuildFilter(props.entity); const [tableProps, { retry, setPage, setPageSize }] = useWorkflowRuns({ projectId, + location, + cloudBuildFilter, }); return ( diff --git a/plugins/cloudbuild/src/components/useCloudBuildFilter.ts b/plugins/cloudbuild/src/components/useCloudBuildFilter.ts new file mode 100644 index 0000000000..a71cc1e9b6 --- /dev/null +++ b/plugins/cloudbuild/src/components/useCloudBuildFilter.ts @@ -0,0 +1,43 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; + +const CLOUDBUILD_FILTER_REPO_STRING = 'substitutions.REPO_NAME='; +const CLOUDBUILD_FILTER_REPO_ANNOTATION = 'google.com/cloudbuild-repo-name'; +const CLOUDBUILD_FILTER_TRIGGER_STRING = 'substitutions.TRIGGER_NAME='; +const CLOUDBUILD_FILTER_TRIGGER_ANNOTATION = + 'google.com/cloudbuild-trigger-name'; + +/** @public */ + +export const getCloudbuildFilter = (entity: Entity) => { + const repoAnnotation = + entity?.metadata.annotations?.[CLOUDBUILD_FILTER_REPO_ANNOTATION] ?? ''; + const triggerAnnotation = + entity?.metadata.annotations?.[CLOUDBUILD_FILTER_TRIGGER_ANNOTATION] ?? ''; + if (repoAnnotation) { + const cloudbuildFilter = CLOUDBUILD_FILTER_REPO_STRING + repoAnnotation; + return cloudbuildFilter; + } else if (triggerAnnotation) { + const cloudbuildFilter = + CLOUDBUILD_FILTER_TRIGGER_STRING + triggerAnnotation; + return cloudbuildFilter; + } + const entityName = entity?.metadata.name ?? ''; + const cloudbuildFilter = CLOUDBUILD_FILTER_REPO_STRING + entityName; + return cloudbuildFilter; +}; diff --git a/plugins/cloudbuild/src/components/useLocation.ts b/plugins/cloudbuild/src/components/useLocation.ts new file mode 100644 index 0000000000..54a3c8f776 --- /dev/null +++ b/plugins/cloudbuild/src/components/useLocation.ts @@ -0,0 +1,30 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; + +const CLOUDBUILD_LOCATION_ANNOTATION = 'google.com/cloudbuild-location'; + +/** @public */ + +export const getLocation = (entity: Entity) => { + const locationAnnotation = + entity?.metadata.annotations?.[CLOUDBUILD_LOCATION_ANNOTATION] ?? ''; + if (locationAnnotation) { + return locationAnnotation; + } + return 'global'; +}; diff --git a/plugins/cloudbuild/src/components/useWorkflowRuns.ts b/plugins/cloudbuild/src/components/useWorkflowRuns.ts index 424f08e38d..7e3e148dd1 100644 --- a/plugins/cloudbuild/src/components/useWorkflowRuns.ts +++ b/plugins/cloudbuild/src/components/useWorkflowRuns.ts @@ -33,8 +33,12 @@ export type WorkflowRun = { rerun: () => void; }; -export function useWorkflowRuns(options: { projectId: string }) { - const { projectId } = options; +export function useWorkflowRuns(options: { + projectId: string; + location: string; + cloudBuildFilter: string; +}) { + const { projectId, location, cloudBuildFilter } = options; const api = useApi(cloudbuildApiRef); const errorApi = useApi(errorApiRef); @@ -51,6 +55,8 @@ export function useWorkflowRuns(options: { projectId: string }) { return api .listWorkflowRuns({ projectId, + location, + cloudBuildFilter, }) .then( ( @@ -65,6 +71,7 @@ export function useWorkflowRuns(options: { projectId: string }) { try { await api.reRunWorkflow({ projectId, + location, runId: run.id, }); } catch (e) { diff --git a/plugins/code-climate/CHANGELOG.md b/plugins/code-climate/CHANGELOG.md index dd10502cf0..9e653f5879 100644 --- a/plugins/code-climate/CHANGELOG.md +++ b/plugins/code-climate/CHANGELOG.md @@ -1,5 +1,98 @@ # @backstage/plugin-code-climate +## 0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.1.29 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.1.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.1.29-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.1.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## 0.1.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.1.28-next.0 ### Patch Changes diff --git a/plugins/code-climate/package.json b/plugins/code-climate/package.json index 5210337fda..b142f7bb40 100644 --- a/plugins/code-climate/package.json +++ b/plugins/code-climate/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-code-climate", - "version": "0.1.28-next.0", + "version": "0.1.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,10 +33,7 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "humanize-duration": "^3.27.1", "luxon": "^3.0.0", @@ -54,10 +51,8 @@ "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "@types/humanize-duration": "^3.27.1", - "@types/luxon": "^3.0.0", - "msw": "^1.0.0" + "@types/luxon": "^3.0.0" }, "files": [ "dist" diff --git a/plugins/code-coverage-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md index 08fe1692d8..ed75e028d1 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,128 @@ # @backstage/plugin-code-coverage-backend +## 0.2.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.2.23 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.2.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.2.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.23-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 0.2.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/integration@1.8.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.2.22-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 0.2.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## 0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 0.2.22-next.0 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index 4bd83b92f6..d929fd4006 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage-backend", "description": "A Backstage backend plugin that helps you keep track of your code coverage", - "version": "0.2.22-next.0", + "version": "0.2.24-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -51,7 +51,6 @@ "@backstage/cli": "workspace:^", "@types/body-parser-xml": "^2.0.2", "@types/supertest": "^2.0.8", - "msw": "^1.0.0", "supertest": "^6.1.6", "xml2js": "^0.6.0" }, diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index a29cf758a8..534fbc0d52 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,110 @@ # @backstage/plugin-code-coverage +## 0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## 0.2.22 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.22-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.2.21-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.2.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.2.21-next.0 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index c4fda6c18b..52dd2a18ec 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage", "description": "A Backstage plugin that helps you keep track of your code coverage", - "version": "0.2.21-next.0", + "version": "0.2.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,12 +31,10 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", @@ -53,17 +51,13 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "@types/highlightjs": "^10.1.0", "@types/react": "^16.13.1 || ^17.0.0", - "@types/recharts": "^1.8.15", - "msw": "^1.0.0" + "@types/recharts": "^1.8.15" }, "files": [ "dist" diff --git a/plugins/codescene/CHANGELOG.md b/plugins/codescene/CHANGELOG.md index 36af453283..df9ed3199f 100644 --- a/plugins/codescene/CHANGELOG.md +++ b/plugins/codescene/CHANGELOG.md @@ -1,5 +1,82 @@ # @backstage/plugin-codescene +## 0.1.21 + +### Patch Changes + +- d5eda61: Updated Readme document in codescene plugin +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.21-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + +## 0.1.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.1.20-next.0 ### Patch Changes diff --git a/plugins/codescene/README.md b/plugins/codescene/README.md index bbfb58e68f..8c84c9015c 100644 --- a/plugins/codescene/README.md +++ b/plugins/codescene/README.md @@ -1,8 +1,8 @@ # codescene -[CodeScene](https://codescene.com/) is a multi-purpose tool bridging code, business and people. See hidden risks and social patterns in your code. Prioritize and reduce technical debt. +[CodeScene](https://codescene.com/) is a multi-purpose tool that connects code, businesses, and people. Discover hidden hazards and social trends in your code. Prioritise and minimise technical debt. -The CodeScene Backstage Plugin exposes a page component that will list the existing projects and their analysis data on your CodeScene instance. +The CodeScene Backstage Plugin provides a page component that displays a list of existing projects and associated analysis data on your CodeScene instance. ![screenshot](./docs/codescene-plugin-screenshot.png) diff --git a/plugins/codescene/package.json b/plugins/codescene/package.json index 9296b76c55..f0dfd20394 100644 --- a/plugins/codescene/package.json +++ b/plugins/codescene/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-codescene", - "version": "0.1.20-next.0", + "version": "0.1.21", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,9 +33,7 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "rc-progress": "3.5.1", @@ -54,7 +52,6 @@ "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0" }, "files": [ diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index b05b2e5e85..0ed7d3afd5 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,85 @@ # @backstage/plugin-config-schema +## 0.1.49 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.1.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.1.49-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.1.48 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.1.48-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + +## 0.1.48-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.1.48-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + ## 0.1.48-next.0 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index da59ec9de9..15860d4e89 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-config-schema", "description": "A Backstage plugin that lets you browse the configuration schema of your app", - "version": "0.1.48-next.0", + "version": "0.1.49", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,11 +30,9 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", - "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -51,14 +49,10 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 471675b140..7f0a7dfd21 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,118 @@ # @backstage/plugin-cost-insights +## 0.12.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/plugin-cost-insights-common@0.1.2 + +## 0.12.18 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0 + - @backstage/plugin-cost-insights-common@0.1.2 + +## 0.12.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.12.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/config@1.1.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/plugin-cost-insights-common@0.1.2 + +## 0.12.18-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/plugin-cost-insights-common@0.1.2 + +## 0.12.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-cost-insights-common@0.1.2 + +## 0.12.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-cost-insights-common@0.1.2 + +## 0.12.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-cost-insights-common@0.1.2 + +## 0.12.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-cost-insights-common@0.1.2 + ## 0.12.17-next.0 ### Patch Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 88166e729d..798853d219 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cost-insights", "description": "A Backstage plugin that helps you keep track of your cloud spend", - "version": "0.12.17-next.0", + "version": "0.12.19-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -74,9 +74,7 @@ "@types/recharts": "^1.8.14", "@types/regression": "^2.0.0", "@types/yup": "^0.29.13", - "canvas": "^2.10.2", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "canvas": "^2.10.2" }, "files": [ "dist", diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index a1e9a0d4fd..ca367deadd 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,154 @@ # @backstage/plugin-devtools-backend +## 0.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/config-loader@1.6.1 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.8 + - @backstage/plugin-permission-common@0.7.12 + +## 0.2.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config-loader@1.6.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.8 + +## 0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/config-loader@1.6.1-next.0 + +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.1-next.0 + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.7 + - @backstage/plugin-permission-common@0.7.11 + +## 0.2.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-devtools-common@0.1.7 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config-loader@1.6.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.7 + +## 0.2.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.6.0-next.0 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-devtools-common@0.1.6 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## 0.2.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.6.0-next.0 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.6 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## 0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.1.1 + - @backstage/config-loader@1.5.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-devtools-common@0.1.6 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + ## 0.2.5-next.0 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index f4976a695f..e35d8c4112 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.2.5-next.0", + "version": "0.2.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -59,7 +59,6 @@ "@types/ping": "^0.4.1", "@types/supertest": "^2.0.8", "@types/yarnpkg__lockfile": "^1.1.4", - "msw": "^1.0.0", "supertest": "^6.2.4" }, "files": [ diff --git a/plugins/devtools-common/CHANGELOG.md b/plugins/devtools-common/CHANGELOG.md index adcc675218..3bbc48a585 100644 --- a/plugins/devtools-common/CHANGELOG.md +++ b/plugins/devtools-common/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-devtools-common +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/types@1.1.1 + +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + - @backstage/types@1.1.1 + ## 0.1.6 ### Patch Changes diff --git a/plugins/devtools-common/package.json b/plugins/devtools-common/package.json index 6cf7f36cbc..a488e997a1 100644 --- a/plugins/devtools-common/package.json +++ b/plugins/devtools-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-devtools-common", "description": "Common functionalities for the devtools plugin", - "version": "0.1.6", + "version": "0.1.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/devtools/.eslintrc.js b/plugins/devtools/.eslintrc.js index e2a53a6ad2..e487f765b2 100644 --- a/plugins/devtools/.eslintrc.js +++ b/plugins/devtools/.eslintrc.js @@ -1 +1,5 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + '@backstage/no-top-level-material-ui-4-imports': 'error', + }, +}); diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index f00218080d..be753cb664 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,108 @@ # @backstage/plugin-devtools +## 0.1.9-next.0 + +### Patch Changes + +- c12a86c: Refactored code to improve accessibility by moving elements outside the `ul` tag and placing them appropriately. Also adjusted theme to offer better contrast. +- b89d8be: Added alpha support for the New Frontend System (Declarative Integration) +- 995d280: Updated imports from named to default imports to help with the Material UI v4 to v5 migration +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-devtools-common@0.1.8 + - @backstage/plugin-permission-react@0.4.19 + +## 0.1.8 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/errors@1.2.3 + - @backstage/plugin-devtools-common@0.1.8 + +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-devtools-common@0.1.7 + +## 0.1.8-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-devtools-common@0.1.7 + +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.7 + +## 0.1.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.6 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## 0.1.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.6 + - @backstage/plugin-permission-react@0.4.18-next.1 + +## 0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.6 + ## 0.1.7-next.0 ### Patch Changes diff --git a/plugins/devtools/api-report-alpha.md b/plugins/devtools/api-report-alpha.md new file mode 100644 index 0000000000..59aefd3698 --- /dev/null +++ b/plugins/devtools/api-report-alpha.md @@ -0,0 +1,19 @@ +## API Report File for "@backstage/plugin-devtools" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { RouteRef } from '@backstage/frontend-plugin-api'; + +// @alpha (undocumented) +const _default: BackstagePlugin< + { + root: RouteRef; + }, + {} +>; +export default _default; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/devtools/api-report.md b/plugins/devtools/api-report.md index 21754d7c1e..bba36a5a9a 100644 --- a/plugins/devtools/api-report.md +++ b/plugins/devtools/api-report.md @@ -10,7 +10,7 @@ import { default as default_2 } from 'react'; import { JSX as JSX_2 } from 'react'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; -import { TabProps } from '@material-ui/core'; +import { TabProps } from '@material-ui/core/Tab'; // @public (undocumented) export const ConfigContent: () => React_2.JSX.Element; diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 9f0318df40..7639df94b8 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,13 +1,26 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.7-next.0", + "version": "0.1.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" + }, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } }, "backstage": { "role": "frontend-plugin" @@ -29,13 +42,13 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { + "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-devtools-common": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", - "@backstage/theme": "workspace:^", - "@backstage/types": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.57", @@ -50,14 +63,9 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/devtools/src/alpha.ts b/plugins/devtools/src/alpha.ts new file mode 100644 index 0000000000..e80f131817 --- /dev/null +++ b/plugins/devtools/src/alpha.ts @@ -0,0 +1,18 @@ +/* + * 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 * from './alpha/index'; +export { default } from './alpha/index'; diff --git a/plugins/devtools/src/alpha/index.ts b/plugins/devtools/src/alpha/index.ts new file mode 100644 index 0000000000..2f137f09ee --- /dev/null +++ b/plugins/devtools/src/alpha/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { default } from './plugin'; diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx new file mode 100644 index 0000000000..746b90a50f --- /dev/null +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -0,0 +1,70 @@ +/* + * 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 { + createApiExtension, + createApiFactory, + createNavItemExtension, + createPageExtension, + createPlugin, + discoveryApiRef, + identityApiRef, +} from '@backstage/frontend-plugin-api'; + +import { devToolsApiRef, DevToolsClient } from '../api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; +import BuildIcon from '@material-ui/icons/Build'; +import { rootRouteRef } from '../routes'; + +/** @alpha */ +export const devToolsApi = createApiExtension({ + factory: createApiFactory({ + api: devToolsApiRef, + deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef }, + factory: ({ discoveryApi, identityApi }) => + new DevToolsClient({ discoveryApi, identityApi }), + }), +}); + +/** @alpha */ +export const devToolsPage = createPageExtension({ + defaultPath: '/devtools', + routeRef: convertLegacyRouteRef(rootRouteRef), + loader: () => + import('../components/DevToolsPage').then(m => + compatWrapper(), + ), +}); + +/** @alpha */ +export const devToolsNavItem = createNavItemExtension({ + title: 'DevTools', + routeRef: convertLegacyRouteRef(rootRouteRef), + icon: BuildIcon, +}); + +/** @alpha */ +export default createPlugin({ + id: 'devtools', + routes: { + root: convertLegacyRouteRef(rootRouteRef), + }, + extensions: [devToolsApi, devToolsPage, devToolsNavItem], +}); diff --git a/plugins/devtools/src/components/Content/ConfigContent/ConfigContent.tsx b/plugins/devtools/src/components/Content/ConfigContent/ConfigContent.tsx index 66129b5dae..4bd4bc95c6 100644 --- a/plugins/devtools/src/components/Content/ConfigContent/ConfigContent.tsx +++ b/plugins/devtools/src/components/Content/ConfigContent/ConfigContent.tsx @@ -15,16 +15,16 @@ */ import { Progress, WarningPanel } from '@backstage/core-components'; +import Box from '@material-ui/core/Box'; +import Paper from '@material-ui/core/Paper'; +import Typography from '@material-ui/core/Typography'; import { - Box, createStyles, makeStyles, - Paper, Theme, - Typography, useTheme, -} from '@material-ui/core'; -import { Alert } from '@material-ui/lab'; +} from '@material-ui/core/styles'; +import Alert from '@material-ui/lab/Alert'; import React from 'react'; import ReactJson from 'react-json-view'; import { useConfig } from '../../../hooks'; @@ -87,7 +87,7 @@ export const ConfigContent = () => { src={configInfo.config as object} name="config" enableClipboard={false} - theme={theme.palette.type === 'dark' ? 'monokai' : 'rjv-default'} + theme={theme.palette.type === 'dark' ? 'chalk' : 'rjv-default'} /> diff --git a/plugins/devtools/src/components/Content/ExternalDependenciesContent/ExternalDependenciesContent.tsx b/plugins/devtools/src/components/Content/ExternalDependenciesContent/ExternalDependenciesContent.tsx index a5dd825fe5..c6a46ea8fb 100644 --- a/plugins/devtools/src/components/Content/ExternalDependenciesContent/ExternalDependenciesContent.tsx +++ b/plugins/devtools/src/components/Content/ExternalDependenciesContent/ExternalDependenciesContent.tsx @@ -23,16 +23,12 @@ import { TableColumn, } from '@backstage/core-components'; import { ExternalDependency } from '@backstage/plugin-devtools-common'; -import { - Box, - createStyles, - Grid, - makeStyles, - Paper, - Theme, - Typography, -} from '@material-ui/core'; -import { Alert } from '@material-ui/lab'; +import Box from '@material-ui/core/Box'; +import Grid from '@material-ui/core/Grid'; +import Paper from '@material-ui/core/Paper'; +import Typography from '@material-ui/core/Typography'; +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; +import Alert from '@material-ui/lab/Alert'; import React from 'react'; import { useExternalDependencies } from '../../../hooks'; diff --git a/plugins/devtools/src/components/Content/InfoContent/BackstageLogoIcon.tsx b/plugins/devtools/src/components/Content/InfoContent/BackstageLogoIcon.tsx index 32f35600f2..8a9df77546 100644 --- a/plugins/devtools/src/components/Content/InfoContent/BackstageLogoIcon.tsx +++ b/plugins/devtools/src/components/Content/InfoContent/BackstageLogoIcon.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { SvgIcon, SvgIconProps } from '@material-ui/core'; +import SvgIcon, { SvgIconProps } from '@material-ui/core/SvgIcon'; import React from 'react'; diff --git a/plugins/devtools/src/components/Content/InfoContent/InfoContent.tsx b/plugins/devtools/src/components/Content/InfoContent/InfoContent.tsx index 0ed43d2fee..d32f8619ba 100644 --- a/plugins/devtools/src/components/Content/InfoContent/InfoContent.tsx +++ b/plugins/devtools/src/components/Content/InfoContent/InfoContent.tsx @@ -15,20 +15,17 @@ */ import { Progress } from '@backstage/core-components'; -import { - Avatar, - Box, - createStyles, - Divider, - List, - ListItem, - ListItemAvatar, - ListItemText, - makeStyles, - Paper, - Theme, -} from '@material-ui/core'; -import { Alert } from '@material-ui/lab'; +import Avatar from '@material-ui/core/Avatar'; +import Box from '@material-ui/core/Box'; +import Divider from '@material-ui/core/Divider'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemAvatar from '@material-ui/core/ListItemAvatar'; +import ListItemText from '@material-ui/core/ListItemText'; +import Paper from '@material-ui/core/Paper'; +import Button from '@material-ui/core/Button'; +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; +import Alert from '@material-ui/lab/Alert'; import React from 'react'; import { useInfo } from '../../../hooks'; import { InfoDependenciesTable } from './InfoDependenciesTable'; @@ -42,6 +39,7 @@ import { DevToolsInfo } from '@backstage/plugin-devtools-common'; const useStyles = makeStyles((theme: Theme) => createStyles({ paperStyle: { + display: 'flex', marginBottom: theme.spacing(2), }, flexContainer: { @@ -127,22 +125,17 @@ export const InfoContent = () => { secondary={about?.backstageVersion} /> - - { - copyToClipboard({ about }); - }} - className={classes.copyButton} - > - - - - - - - + + diff --git a/plugins/devtools/src/components/DevToolsLayout/DevToolsLayout.tsx b/plugins/devtools/src/components/DevToolsLayout/DevToolsLayout.tsx index ae9a337ced..bce0837ef1 100644 --- a/plugins/devtools/src/components/DevToolsLayout/DevToolsLayout.tsx +++ b/plugins/devtools/src/components/DevToolsLayout/DevToolsLayout.tsx @@ -19,7 +19,7 @@ import { attachComponentData, useElementFilter, } from '@backstage/core-plugin-api'; -import { TabProps } from '@material-ui/core'; +import { TabProps } from '@material-ui/core/Tab'; import { default as React } from 'react'; /** @public */ diff --git a/plugins/dynatrace/CHANGELOG.md b/plugins/dynatrace/CHANGELOG.md index 180444eabd..6d363c3bc3 100644 --- a/plugins/dynatrace/CHANGELOG.md +++ b/plugins/dynatrace/CHANGELOG.md @@ -1,5 +1,98 @@ # @backstage/plugin-dynatrace +## 8.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 8.0.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 8.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 8.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 8.0.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 8.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## 8.0.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 8.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 8.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 8.0.2-next.0 ### Patch Changes diff --git a/plugins/dynatrace/package.json b/plugins/dynatrace/package.json index cd09c89a1c..23ff57d6c0 100644 --- a/plugins/dynatrace/package.json +++ b/plugins/dynatrace/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-dynatrace", - "version": "8.0.2-next.0", + "version": "8.0.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,10 +32,7 @@ "@backstage/catalog-model": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "react-use": "^17.2.4" }, @@ -52,10 +49,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "express": "^4.18.1", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist", diff --git a/plugins/entity-feedback-backend/CHANGELOG.md b/plugins/entity-feedback-backend/CHANGELOG.md index 441859172c..9248c16404 100644 --- a/plugins/entity-feedback-backend/CHANGELOG.md +++ b/plugins/entity-feedback-backend/CHANGELOG.md @@ -1,5 +1,120 @@ # @backstage/plugin-entity-feedback-backend +## 0.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-entity-feedback-common@0.1.3 + ## 0.2.5-next.0 ### Patch Changes diff --git a/plugins/entity-feedback-backend/package.json b/plugins/entity-feedback-backend/package.json index e13f20fa64..013aff7146 100644 --- a/plugins/entity-feedback-backend/package.json +++ b/plugins/entity-feedback-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-entity-feedback-backend", - "version": "0.2.5-next.0", + "version": "0.2.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -47,7 +47,6 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.12", - "msw": "^1.0.0", "supertest": "^6.2.4" }, "files": [ diff --git a/plugins/entity-feedback/CHANGELOG.md b/plugins/entity-feedback/CHANGELOG.md index e890abd312..69f503b584 100644 --- a/plugins/entity-feedback/CHANGELOG.md +++ b/plugins/entity-feedback/CHANGELOG.md @@ -1,5 +1,114 @@ # @backstage/plugin-entity-feedback +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.12 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.12-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.11-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-entity-feedback-common@0.1.3 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-entity-feedback-common@0.1.3 + ## 0.2.11-next.0 ### Patch Changes diff --git a/plugins/entity-feedback/package.json b/plugins/entity-feedback/package.json index 4569a53c34..d16702c230 100644 --- a/plugins/entity-feedback/package.json +++ b/plugins/entity-feedback/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-entity-feedback", - "version": "0.2.11-next.0", + "version": "0.2.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,10 +35,8 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-entity-feedback-common": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "react-use": "^17.2.4" }, @@ -49,8 +47,6 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", - "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", diff --git a/plugins/entity-validation/CHANGELOG.md b/plugins/entity-validation/CHANGELOG.md index 3c5dd92fa1..3c7c1579cf 100644 --- a/plugins/entity-validation/CHANGELOG.md +++ b/plugins/entity-validation/CHANGELOG.md @@ -1,5 +1,126 @@ # @backstage/plugin-entity-validation +## 0.1.15-next.0 + +### Patch Changes + +- 916da47: Change default icon for unknown entities to nothing instead of the help icon. +- 1f70e46: Improves UX of the EntityValidationPage: Moves the validate button below the EntityTextArea which is actually validated, the location TextField can now be hidden to prevent confusion about what is validated and additional content can be added to the top of the validation page. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.1.14 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.1.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.1.14-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.1.13 + +### Patch Changes + +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.1.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.18 + +## 0.1.13-next.1 + +### Patch Changes + +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-catalog-common@1.0.18 + ## 0.1.13-next.0 ### Patch Changes diff --git a/plugins/entity-validation/api-report.md b/plugins/entity-validation/api-report.md index 9d69458ed0..95fdfaf42c 100644 --- a/plugins/entity-validation/api-report.md +++ b/plugins/entity-validation/api-report.md @@ -7,12 +7,15 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; +import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; // @public (undocumented) export const EntityValidationPage: (props: { defaultYaml?: string | undefined; defaultLocation?: string | undefined; + hideFileLocationField?: boolean | undefined; + contentHead?: ReactNode; }) => JSX_2.Element; // @public (undocumented) diff --git a/plugins/entity-validation/package.json b/plugins/entity-validation/package.json index 8657c2cbdc..cdf3dc88da 100644 --- a/plugins/entity-validation/package.json +++ b/plugins/entity-validation/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-entity-validation", - "version": "0.1.13-next.0", + "version": "0.1.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,14 +36,13 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@codemirror/language": "^6.0.0", "@codemirror/legacy-modes": "^6.1.0", "@codemirror/view": "^6.0.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "@react-hookz/web": "^20.0.0", + "@react-hookz/web": "^23.0.0", "@types/react": "^16.13.1 || ^17.0.0", "@uiw/react-codemirror": "^4.9.3", "lodash": "^4.17.21", @@ -57,14 +56,11 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/entity-validation/src/components/EntityValidationOutput/EntityResult.tsx b/plugins/entity-validation/src/components/EntityValidationOutput/EntityResult.tsx index 85d47fa2c2..a9989f9f70 100644 --- a/plugins/entity-validation/src/components/EntityValidationOutput/EntityResult.tsx +++ b/plugins/entity-validation/src/components/EntityValidationOutput/EntityResult.tsx @@ -27,11 +27,11 @@ import { Paper, } from '@material-ui/core'; import { humanizeEntityRef } from '@backstage/plugin-catalog-react'; -import WorkIcon from '@material-ui/icons/Work'; import ExpandLessIcon from '@material-ui/icons/ExpandLess'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { MarkdownContent } from '@backstage/core-components'; import { ValidationOutputOk } from '../../types'; +import SvgIcon from '@material-ui/core/SvgIcon'; const useStyles = makeStyles(theme => ({ validationOk: { @@ -60,9 +60,9 @@ export const EntityResult = ({ const app = useApp(); const [expanded, setExpanded] = useState(isFirstError); - const Icon = - app.getSystemIcon(`kind:${item.entity.kind.toLocaleLowerCase('en-US')}`) ?? - WorkIcon; + const Icon = app.getSystemIcon( + `kind:${item.entity.kind.toLocaleLowerCase('en-US')}`, + ) as typeof SvgIcon; const fetchErrorMessages = (response: ValidateEntityResponse) => { if (!response.valid) { @@ -75,13 +75,15 @@ export const EntityResult = ({ <> - + {Icon && ( + + )} { + const catalogApi: Partial = { + getEntities: () => + Promise.resolve({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { + name: 'Entity1', + annotations: { + 'backstage.io/view-url': 'viewurl', + 'backstage.io/edit-url': 'editurl', + }, + }, + spec: { type: 'openapi' }, + }, + ], + }), + }; + + const contentHead = ; + + it('should show loation text field', async () => { + const { getByText, getByTestId } = await renderInTestApp( + + + , + ); + const mainGrid = getByTestId('main-grid'); + + expect(mainGrid.children.length).toBe(2); + expect(getByText('File Location')).toBeInTheDocument(); + }); + + it('should not show loation text field', async () => { + const { queryByText, getByTestId } = await renderInTestApp( + + + , + ); + const mainGrid = getByTestId('main-grid'); + + expect(mainGrid.children.length).toBe(1); + expect(queryByText('File Location')).not.toBeInTheDocument(); + }); + + it('should not show content head', async () => { + const { getByTestId } = await renderInTestApp( + + + , + ); + const mainGrid = getByTestId('main-grid'); + + expect(mainGrid.children.length).toBe(2); + }); + + it('should show content head', async () => { + const { getByTestId } = await renderInTestApp( + + + , + ); + const mainGrid = getByTestId('main-grid'); + + expect(mainGrid.children.length).toBe(3); + }); +}); diff --git a/plugins/entity-validation/src/components/EntityValidationPage/EntityValidationPage.tsx b/plugins/entity-validation/src/components/EntityValidationPage/EntityValidationPage.tsx index 0f0e60366b..c3e6386a37 100644 --- a/plugins/entity-validation/src/components/EntityValidationPage/EntityValidationPage.tsx +++ b/plugins/entity-validation/src/components/EntityValidationPage/EntityValidationPage.tsx @@ -15,9 +15,9 @@ */ import React, { useState } from 'react'; -import { Content, Header, LinkButton, Page } from '@backstage/core-components'; +import { Content, Header, Page } from '@backstage/core-components'; import { EntityTextArea } from '../EntityTextArea'; -import { Grid, TextField } from '@material-ui/core'; +import { Button, Grid, TextField } from '@material-ui/core'; import { CatalogProcessorResult } from '../../types'; import { parseEntityYaml } from '../../utils'; import { EntityValidationOutput } from '../EntityValidationOutput'; @@ -40,10 +40,14 @@ spec: export const EntityValidationPage = (props: { defaultYaml?: string; defaultLocation?: string; + hideFileLocationField?: boolean; + contentHead?: React.ReactNode; }) => { const { defaultYaml = EXAMPLE_CATALOG_INFO_YAML, defaultLocation = 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', + hideFileLocationField = false, + contentHead, } = props; const [catalogYaml, setCatalogYaml] = useState(defaultYaml); @@ -67,8 +71,16 @@ export const EntityValidationPage = (props: { subtitle="Tool to validate catalog-info.yaml files" /> - - + + {contentHead} + + {!hideFileLocationField && ( setLocationUrl(e.target.value)} /> - - - - - - Validate - + )} + + + + + + setCatalogYaml(value)} + catalogYaml={catalogYaml} + /> + + + + - - - - - setCatalogYaml(value)} - catalogYaml={catalogYaml} - /> - - - - - + + + + + diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index c4f5ad2d51..32f5f8f1d7 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,119 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.19-next.0 + +## 0.2.12 + +### Patch Changes + +- 7b8e551: Fix errors when deleting SQS messages: + + - If zero messages were received, skip deletion to avoid `EmptyBatchRequest` error from the SQS client. + - If zero failures were returned from the SQS client during deletion, skip error logging. + +- d5ddc4e: Add documentation on how to install the plugins with the new backend system. +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/backend-tasks@0.5.14 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.18 + +## 0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.18-next.0 + +## 0.2.11 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.17 + +## 0.2.11-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.3 + +## 0.2.11-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.1 + ## 0.2.11-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/README.md b/plugins/events-backend-module-aws-sqs/README.md index cb3a5045c0..397de35c10 100644 --- a/plugins/events-backend-module-aws-sqs/README.md +++ b/plugins/events-backend-module-aws-sqs/README.md @@ -40,3 +40,7 @@ events: # From your Backstage root directory yarn add --cwd packages/backend @backstage/plugin-events-backend-module-aws-sqs ``` + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-events-backend-module-aws-sqs/alpha')); +``` diff --git a/plugins/events-backend-module-aws-sqs/alpha-api-report.md b/plugins/events-backend-module-aws-sqs/api-report-alpha.md similarity index 100% rename from plugins/events-backend-module-aws-sqs/alpha-api-report.md rename to plugins/events-backend-module-aws-sqs/api-report-alpha.md diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index 74a36221e3..2d27225196 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-aws-sqs", - "version": "0.2.11-next.0", + "version": "0.2.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts index 4f4e6ea55d..a2f6a00fe7 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts @@ -111,7 +111,7 @@ export class AwsSqsConsumingEventPublisher implements EventPublisher { } private async deleteMessages(messages?: Message[]): Promise { - if (!messages) { + if (!messages || messages.length === 0) { return; } @@ -129,7 +129,7 @@ export class AwsSqsConsumingEventPublisher implements EventPublisher { const result = await this.sqs.send( new DeleteMessageBatchCommand(deleteParams), ); - if (result.Failed) { + if (result.Failed && result.Failed.length > 0) { this.logger.error( `Failed to delete ${result.Failed!.length} of ${ messages.length diff --git a/plugins/events-backend-module-aws-sqs/src/service/eventsModuleAwsSqsConsumingEventPublisher.ts b/plugins/events-backend-module-aws-sqs/src/service/eventsModuleAwsSqsConsumingEventPublisher.ts index 9045047094..eabab94708 100644 --- a/plugins/events-backend-module-aws-sqs/src/service/eventsModuleAwsSqsConsumingEventPublisher.ts +++ b/plugins/events-backend-module-aws-sqs/src/service/eventsModuleAwsSqsConsumingEventPublisher.ts @@ -29,7 +29,7 @@ import { AwsSqsConsumingEventPublisher } from '../publisher/AwsSqsConsumingEvent */ export const eventsModuleAwsSqsConsumingEventPublisher = createBackendModule({ pluginId: 'events', - moduleId: 'awsSqsConsumingEventPublisher', + moduleId: 'aws-sqs-consuming-event-publisher', register(env) { env.registerInit({ deps: { diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index 6ff642a8e3..ff61ec0c12 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,82 @@ # @backstage/plugin-events-backend-module-azure +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/plugin-events-node@0.2.19-next.0 + +## 0.1.19 + +### Patch Changes + +- af76a95: Add default exports for the new backend system and documentation. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-events-node@0.2.18 + +## 0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## 0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + +## 0.1.18 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + - @backstage/plugin-events-node@0.2.17 + +## 0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + +## 0.1.18-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/plugin-events-node@0.2.17-next.2 + +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + ## 0.1.18-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-azure/README.md b/plugins/events-backend-module-azure/README.md index 26bffbc45d..457fd81fbc 100644 --- a/plugins/events-backend-module-azure/README.md +++ b/plugins/events-backend-module-azure/README.md @@ -31,6 +31,14 @@ Install this module: yarn add --cwd packages/backend @backstage/plugin-events-backend-module-azure ``` +### Add to backend + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-events-backend-module-azure/alpha')); +``` + +### Add to backend (old) + Add the event router to the `EventsBackend` instance in `packages/backend/src/plugins/events.ts`: ```diff diff --git a/plugins/events-backend-module-azure/alpha-api-report.md b/plugins/events-backend-module-azure/api-report-alpha.md similarity index 65% rename from plugins/events-backend-module-azure/alpha-api-report.md rename to plugins/events-backend-module-azure/api-report-alpha.md index 1bd568d1df..d07f8852f3 100644 --- a/plugins/events-backend-module-azure/alpha-api-report.md +++ b/plugins/events-backend-module-azure/api-report-alpha.md @@ -6,7 +6,9 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -export const eventsModuleAzureDevOpsEventRouter: () => BackendFeature; +const eventsModuleAzureDevOpsEventRouter: () => BackendFeature; +export default eventsModuleAzureDevOpsEventRouter; +export { eventsModuleAzureDevOpsEventRouter }; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index c4df3cc562..94b71afc70 100644 --- a/plugins/events-backend-module-azure/package.json +++ b/plugins/events-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-azure", - "version": "0.1.18-next.0", + "version": "0.1.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -48,8 +48,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/plugin-events-backend-test-utils": "workspace:^", - "supertest": "^6.1.3" + "@backstage/plugin-events-backend-test-utils": "workspace:^" }, "files": [ "dist" diff --git a/plugins/events-backend-module-azure/src/alpha.ts b/plugins/events-backend-module-azure/src/alpha.ts index 07334fcbda..4de53df42a 100644 --- a/plugins/events-backend-module-azure/src/alpha.ts +++ b/plugins/events-backend-module-azure/src/alpha.ts @@ -15,3 +15,4 @@ */ export { eventsModuleAzureDevOpsEventRouter } from './service/eventsModuleAzureDevOpsEventRouter'; +export { eventsModuleAzureDevOpsEventRouter as default } from './service/eventsModuleAzureDevOpsEventRouter'; diff --git a/plugins/events-backend-module-azure/src/service/eventsModuleAzureDevOpsEventRouter.ts b/plugins/events-backend-module-azure/src/service/eventsModuleAzureDevOpsEventRouter.ts index 36f3acada0..50bc384502 100644 --- a/plugins/events-backend-module-azure/src/service/eventsModuleAzureDevOpsEventRouter.ts +++ b/plugins/events-backend-module-azure/src/service/eventsModuleAzureDevOpsEventRouter.ts @@ -27,7 +27,7 @@ import { AzureDevOpsEventRouter } from '../router/AzureDevOpsEventRouter'; */ export const eventsModuleAzureDevOpsEventRouter = createBackendModule({ pluginId: 'events', - moduleId: 'azureDevOpsEventRouter', + moduleId: 'azure-dev-ops-event-router', register(env) { env.registerInit({ deps: { diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index 0a74f17134..5fb99e39d2 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,82 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/plugin-events-node@0.2.19-next.0 + +## 0.1.19 + +### Patch Changes + +- af76a95: Add default exports for the new backend system and documentation. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-events-node@0.2.18 + +## 0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## 0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + +## 0.1.18 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + - @backstage/plugin-events-node@0.2.17 + +## 0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + +## 0.1.18-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/plugin-events-node@0.2.17-next.2 + +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + ## 0.1.18-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/README.md b/plugins/events-backend-module-bitbucket-cloud/README.md index 60fdf52af9..d70a70c87e 100644 --- a/plugins/events-backend-module-bitbucket-cloud/README.md +++ b/plugins/events-backend-module-bitbucket-cloud/README.md @@ -31,6 +31,16 @@ Install this module: yarn add --cwd packages/backend @backstage/plugin-events-backend-module-bitbucket-cloud ``` +### Add to backend + +```ts title="packages/backend/src/index.ts" +backend.add( + import('@backstage/plugin-events-backend-module-bitbucket-cloud/alpha'), +); +``` + +### Add to backend (old) + Add the event router to the `EventsBackend` instance in `packages/backend/src/plugins/events.ts`: ```diff diff --git a/plugins/events-backend-module-bitbucket-cloud/alpha-api-report.md b/plugins/events-backend-module-bitbucket-cloud/api-report-alpha.md similarity index 65% rename from plugins/events-backend-module-bitbucket-cloud/alpha-api-report.md rename to plugins/events-backend-module-bitbucket-cloud/api-report-alpha.md index 5e63ae36c8..fa7cdfa9f6 100644 --- a/plugins/events-backend-module-bitbucket-cloud/alpha-api-report.md +++ b/plugins/events-backend-module-bitbucket-cloud/api-report-alpha.md @@ -6,7 +6,9 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -export const eventsModuleBitbucketCloudEventRouter: () => BackendFeature; +const eventsModuleBitbucketCloudEventRouter: () => BackendFeature; +export default eventsModuleBitbucketCloudEventRouter; +export { eventsModuleBitbucketCloudEventRouter }; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 68783b5478..2ae9b32ac9 100644 --- a/plugins/events-backend-module-bitbucket-cloud/package.json +++ b/plugins/events-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-bitbucket-cloud", - "version": "0.1.18-next.0", + "version": "0.1.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -48,8 +48,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/plugin-events-backend-test-utils": "workspace:^", - "supertest": "^6.1.3" + "@backstage/plugin-events-backend-test-utils": "workspace:^" }, "files": [ "dist" diff --git a/plugins/events-backend-module-bitbucket-cloud/src/alpha.ts b/plugins/events-backend-module-bitbucket-cloud/src/alpha.ts index 75cda4327d..1f0e09a653 100644 --- a/plugins/events-backend-module-bitbucket-cloud/src/alpha.ts +++ b/plugins/events-backend-module-bitbucket-cloud/src/alpha.ts @@ -15,3 +15,4 @@ */ export { eventsModuleBitbucketCloudEventRouter } from './service/eventsModuleBitbucketCloudEventRouter'; +export { eventsModuleBitbucketCloudEventRouter as default } from './service/eventsModuleBitbucketCloudEventRouter'; diff --git a/plugins/events-backend-module-bitbucket-cloud/src/service/eventsModuleBitbucketCloudEventRouter.ts b/plugins/events-backend-module-bitbucket-cloud/src/service/eventsModuleBitbucketCloudEventRouter.ts index 60a8cc31c8..841a463001 100644 --- a/plugins/events-backend-module-bitbucket-cloud/src/service/eventsModuleBitbucketCloudEventRouter.ts +++ b/plugins/events-backend-module-bitbucket-cloud/src/service/eventsModuleBitbucketCloudEventRouter.ts @@ -27,7 +27,7 @@ import { BitbucketCloudEventRouter } from '../router/BitbucketCloudEventRouter'; */ export const eventsModuleBitbucketCloudEventRouter = createBackendModule({ pluginId: 'events', - moduleId: 'bitbucketCloudEventRouter', + moduleId: 'bitbucket-cloud-event-router', register(env) { env.registerInit({ deps: { diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index 300f445915..b641a9a8f9 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,82 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/plugin-events-node@0.2.19-next.0 + +## 0.1.19 + +### Patch Changes + +- af76a95: Add default exports for the new backend system and documentation. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-events-node@0.2.18 + +## 0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## 0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-events-node@0.2.18-next.0 + +## 0.1.18 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + - @backstage/plugin-events-node@0.2.17 + +## 0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-events-node@0.2.17-next.3 + +## 0.1.18-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/plugin-events-node@0.2.17-next.2 + +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-events-node@0.2.17-next.1 + ## 0.1.18-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/README.md b/plugins/events-backend-module-gerrit/README.md index 1808ef46ba..34b2d154be 100644 --- a/plugins/events-backend-module-gerrit/README.md +++ b/plugins/events-backend-module-gerrit/README.md @@ -30,6 +30,14 @@ Install this module: yarn add --cwd packages/backend @backstage/plugin-events-backend-module-gerrit ``` +### Add to backend + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-events-backend-module-gerrit/alpha')); +``` + +### Add to backend (old) + Add the event router to the `EventsBackend` instance in `packages/backend/src/plugins/events.ts`: ```diff diff --git a/plugins/events-backend-module-gerrit/alpha-api-report.md b/plugins/events-backend-module-gerrit/api-report-alpha.md similarity index 68% rename from plugins/events-backend-module-gerrit/alpha-api-report.md rename to plugins/events-backend-module-gerrit/api-report-alpha.md index d1be54730f..66dea72747 100644 --- a/plugins/events-backend-module-gerrit/alpha-api-report.md +++ b/plugins/events-backend-module-gerrit/api-report-alpha.md @@ -6,7 +6,9 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -export const eventsModuleGerritEventRouter: () => BackendFeature; +const eventsModuleGerritEventRouter: () => BackendFeature; +export default eventsModuleGerritEventRouter; +export { eventsModuleGerritEventRouter }; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index 882aa89f5e..941570b43b 100644 --- a/plugins/events-backend-module-gerrit/package.json +++ b/plugins/events-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gerrit", - "version": "0.1.18-next.0", + "version": "0.1.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -48,8 +48,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/plugin-events-backend-test-utils": "workspace:^", - "supertest": "^6.1.3" + "@backstage/plugin-events-backend-test-utils": "workspace:^" }, "files": [ "dist" diff --git a/plugins/events-backend-module-gerrit/src/alpha.ts b/plugins/events-backend-module-gerrit/src/alpha.ts index 788331b998..9c1eea0b75 100644 --- a/plugins/events-backend-module-gerrit/src/alpha.ts +++ b/plugins/events-backend-module-gerrit/src/alpha.ts @@ -15,3 +15,4 @@ */ export { eventsModuleGerritEventRouter } from './service/eventsModuleGerritEventRouter'; +export { eventsModuleGerritEventRouter as default } from './service/eventsModuleGerritEventRouter'; diff --git a/plugins/events-backend-module-gerrit/src/service/eventsModuleGerritEventRouter.ts b/plugins/events-backend-module-gerrit/src/service/eventsModuleGerritEventRouter.ts index 8d490ea0b2..780ff7f878 100644 --- a/plugins/events-backend-module-gerrit/src/service/eventsModuleGerritEventRouter.ts +++ b/plugins/events-backend-module-gerrit/src/service/eventsModuleGerritEventRouter.ts @@ -27,7 +27,7 @@ import { GerritEventRouter } from '../router/GerritEventRouter'; */ export const eventsModuleGerritEventRouter = createBackendModule({ pluginId: 'events', - moduleId: 'gerritEventRouter', + moduleId: 'gerrit-event-router', register(env) { env.registerInit({ deps: { diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index 0d68ab29f1..7fd8bd9bc9 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,89 @@ # @backstage/plugin-events-backend-module-github +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.19-next.0 + +## 0.1.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.18 + +## 0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## 0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.18-next.0 + +## 0.1.18 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17 + +## 0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.3 + +## 0.1.18-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.2 + +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.1 + ## 0.1.18-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-github/alpha-api-report.md b/plugins/events-backend-module-github/api-report-alpha.md similarity index 100% rename from plugins/events-backend-module-github/alpha-api-report.md rename to plugins/events-backend-module-github/api-report-alpha.md diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index acc0a7fcf9..4c56579e6f 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-github", - "version": "0.1.18-next.0", + "version": "0.1.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -50,8 +50,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/plugin-events-backend-test-utils": "workspace:^", - "supertest": "^6.1.3" + "@backstage/plugin-events-backend-test-utils": "workspace:^" }, "files": [ "config.d.ts", diff --git a/plugins/events-backend-module-github/src/service/eventsModuleGithubEventRouter.ts b/plugins/events-backend-module-github/src/service/eventsModuleGithubEventRouter.ts index 17485098c2..093307dfaf 100644 --- a/plugins/events-backend-module-github/src/service/eventsModuleGithubEventRouter.ts +++ b/plugins/events-backend-module-github/src/service/eventsModuleGithubEventRouter.ts @@ -27,7 +27,7 @@ import { GithubEventRouter } from '../router/GithubEventRouter'; */ export const eventsModuleGithubEventRouter = createBackendModule({ pluginId: 'events', - moduleId: 'githubEventRouter', + moduleId: 'github-event-router', register(env) { env.registerInit({ deps: { diff --git a/plugins/events-backend-module-github/src/service/eventsModuleGithubWebhook.ts b/plugins/events-backend-module-github/src/service/eventsModuleGithubWebhook.ts index b4bd666036..647b21c364 100644 --- a/plugins/events-backend-module-github/src/service/eventsModuleGithubWebhook.ts +++ b/plugins/events-backend-module-github/src/service/eventsModuleGithubWebhook.ts @@ -30,7 +30,7 @@ import { createGithubSignatureValidator } from '../http/createGithubSignatureVal */ export const eventsModuleGithubWebhook = createBackendModule({ pluginId: 'events', - moduleId: 'githubWebhook', + moduleId: 'github-webhook', register(env) { env.registerInit({ deps: { diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md index ce34e6fe75..1b6f9f2cef 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,89 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.19-next.0 + +## 0.1.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.18 + +## 0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## 0.1.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.18-next.0 + +## 0.1.18 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17 + +## 0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.3 + +## 0.1.18-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.2 + +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.1 + ## 0.1.18-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/alpha-api-report.md b/plugins/events-backend-module-gitlab/api-report-alpha.md similarity index 100% rename from plugins/events-backend-module-gitlab/alpha-api-report.md rename to plugins/events-backend-module-gitlab/api-report-alpha.md diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 794e6cba27..ca5fd57c59 100644 --- a/plugins/events-backend-module-gitlab/package.json +++ b/plugins/events-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gitlab", - "version": "0.1.18-next.0", + "version": "0.1.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -49,8 +49,7 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/plugin-events-backend-test-utils": "workspace:^", - "supertest": "^6.1.3" + "@backstage/plugin-events-backend-test-utils": "workspace:^" }, "files": [ "config.d.ts", diff --git a/plugins/events-backend-module-gitlab/src/service/eventsModuleGitlabEventRouter.ts b/plugins/events-backend-module-gitlab/src/service/eventsModuleGitlabEventRouter.ts index 3425601529..fc44e95057 100644 --- a/plugins/events-backend-module-gitlab/src/service/eventsModuleGitlabEventRouter.ts +++ b/plugins/events-backend-module-gitlab/src/service/eventsModuleGitlabEventRouter.ts @@ -27,7 +27,7 @@ import { GitlabEventRouter } from '../router/GitlabEventRouter'; */ export const eventsModuleGitlabEventRouter = createBackendModule({ pluginId: 'events', - moduleId: 'gitlabEventRouter', + moduleId: 'gitlab-event-router', register(env) { env.registerInit({ deps: { diff --git a/plugins/events-backend-module-gitlab/src/service/eventsModuleGitlabWebhook.ts b/plugins/events-backend-module-gitlab/src/service/eventsModuleGitlabWebhook.ts index bd596b4baf..77bca7236b 100644 --- a/plugins/events-backend-module-gitlab/src/service/eventsModuleGitlabWebhook.ts +++ b/plugins/events-backend-module-gitlab/src/service/eventsModuleGitlabWebhook.ts @@ -32,7 +32,7 @@ import { createGitlabTokenValidator } from '../http/createGitlabTokenValidator'; */ export const eventsModuleGitlabWebhook = createBackendModule({ pluginId: 'events', - moduleId: 'gitlabWebhook', + moduleId: 'gitlab-webhook', register(env) { env.registerInit({ deps: { diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index 505bdcd256..26fe7dc08b 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.19-next.0 + +## 0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.18 + +## 0.1.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.18-next.2 + +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.18-next.1 + +## 0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.18-next.0 + +## 0.1.18 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.17 + +## 0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.17-next.3 + +## 0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.17-next.2 + +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.2.17-next.1 + ## 0.1.18-next.0 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index e82047e483..9a570e7863 100644 --- a/plugins/events-backend-test-utils/package.json +++ b/plugins/events-backend-test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-events-backend-test-utils", "description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node", - "version": "0.1.18-next.0", + "version": "0.1.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index 2c0c2cbf70..db5c82102d 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,98 @@ # @backstage/plugin-events-backend +## 0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.19-next.0 + +## 0.2.18 + +### Patch Changes + +- 92ea615: Update `README.md` +- d5ddc4e: Add documentation on how to install the plugins with the new backend system. +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.18 + +## 0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-events-node@0.2.18-next.2 + +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-events-node@0.2.18-next.1 + +## 0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.18-next.0 + +## 0.2.17 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17 + +## 0.2.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.3 + +## 0.2.17-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.2 + +## 0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-events-node@0.2.17-next.1 + ## 0.2.17-next.0 ### Patch Changes diff --git a/plugins/events-backend/README.md b/plugins/events-backend/README.md index 969e1bbcb2..2fd980f003 100644 --- a/plugins/events-backend/README.md +++ b/plugins/events-backend/README.md @@ -24,7 +24,15 @@ to the used event broker. yarn add --cwd packages/backend @backstage/plugin-events-backend @backstage/plugin-events-node ``` -### Event Broker +### Add to backend + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-events-backend/alpha')); +``` + +### Add to backend (old) + +#### Event Broker First you will need to add and implementation of the `EventBroker` interface to the backend plugin environment. This will allow event broker instance any backend plugins to publish and subscribe to events in order to communicate @@ -34,6 +42,7 @@ Add the following to `makeCreateEnv` ```diff // packages/backend/src/index.ts ++ import { DefaultEventBroker } from '@backstage/plugin-events-backend'; + const eventBroker = new DefaultEventBroker(root.child({ type: 'plugin' })); ``` @@ -41,10 +50,11 @@ Then update plugin environment to include the event broker. ```diff // packages/backend/src/types.ts ++ import { EventBroker } from '@backstage/plugin-events-node'; + eventBroker: EventBroker; ``` -### Publishing and Subscribing to events with the broker +#### Publishing and Subscribing to events with the broker Backend plugins are passed the event broker in the plugin environment at startup of the application. The plugin can make use of this to communicate between parts of the application. @@ -80,27 +90,27 @@ export default async function createPlugin( } ``` -### Implementing an `EventSubscriber` class +#### Implementing an `EventSubscriber` class More complex solutions might need the creation of a class that implements the `EventSubscriber` interface. e.g. ```typescript jsx -import { EventSubscriber } from "./EventSubscriber"; +import { EventSubscriber } from './EventSubscriber'; class ExampleSubscriber implements EventSubscriber { - ... + // ... supportsEventTopics() { - return ['publish.example'] + return ['publish.example']; } async onEvent(params: EventParams) { - env.logger.info(`receieved ${params.topic} event`) + env.logger.info(`receieved ${params.topic} event`); } } ``` -### Events Backend +#### Events Backend The events backend plugin provides a router to handler http events and publish the http requests onto the event broker. @@ -201,7 +211,7 @@ import { eventsExtensionPoint } from '@backstage/plugin-events-node'; export const yourModuleEventsModule = createBackendModule({ pluginId: 'events', - moduleId: 'yourModule', + moduleId: 'your-module', register(env) { // [...] env.registerInit({ @@ -252,7 +262,7 @@ import { eventsExtensionPoint } from '@backstage/plugin-events-node'; export const eventsModuleYourFeature = createBackendModule({ pluginId: 'events', - moduleId: 'yourFeature', + moduleId: 'your-feature', register(env) { // [...] env.registerInit({ diff --git a/plugins/events-backend/alpha-api-report.md b/plugins/events-backend/api-report-alpha.md similarity index 100% rename from plugins/events-backend/alpha-api-report.md rename to plugins/events-backend/api-report-alpha.md diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 1288abf0a3..6980590c2f 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.2.17-next.0", + "version": "0.2.19-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 787e3daa7b..febba33d77 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/plugin-events-node +## 0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.10-next.0 + +## 0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + +## 0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8 + +## 0.2.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + +## 0.2.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + +## 0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.1 + ## 0.2.17-next.0 ### Patch Changes diff --git a/plugins/events-node/alpha-api-report.md b/plugins/events-node/api-report-alpha.md similarity index 100% rename from plugins/events-node/alpha-api-report.md rename to plugins/events-node/api-report-alpha.md diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 531138ff69..0c4b63f5e2 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-events-node", "description": "The plugin-events-node module for @backstage/plugin-events-backend", - "version": "0.2.17-next.0", + "version": "0.2.19-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index 1cc13d252a..09e9b21b55 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,98 @@ # @internal/plugin-todo-list-backend +## 1.0.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/errors@1.2.3 + +## 1.0.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/errors@1.2.3 + +## 1.0.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 1.0.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + +## 1.0.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + +## 1.0.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 1.0.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + +## 1.0.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 1.0.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + ## 1.0.20-next.0 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index a04643c4c2..d3b38bbaf2 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.20-next.0", + "version": "1.0.22-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@types/express": "^4.17.6", @@ -45,7 +44,6 @@ "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", - "msw": "^1.0.0", "supertest": "^6.1.6" }, "files": [ diff --git a/plugins/example-todo-list-common/CHANGELOG.md b/plugins/example-todo-list-common/CHANGELOG.md index c4d964b881..98d6bafa7f 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,19 @@ # @internal/plugin-todo-list-common +## 1.0.17 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + +## 1.0.16 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + ## 1.0.15 ### Patch Changes diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 33ec099414..1d984812f9 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-common", - "version": "1.0.15", + "version": "1.0.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,7 @@ "@backstage/plugin-permission-common": "workspace:^" }, "devDependencies": { - "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", - "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@backstage/cli": "workspace:^" }, "files": [ "dist" diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index b3a8a8f93d..8dfacdd105 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,65 @@ # @internal/plugin-todo-list +## 1.0.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 1.0.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## 1.0.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## 1.0.20 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## 1.0.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## 1.0.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 1.0.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + ## 1.0.20-next.0 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index d08efb9d22..63634879df 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.20-next.0", + "version": "1.0.21", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,7 @@ "dependencies": { "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "react-use": "^17.2.4" }, @@ -45,15 +43,10 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", - "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "@types/react": "^16.13.1 || ^17.0.0", - "msw": "^1.0.0" + "@types/react": "^16.13.1 || ^17.0.0" }, "files": [ "dist" diff --git a/plugins/explore-backend/CHANGELOG.md b/plugins/explore-backend/CHANGELOG.md index 9e7c38e8f8..7d78e292e3 100644 --- a/plugins/explore-backend/CHANGELOG.md +++ b/plugins/explore-backend/CHANGELOG.md @@ -1,5 +1,119 @@ # @backstage/plugin-explore-backend +## 0.0.20-next.0 + +### Patch Changes + +- fd3d51c: Add support for the new backend system. + + A new backend plugin for the explore backend + was added and exported as `default`. + + You can use it with the new backend system like + + ```ts title="packages/backend/src/index.ts" + backend.add(import('@backstage/plugin-explore-backend')); + ``` + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.14-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + +## 0.0.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-search-backend-module-explore@0.1.13 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + +## 0.0.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.2 + +## 0.0.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + +## 0.0.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.13-next.0 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + +## 0.0.18 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-search-backend-module-explore@0.1.12 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.0.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.3 + - @backstage/plugin-search-common@1.2.8 + +## 0.0.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.2 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.8 + +## 0.0.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-backend-module-explore@0.1.12-next.1 + - @backstage/plugin-search-common@1.2.8 + ## 0.0.18-next.0 ### Patch Changes diff --git a/plugins/explore-backend/README.md b/plugins/explore-backend/README.md index 543e8e2fa2..aae3ff94b8 100644 --- a/plugins/explore-backend/README.md +++ b/plugins/explore-backend/README.md @@ -9,6 +9,36 @@ for these tools. ### Adding the plugin to your `packages/backend` +Install dependencies + +```bash +# From your Backstage root directory +yarn add --cwd packages/backend @backstage/plugin-explore-backend +``` + +Add feature + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-explore-backend')); +``` + +Config: + +```yaml +explore: + tools: + - title: New Relic + description: new relic plugin + url: /newrelic + image: https://i.imgur.com/L37ikrX.jpg + tags: + - newrelic + - proxy + - nerdGraph +``` + +### Adding the plugin to your `packages/backend` (old) + #### Tools as Config Install dependencies diff --git a/plugins/explore-backend/api-report.md b/plugins/explore-backend/api-report.md index b652f5515b..05a057fa7b 100644 --- a/plugins/explore-backend/api-report.md +++ b/plugins/explore-backend/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { ExploreTool } from '@backstage/plugin-explore-common'; import express from 'express'; @@ -16,6 +17,10 @@ import type { ToolDocumentCollatorFactoryOptions as ToolDocumentCollatorFactoryO // @public (undocumented) export function createRouter(options: RouterOptions): Promise; +// @public +const explorePlugin: () => BackendFeature; +export default explorePlugin; + // @public (undocumented) export interface ExploreToolProvider { getTools(request: GetExploreToolsRequest): Promise; diff --git a/plugins/explore-backend/package.json b/plugins/explore-backend/package.json index ac94ef4c05..6589a40e29 100644 --- a/plugins/explore-backend/package.json +++ b/plugins/explore-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore-backend", - "version": "0.0.18-next.0", + "version": "0.0.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,10 +29,10 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-explore-common": "workspace:^", "@backstage/plugin-search-backend-module-explore": "workspace:^", - "@backstage/plugin-search-common": "workspace:^", "@backstage/types": "workspace:^", "@types/express": "*", "express": "^4.18.1", @@ -43,9 +43,9 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.8", - "msw": "^1.0.0", "supertest": "^6.2.4" }, "files": [ diff --git a/plugins/explore-backend/src/index.ts b/plugins/explore-backend/src/index.ts index 15659d6ab6..ae3ffd182b 100644 --- a/plugins/explore-backend/src/index.ts +++ b/plugins/explore-backend/src/index.ts @@ -20,6 +20,7 @@ * @packageDocumentation */ +export { explorePlugin as default } from './plugin'; export * from './service'; export * from './tools'; diff --git a/plugins/explore-backend/src/plugin.test.ts b/plugins/explore-backend/src/plugin.test.ts new file mode 100644 index 0000000000..391c00ba88 --- /dev/null +++ b/plugins/explore-backend/src/plugin.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2024 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 { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { ExploreTool } from '@backstage/plugin-explore-common'; +import { explorePlugin } from './plugin'; + +describe('explorePlugin', () => { + it('should register explore plugin and its router', async () => { + const tool: ExploreTool = { + title: 'Tool Title', + image: 'https://example.com/image.png', + url: 'https://example.com', + lifecycle: 'production', + tags: ['tag1', 'tag2'], + }; + + const httpRouterMock = mockServices.httpRouter.mock(); + + await startTestBackend({ + extensionPoints: [], + features: [ + explorePlugin(), + httpRouterMock.factory, + mockServices.rootConfig.factory({ + data: { + explore: { + tools: [tool], + }, + }, + }), + ], + }); + + expect(httpRouterMock.use).toHaveBeenCalledTimes(1); + }); +}); diff --git a/plugins/explore-backend/src/plugin.ts b/plugins/explore-backend/src/plugin.ts new file mode 100644 index 0000000000..d81493c85a --- /dev/null +++ b/plugins/explore-backend/src/plugin.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2024 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 { loggerToWinstonLogger } from '@backstage/backend-common'; +import { + coreServices, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; +import { createRouter } from './service'; +import { StaticExploreToolProvider } from './tools'; + +/** + * The explore backend plugin. + * + * @public + */ +export const explorePlugin = createBackendPlugin({ + pluginId: 'explore', + register(env) { + env.registerInit({ + deps: { + config: coreServices.rootConfig, + httpRouter: coreServices.httpRouter, + logger: coreServices.logger, + }, + async init({ config, httpRouter, logger }) { + httpRouter.use( + await createRouter({ + logger: loggerToWinstonLogger(logger), + toolProvider: StaticExploreToolProvider.fromConfig(config), + }), + ); + }, + }); + }, +}); diff --git a/plugins/explore-react/CHANGELOG.md b/plugins/explore-react/CHANGELOG.md index 59a6c265d0..d9668070c2 100644 --- a/plugins/explore-react/CHANGELOG.md +++ b/plugins/explore-react/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-explore-react +## 0.0.35 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-explore-common@0.0.2 + +## 0.0.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-explore-common@0.0.2 + +## 0.0.35-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-explore-common@0.0.2 + +## 0.0.34 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-explore-common@0.0.2 + +## 0.0.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-explore-common@0.0.2 + ## 0.0.34-next.0 ### Patch Changes diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index 65e554db77..a3128511ac 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore-react", "description": "A frontend library for Backstage plugins that want to interact with the explore plugin", - "version": "0.0.34-next.0", + "version": "0.0.35", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -43,15 +43,10 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "@types/react": "^16.13.1 || ^17.0.0", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@types/react": "^16.13.1 || ^17.0.0" }, "files": [ "dist" diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index 091216334f..643d1674d0 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,152 @@ # @backstage/plugin-explore +## 0.4.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-explore-react@0.0.35 + - @backstage/plugin-search-common@1.2.10 + +## 0.4.15 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-explore-react@0.0.35 + - @backstage/plugin-search-react@1.7.5 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.10 + +## 0.4.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + +## 0.4.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-explore-react@0.0.35-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.4.15-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-explore-react@0.0.35-next.0 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.4.14 + +### Patch Changes + +- aac659e: Added option to set `Direction` for the graph in the `GroupsDiagram` +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-react@1.7.4 + - @backstage/plugin-explore-react@0.0.34 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.9 + +## 0.4.14-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-explore-react@0.0.34-next.1 + - @backstage/plugin-search-common@1.2.8 + - @backstage/plugin-search-react@1.7.4-next.3 + +## 0.4.14-next.2 + +### Patch Changes + +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-explore-react@0.0.34-next.1 + - @backstage/plugin-search-common@1.2.8 + +## 0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/plugin-explore-react@0.0.34-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-explore-common@0.0.2 + - @backstage/plugin-search-common@1.2.8 + ## 0.4.14-next.0 ### Patch Changes diff --git a/plugins/explore/alpha-api-report.md b/plugins/explore/api-report-alpha.md similarity index 76% rename from plugins/explore/alpha-api-report.md rename to plugins/explore/api-report-alpha.md index 8670338f3e..7956cb8546 100644 --- a/plugins/explore/alpha-api-report.md +++ b/plugins/explore/api-report-alpha.md @@ -4,14 +4,14 @@ ```ts import { BackstagePlugin } from '@backstage/frontend-plugin-api'; -import { Extension } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) const _default: BackstagePlugin<{}, {}>; export default _default; // @alpha (undocumented) -export const ExploreSearchResultListItemExtension: Extension<{ +export const exploreSearchResultListItem: ExtensionDefinition<{ noTrack?: boolean | undefined; }>; diff --git a/plugins/explore/api-report.md b/plugins/explore/api-report.md index d32a133685..532b853671 100644 --- a/plugins/explore/api-report.md +++ b/plugins/explore/api-report.md @@ -8,6 +8,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { default as default_2 } from 'react'; +import { DependencyGraphTypes } from '@backstage/core-components'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { DomainEntity } from '@backstage/catalog-model'; import { ExploreToolsConfig } from '@backstage/plugin-explore-react'; @@ -110,6 +111,7 @@ export const exploreRouteRef: RouteRef; // @public (undocumented) export const GroupsExplorerContent: (props: { title?: string | undefined; + direction?: DependencyGraphTypes.Direction | undefined; }) => JSX_2.Element; // @public (undocumented) diff --git a/plugins/explore/package.json b/plugins/explore/package.json index b1169ae515..9293c39b10 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore", "description": "A Backstage plugin for building an exploration page of your software ecosystem", - "version": "0.4.14-next.0", + "version": "0.4.16-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -56,10 +56,8 @@ "@backstage/plugin-explore-react": "workspace:^", "@backstage/plugin-search-common": "workspace:^", "@backstage/plugin-search-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "classnames": "^2.2.6", "pluralize": "^8.0.0", @@ -79,8 +77,6 @@ "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "cross-fetch": "^4.0.0", "msw": "^1.0.0" }, "files": [ diff --git a/plugins/explore/src/alpha.tsx b/plugins/explore/src/alpha.tsx index 1825af5ddb..3ee25f1bdb 100644 --- a/plugins/explore/src/alpha.tsx +++ b/plugins/explore/src/alpha.tsx @@ -18,18 +18,16 @@ import { createPlugin } from '@backstage/frontend-plugin-api'; import { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha'; /** @alpha */ -export const ExploreSearchResultListItemExtension = - createSearchResultListItemExtension({ - id: 'explore', - predicate: result => result.type === 'tools', - component: () => - import('./components/ToolSearchResultListItem').then( - m => m.ToolSearchResultListItem, - ), - }); +export const exploreSearchResultListItem = createSearchResultListItemExtension({ + predicate: result => result.type === 'tools', + component: () => + import('./components/ToolSearchResultListItem').then( + m => m.ToolSearchResultListItem, + ), +}); /** @alpha */ export default createPlugin({ id: 'explore', - extensions: [ExploreSearchResultListItemExtension], + extensions: [exploreSearchResultListItem], }); diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx index ea989c2834..f7f0eab0bd 100644 --- a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx +++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.tsx @@ -164,7 +164,9 @@ function RenderNode(props: DependencyGraphTypes.RenderNodeProps) { /** * Dynamically generates a diagram of groups registered in the catalog. */ -export function GroupsDiagram() { +export function GroupsDiagram(props: { + direction?: DependencyGraphTypes.Direction; +}) { const nodes = new Array<{ id: string; kind: string; @@ -243,7 +245,7 @@ export function GroupsDiagram() { nodes={nodes} edges={edges} nodeMargin={10} - direction={DependencyGraphTypes.Direction.RIGHT_LEFT} + direction={props.direction || DependencyGraphTypes.Direction.RIGHT_LEFT} renderNode={RenderNode} className={classes.graph} fit="contain" diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.tsx index 61757a35b6..27a7f629ad 100644 --- a/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.tsx +++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.tsx @@ -19,6 +19,7 @@ import { GroupsDiagram } from './GroupsDiagram'; import { Content, ContentHeader, + DependencyGraphTypes, SupportButton, } from '@backstage/core-components'; import { makeStyles } from '@material-ui/core/styles'; @@ -34,7 +35,10 @@ const useStyles = makeStyles( { name: 'ExploreGroupsContent' }, ); -export const GroupsExplorerContent = (props: { title?: string }) => { +export const GroupsExplorerContent = (props: { + title?: string; + direction?: DependencyGraphTypes.Direction; +}) => { const classes = useStyles(); return ( @@ -42,7 +46,7 @@ export const GroupsExplorerContent = (props: { title?: string }) => { Explore your groups. - + ); }; diff --git a/plugins/firehydrant/CHANGELOG.md b/plugins/firehydrant/CHANGELOG.md index 53b350f331..9d3abff382 100644 --- a/plugins/firehydrant/CHANGELOG.md +++ b/plugins/firehydrant/CHANGELOG.md @@ -1,5 +1,98 @@ # @backstage/plugin-firehydrant +## 0.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.2.13 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.2.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.2.13-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## 0.2.12-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.2.12-next.0 ### Patch Changes diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index 19f4ed58d6..0171f83c7d 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-firehydrant", "description": "A Backstage plugin that integrates towards FireHydrant", - "version": "0.2.12-next.0", + "version": "0.2.14-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,8 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "luxon": "^3.0.0", "react-use": "^17.2.4" @@ -54,9 +52,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index fea4e21868..eb6388bee3 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,106 @@ # @backstage/plugin-fossa +## 0.2.62-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## 0.2.61 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.61-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.2.61-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.61-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.2.60 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.60-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.2.60-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.2.60-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.2.60-next.0 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index be34560e1b..7eee0592f5 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-fossa", "description": "A Backstage plugin that integrates towards FOSSA", - "version": "0.2.60-next.0", + "version": "0.2.62-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -39,9 +39,7 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^4.0.0", @@ -62,7 +60,6 @@ "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0" }, "files": [ diff --git a/plugins/gcalendar/CHANGELOG.md b/plugins/gcalendar/CHANGELOG.md index e8663cb365..cb9865d96c 100644 --- a/plugins/gcalendar/CHANGELOG.md +++ b/plugins/gcalendar/CHANGELOG.md @@ -1,5 +1,74 @@ # @backstage/plugin-gcalendar +## 0.3.22 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## 0.3.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/errors@1.2.3 + +## 0.3.22-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.3.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/errors@1.2.3 + +## 0.3.21-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + +## 0.3.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.3.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.3.21-next.0 ### Patch Changes diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json index c75c431f18..c8a5b0bd0a 100644 --- a/plugins/gcalendar/package.json +++ b/plugins/gcalendar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gcalendar", - "version": "0.3.21-next.0", + "version": "0.3.22", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,15 +32,12 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@maxim_mazurok/gapi.client.calendar": "^3.0.20220408", "@tanstack/react-query": "^4.1.3", "@types/react": "^16.13.1 || ^17.0.0", "classnames": "^2.3.1", - "cross-fetch": "^4.0.0", "dompurify": "^2.3.6", "lodash": "^4.17.21", "luxon": "^3.0.0", @@ -54,16 +51,13 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "@types/dompurify": "^2.3.3", - "@types/sanitize-html": "^2.6.2", - "msw": "^1.0.0" + "@types/sanitize-html": "^2.6.2" }, "files": [ "dist" diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index c1a3f6ca6d..bcdc653349 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,70 @@ # @backstage/plugin-gcp-projects +## 0.3.45 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 0.3.45-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## 0.3.45-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## 0.3.44 + +### Patch Changes + +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- d2f5662: Fix query parameter for project details page which should point to projectId rather than project name +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## 0.3.44-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## 0.3.44-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.3.44-next.1 + +### Patch Changes + +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + ## 0.3.44-next.0 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 1fde4908fa..571267216c 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gcp-projects", "description": "A Backstage plugin that helps you manage projects in GCP", - "version": "0.3.44-next.0", + "version": "0.3.45", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,11 +36,8 @@ "dependencies": { "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", - "@react-hookz/web": "^20.0.0", + "@react-hookz/web": "^23.0.0", "@types/react": "^16.13.1 || ^17.0.0" }, "peerDependencies": { @@ -50,15 +47,10 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx b/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx index 9cde3a51c2..2982cbbd42 100644 --- a/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx +++ b/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx @@ -126,7 +126,7 @@ const DetailsPage = () => { {details?.name && ( + } + /> + ) : ( + + + + {projectName} + + + + + + } + > + + + )} + + ); +}; + +export default WorkflowRunsCard; diff --git a/plugins/scaffolder/src/legacy/ScaffolderPage/index.ts b/plugins/github-actions/src/components/WorkflowRunsCard/index.ts similarity index 88% rename from plugins/scaffolder/src/legacy/ScaffolderPage/index.ts rename to plugins/github-actions/src/components/WorkflowRunsCard/index.ts index 433e39723f..77b4c85664 100644 --- a/plugins/scaffolder/src/legacy/ScaffolderPage/index.ts +++ b/plugins/github-actions/src/components/WorkflowRunsCard/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { ScaffolderPage } from './ScaffolderPage'; +export { WorkflowRunsCard, WorkflowRunsCardView } from './WorkflowRunsCard'; diff --git a/plugins/github-actions/src/components/useWorkflowRuns.ts b/plugins/github-actions/src/components/useWorkflowRuns.ts index 9ffbdad452..aacfabbe2f 100644 --- a/plugins/github-actions/src/components/useWorkflowRuns.ts +++ b/plugins/github-actions/src/components/useWorkflowRuns.ts @@ -17,6 +17,7 @@ import { useState } from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; import { githubActionsApiRef } from '../api/GithubActionsApi'; import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { Branch } from '../api'; export type WorkflowRun = { workflowName?: string; @@ -41,12 +42,12 @@ export function useWorkflowRuns({ owner, repo, branch, - initialPageSize = 5, + initialPageSize = 6, }: { hostname?: string; owner: string; repo: string; - branch?: string; + branch?: string | undefined; initialPageSize?: number; }) { const api = useApi(githubActionsApiRef); @@ -56,6 +57,8 @@ export function useWorkflowRuns({ const [total, setTotal] = useState(0); const [page, setPage] = useState(0); const [pageSize, setPageSize] = useState(initialPageSize); + const [branches, setBranches] = useState([]); + const [defaultBranch, setDefaultBranch] = useState(''); const { loading, @@ -63,6 +66,43 @@ export function useWorkflowRuns({ retry, error, } = useAsyncRetry(async () => { + const fetchedDefaultBranch = await api.getDefaultBranch({ + hostname, + owner, + repo, + }); + + setDefaultBranch(fetchedDefaultBranch); + let selectedBranch = branch; + if (branch === 'default') { + selectedBranch = fetchedDefaultBranch; + } + + const fetchBranches = async () => { + let next = true; + let iteratePage = 0; + const branchSet: Branch[] = []; + + while (next) { + const branchesData = await api.listBranches({ + hostname, + owner, + repo, + page: iteratePage, + }); + if (branchesData.length === 0) { + next = false; + } + iteratePage++; + branchSet.push(...branchesData); + } + + return branchSet; + }; + + const branchSet = await fetchBranches(); + setBranches(branchSet); + // GitHub API pagination count starts from 1 const workflowRunsData = await api.listWorkflowRuns({ hostname, @@ -70,7 +110,7 @@ export function useWorkflowRuns({ repo, pageSize, page: page + 1, - branch, + branch: selectedBranch, }); setTotal(workflowRunsData.total_count); // Transformation here @@ -88,7 +128,7 @@ export function useWorkflowRuns({ }); } catch (e) { errorApi.post( - new Error(`Failed to rerun the workflow: ${e.message}`), + new Error(`Failed to rerun the workflow: ${(e as Error).message}`), ); } }, @@ -115,6 +155,8 @@ export function useWorkflowRuns({ pageSize, loading, runs, + branches, + defaultBranch, projectName: `${owner}/${repo}`, total, error, diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md index 7945e4810d..0378efa14f 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,123 @@ # @backstage/plugin-github-deployments +## 0.1.61-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.23 + +## 0.1.60 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.60-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## 0.1.60-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.60-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.59 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.59-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.59-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + +## 0.1.59-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.1.59-next.0 ### Patch Changes diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index a3a7383151..713b8d16f6 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-deployments", "description": "A Backstage plugin that integrates towards GitHub Deployments", - "version": "0.1.59-next.0", + "version": "0.1.61-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,10 +37,8 @@ "@backstage/integration": "workspace:^", "@backstage/integration-react": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@octokit/graphql": "^5.0.0", "@types/react": "^16.13.1 || ^17.0.0", "luxon": "^3.0.0", @@ -59,7 +57,6 @@ "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0" }, "files": [ diff --git a/plugins/github-issues/CHANGELOG.md b/plugins/github-issues/CHANGELOG.md index 2f6ab11e34..5920fad0d3 100644 --- a/plugins/github-issues/CHANGELOG.md +++ b/plugins/github-issues/CHANGELOG.md @@ -1,5 +1,116 @@ # @backstage/plugin-github-issues +## 0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.2.18 + +### Patch Changes + +- bf92ae3: Updated dependency `octokit` to `^3.0.0`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.18-next.0 + +### Patch Changes + +- bf92ae3: Updated dependency `octokit` to `^3.0.0`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.2.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## 0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.2.17-next.0 ### Patch Changes diff --git a/plugins/github-issues/package.json b/plugins/github-issues/package.json index bf6b8a39fd..d2d47af426 100644 --- a/plugins/github-issues/package.json +++ b/plugins/github-issues/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-issues", - "version": "0.2.17-next.0", + "version": "0.2.19-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,13 +35,12 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "luxon": "^3.0.0", - "octokit": "^2.0.4", + "octokit": "^3.0.0", "react-use": "^17.4.0" }, "peerDependencies": { @@ -51,14 +50,11 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/github-issues/src/setupTests.ts b/plugins/github-issues/src/setupTests.ts index 326d2ed212..2f91bcb7b3 100644 --- a/plugins/github-issues/src/setupTests.ts +++ b/plugins/github-issues/src/setupTests.ts @@ -13,4 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import '@testing-library/jest-dom'; +import { Settings } from 'luxon'; + +Settings.defaultLocale = 'en'; diff --git a/plugins/github-pull-requests-board/CHANGELOG.md b/plugins/github-pull-requests-board/CHANGELOG.md index 6dfb8de0c8..886bdc7652 100644 --- a/plugins/github-pull-requests-board/CHANGELOG.md +++ b/plugins/github-pull-requests-board/CHANGELOG.md @@ -1,5 +1,106 @@ # @backstage/plugin-github-pull-requests-board +## 0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/integration@1.8.0 + +## 0.1.23 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/integration@1.8.0 + +## 0.1.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + +## 0.1.23-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/integration@1.8.0 + +## 0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/integration@1.8.0 + - @backstage/catalog-model@1.4.3 + +## 0.1.22-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/integration@1.8.0-next.1 + +## 0.1.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.1.22-next.0 ### Patch Changes diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index fe792bd7ab..87737e4294 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-pull-requests-board", "description": "A Backstage plugin that allows you to see all open Pull Requests for all the repositories owned by your team", - "version": "0.1.22-next.0", + "version": "0.1.24-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -40,15 +40,13 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@octokit/rest": "^19.0.3", "@types/react": "^16.13.1 || ^17.0.0", "luxon": "^3.0.0", - "p-limit": "^4.0.0", - "react-use": "^17.2.4" + "p-limit": "^4.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", @@ -57,13 +55,10 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md index 5cbca6f5d7..cd312c04bf 100644 --- a/plugins/gitops-profiles/CHANGELOG.md +++ b/plugins/gitops-profiles/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-gitops-profiles +## 0.3.44 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 0.3.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## 0.3.44-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## 0.3.43 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/config@1.1.1 + +## 0.3.43-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## 0.3.43-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.3.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + ## 0.3.43-next.0 ### Patch Changes diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index e5befa4e8e..d9e509a49b 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gitops-profiles", "description": "A Backstage plugin that helps you manage GitOps profiles", - "version": "0.3.43-next.0", + "version": "0.3.44", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", @@ -56,10 +54,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/gocd/CHANGELOG.md b/plugins/gocd/CHANGELOG.md index e0d44dfe91..eb4276a060 100644 --- a/plugins/gocd/CHANGELOG.md +++ b/plugins/gocd/CHANGELOG.md @@ -1,5 +1,106 @@ # @backstage/plugin-gocd +## 0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## 0.1.35 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.35-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.1.34 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.34-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.1.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.1.34-next.0 ### Patch Changes diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index 5e54063738..86b5f1eb54 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gocd", "description": "A Backstage plugin that integrates towards GoCD", - "version": "0.1.34-next.0", + "version": "0.1.36-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,7 +35,6 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", @@ -60,8 +59,7 @@ "@testing-library/user-event": "^14.0.0", "@types/lodash": "^4.14.173", "@types/luxon": "^3.0.0", - "@types/react": "^16.13.1 || ^17.0.0", - "msw": "^1.0.0" + "@types/react": "^16.13.1 || ^17.0.0" }, "files": [ "dist", diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md index 3066f9fa22..62965f5a24 100644 --- a/plugins/graphiql/CHANGELOG.md +++ b/plugins/graphiql/CHANGELOG.md @@ -1,5 +1,108 @@ # @backstage/plugin-graphiql +## 0.3.3-next.0 + +### Patch Changes + +- 912ca7b: Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 0.3.2 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + +## 0.3.2-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + +## 0.3.1 + +### Patch Changes + +- 03d0b6d: The `convertLegacyRouteRef` utility used by the alpha exports is now imported from `@backstage/core-compat-api`. +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## 0.3.1-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + +## 0.3.1-next.3 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/core-compat-api@0.0.1-next.1 + - @backstage/theme@0.5.0-next.0 + ## 0.3.1-next.1 ### Patch Changes diff --git a/plugins/graphiql/alpha-api-report.md b/plugins/graphiql/api-report-alpha.md similarity index 70% rename from plugins/graphiql/alpha-api-report.md rename to plugins/graphiql/api-report-alpha.md index 3362cf0a59..e642425463 100644 --- a/plugins/graphiql/alpha-api-report.md +++ b/plugins/graphiql/api-report-alpha.md @@ -4,20 +4,21 @@ ```ts import { BackstagePlugin } from '@backstage/frontend-plugin-api'; -import { Extension } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { GraphQLEndpoint } from '@backstage/plugin-graphiql'; import { PortableSchema } from '@backstage/frontend-plugin-api'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) -export function createEndpointExtension(options: { - id: string; +export function createGraphiQLEndpointExtension(options: { + namespace?: string; + name?: string; configSchema?: PortableSchema; disabled?: boolean; factory: (options: { config: TConfig }) => { endpoint: GraphQLEndpoint; }; -}): Extension; +}): ExtensionDefinition; // @alpha (undocumented) const _default: BackstagePlugin< @@ -29,16 +30,16 @@ const _default: BackstagePlugin< export default _default; // @alpha (undocumented) -export const graphiqlBrowseApi: Extension<{}>; +export const graphiqlBrowseApi: ExtensionDefinition<{}>; // @alpha (undocumented) -export const GraphiqlPage: Extension<{ - path: string; +export const graphiqlNavItem: ExtensionDefinition<{ + title: string; }>; // @alpha (undocumented) -export const graphiqlPageSidebarItem: Extension<{ - title: string; +export const graphiqlPage: ExtensionDefinition<{ + path: string; }>; // (No @packageDocumentation comment for this package) diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index eab8f6c141..ed4b6ee72b 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.3.1-next.1", + "version": "0.3.3-next.0", "publishConfig": { "access": "public" }, @@ -50,10 +50,7 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "graphiql": "^3.0.6", "graphql": "^16.0.0", @@ -67,16 +64,12 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "@types/codemirror": "^5.0.0", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@types/codemirror": "^5.0.0" }, "files": [ "dist" diff --git a/plugins/graphiql/src/alpha.tsx b/plugins/graphiql/src/alpha.tsx index 760bc77d8e..086f397d7a 100644 --- a/plugins/graphiql/src/alpha.tsx +++ b/plugins/graphiql/src/alpha.tsx @@ -34,19 +34,22 @@ import { } from '@backstage/plugin-graphiql'; import { createApiFactory, IconComponent } from '@backstage/core-plugin-api'; import { graphiQLRouteRef } from './route-refs'; -import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { + compatWrapper, + convertLegacyRouteRef, + convertLegacyRouteRefs, +} from '@backstage/core-compat-api'; /** @alpha */ -export const GraphiqlPage = createPageExtension({ - id: 'plugin.graphiql.page', +export const graphiqlPage = createPageExtension({ defaultPath: '/graphiql', routeRef: convertLegacyRouteRef(graphiQLRouteRef), - loader: () => import('./components').then(m => ), + loader: () => + import('./components').then(m => compatWrapper()), }); /** @alpha */ -export const graphiqlPageSidebarItem = createNavItemExtension({ - id: 'plugin.graphiql.nav.index', +export const graphiqlNavItem = createNavItemExtension({ title: 'GraphiQL', icon: GraphiQLIcon as IconComponent, routeRef: convertLegacyRouteRef(graphiQLRouteRef), @@ -54,12 +57,12 @@ export const graphiqlPageSidebarItem = createNavItemExtension({ /** @internal */ const endpointDataRef = createExtensionDataRef( - 'plugin.graphiql.endpoint', + 'graphiql.graphiql-endpoint', ); /** @alpha */ export const graphiqlBrowseApi = createApiExtension({ - api: graphQlBrowseApiRef, // apis.plugin.graphiql.browse + api: graphQlBrowseApiRef, inputs: { endpoints: createExtensionInput({ endpoint: endpointDataRef, @@ -68,21 +71,24 @@ export const graphiqlBrowseApi = createApiExtension({ factory({ inputs }) { return createApiFactory( graphQlBrowseApiRef, - GraphQLEndpoints.from(inputs.endpoints.map(i => i.endpoint)), + GraphQLEndpoints.from(inputs.endpoints.map(i => i.output.endpoint)), ); }, }); /** @alpha */ -export function createEndpointExtension(options: { - id: string; +export function createGraphiQLEndpointExtension(options: { + namespace?: string; + name?: string; configSchema?: PortableSchema; disabled?: boolean; factory: (options: { config: TConfig }) => { endpoint: GraphQLEndpoint }; }) { return createExtension({ - id: `apis.plugin.graphiql.browse.${options.id}`, - attachTo: { id: 'apis.plugin.graphiql.browse', input: 'endpoints' }, + kind: 'graphiql-endpoint', + namespace: options.namespace, + name: options.name, + attachTo: { id: 'api:graphiql/browse', input: 'endpoints' }, configSchema: options.configSchema, disabled: options.disabled ?? false, output: { @@ -97,31 +103,33 @@ export function createEndpointExtension(options: { } /** @alpha */ -const gitlabGraphiQLBrowseExtension = createEndpointExtension({ - id: 'gitlab', - disabled: true, - configSchema: createSchemaFromZod(z => - z - .object({ - id: z.string().default('gitlab'), - title: z.string().default('GitLab'), - url: z.string().default('https://gitlab.com/api/graphql'), - }) - .default({}), - ), - factory: ({ config }) => ({ endpoint: GraphQLEndpoints.create(config) }), -}); +const graphiqlGitlabGraphiQLEndpointExtension = createGraphiQLEndpointExtension( + { + name: 'gitlab', + disabled: true, + configSchema: createSchemaFromZod(z => + z + .object({ + id: z.string().default('gitlab'), + title: z.string().default('GitLab'), + url: z.string().default('https://gitlab.com/api/graphql'), + }) + .default({}), + ), + factory: ({ config }) => ({ endpoint: GraphQLEndpoints.create(config) }), + }, +); /** @alpha */ export default createPlugin({ id: 'graphiql', extensions: [ - GraphiqlPage, + graphiqlPage, graphiqlBrowseApi, - gitlabGraphiQLBrowseExtension, - graphiqlPageSidebarItem, + graphiqlGitlabGraphiQLEndpointExtension, + graphiqlNavItem, ], - routes: { - root: convertLegacyRouteRef(graphiQLRouteRef), - }, + routes: convertLegacyRouteRefs({ + root: graphiQLRouteRef, + }), }); diff --git a/plugins/graphql-voyager/CHANGELOG.md b/plugins/graphql-voyager/CHANGELOG.md index d3ea493357..f65e23b8bb 100644 --- a/plugins/graphql-voyager/CHANGELOG.md +++ b/plugins/graphql-voyager/CHANGELOG.md @@ -1,5 +1,67 @@ # @backstage/plugin-graphql-voyager +## 0.1.11 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## 0.1.11-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## 0.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + ## 0.1.10-next.0 ### Patch Changes diff --git a/plugins/graphql-voyager/package.json b/plugins/graphql-voyager/package.json index 904f388234..2a2941b033 100644 --- a/plugins/graphql-voyager/package.json +++ b/plugins/graphql-voyager/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphql-voyager", "description": "Backstage plugin for GraphQL Voyager", - "version": "0.1.10-next.0", + "version": "0.1.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,10 +32,7 @@ "dependencies": { "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "graphql-voyager": "^1.0.0-rc.31", "react-use": "^17.2.4" @@ -47,14 +44,10 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", - "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index bfe53d9bff..4470b9218e 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,112 @@ # @backstage/plugin-home-react +## 0.1.8-next.0 + +### Patch Changes + +- 6a74ffd: Updated dependency `@rjsf/utils` to `5.16.1`. + Updated dependency `@rjsf/core` to `5.16.1`. + Updated dependency `@rjsf/material-ui` to `5.16.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 0.1.7 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 0.1.7-next.2 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. + +## 0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## 0.1.7-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## 0.1.6 + +### Patch Changes + +- 2b72591: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- 6cd12f2: Updated dependency `@rjsf/utils` to `5.14.1`. + Updated dependency `@rjsf/core` to `5.14.1`. + Updated dependency `@rjsf/material-ui` to `5.14.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- 63c494e: Updated dependency `@rjsf/utils` to `5.14.2`. + Updated dependency `@rjsf/core` to `5.14.2`. + Updated dependency `@rjsf/material-ui` to `5.14.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + +## 0.1.6-next.3 + +### Patch Changes + +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.1.6-next.1 + +### Patch Changes + +- 2b725913c1: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + ## 0.1.6-next.0 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index cb9a0cd388..9d2e6b9dad 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-home-react", "description": "A Backstage plugin that contains react components helps you build a home page", - "version": "0.1.6-next.0", + "version": "0.1.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -38,7 +38,7 @@ "@backstage/core-plugin-api": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@rjsf/utils": "5.14.2", + "@rjsf/utils": "5.16.1", "@types/react": "^16.13.1 || ^17.0.0" }, "peerDependencies": { @@ -48,16 +48,7 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", - "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", - "@testing-library/dom": "^9.0.0", - "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "@types/react-grid-layout": "^1.3.2", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@types/react-grid-layout": "^1.3.2" }, "files": [ "dist" diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 28528b9793..32a9c81161 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,211 @@ # @backstage/plugin-home +## 0.6.2-next.0 + +### Patch Changes + +- 6a74ffd: Updated dependency `@rjsf/utils` to `5.16.1`. + Updated dependency `@rjsf/core` to `5.16.1`. + Updated dependency `@rjsf/material-ui` to `5.16.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. +- e9cdfd3: Fix typo in VisitsStorageApi +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-home-react@0.1.8-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + +## 0.6.1 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-home-react@0.1.7 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.3 + - @backstage/theme@0.5.0 + +## 0.6.1-next.2 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-home-react@0.1.7-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/core-app-api@1.11.3-next.0 + - @backstage/core-compat-api@0.1.1-next.1 + - @backstage/frontend-plugin-api@0.4.1-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-home-react@0.1.7-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + +## 0.6.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/frontend-plugin-api@0.4.1-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-home-react@0.1.7-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-app-api@1.11.2 + - @backstage/core-compat-api@0.1.1-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + +## 0.6.0 + +### Minor Changes + +- 5a317f5: Added view of entities grouped by kind to make it easier to distinguish entities with different kind but same name + +### Patch Changes + +- 2633d64: Change user settings backend plugin id and fix when using user setting backend home page first will cause edit page loop render +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 5814122: Updated `/alpha` exports to fit new naming patterns. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 2b72591: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- 6cd12f2: Updated dependency `@rjsf/utils` to `5.14.1`. + Updated dependency `@rjsf/core` to `5.14.1`. + Updated dependency `@rjsf/material-ui` to `5.14.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- 63c494e: Updated dependency `@rjsf/utils` to `5.14.2`. + Updated dependency `@rjsf/core` to `5.14.2`. + Updated dependency `@rjsf/material-ui` to `5.14.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- 54cef27: StarredEntities component calls `getEntitiesByRefs` instead of `getEntities` to improve performance since we have the `entityRefs` +- c8908d4: Use new option from RJSF 5.15 +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/core-compat-api@0.1.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/frontend-plugin-api@0.4.0 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/core-app-api@1.11.2 + - @backstage/plugin-home-react@0.1.6 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 0.6.0-next.3 + +### Patch Changes + +- 2633d64: Change user settings backend plugin id and fix when using user setting backend home page first will cause edit page loop render +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- 54cef27: StarredEntities component calls `getEntitiesByRefs` instead of `getEntities` to improve performance since we have the `entityRefs` +- c8908d4: Use new option from RJSF 5.15 +- Updated dependencies + - @backstage/plugin-home-react@0.1.6-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-compat-api@0.1.0-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/frontend-plugin-api@0.4.0-next.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.6.0-next.2 + +### Patch Changes + +- a1227cc: Wrap `/alpha` export extension elements in backwards compatibility wrapper. +- 8f5d6c1: Updates to the `/alpha` exports to match the extension input wrapping change. +- 36c94b8: Refactor of the alpha exports due to API change in how extension IDs are constructed. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.2 + - @backstage/theme@0.5.0-next.1 + - @backstage/core-compat-api@0.1.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-home-react@0.1.6-next.2 + +## 0.6.0-next.1 + +### Minor Changes + +- 5a317f59c0: Added view of entities grouped by kind to make it easier to distinguish entities with different kind but same name + +### Patch Changes + +- 2b725913c1: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.4.0-next.1 + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/core-app-api@1.11.2-next.1 + - @backstage/plugin-home-react@0.1.6-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + ## 0.5.12-next.0 ### Patch Changes diff --git a/plugins/home/alpha-api-report.md b/plugins/home/api-report-alpha.md similarity index 100% rename from plugins/home/alpha-api-report.md rename to plugins/home/api-report-alpha.md diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index 18049cae84..dd6e22c57d 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -148,7 +148,7 @@ export const HomePageRecentlyVisited: ( // @public export const HomePageStarredEntities: ( - props: CardExtensionProps_2, + props: CardExtensionProps_2>, ) => JSX_2.Element; // @public @@ -192,6 +192,12 @@ export const SettingsModal: (props: { children: JSX.Element; }) => JSX_2.Element; +// @public +export type StarredEntitiesProps = { + noStarredEntitiesMessage?: React_2.ReactNode | undefined; + groupByKind?: boolean; +}; + // @public (undocumented) export const TemplateBackstageLogo: (props: { classes: { diff --git a/plugins/home/package.json b/plugins/home/package.json index e239332412..603b286c7a 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-home", "description": "A Backstage plugin that helps you build a home page", - "version": "0.5.12-next.0", + "version": "0.6.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -50,29 +50,28 @@ "dependencies": { "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", - "@backstage/config": "workspace:^", "@backstage/core-app-api": "workspace:^", + "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-home-react": "workspace:^", "@backstage/theme": "workspace:^", - "@backstage/types": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "@rjsf/core": "5.14.2", - "@rjsf/material-ui": "5.14.2", - "@rjsf/utils": "5.14.2", - "@rjsf/validator-ajv8": "5.14.2", + "@rjsf/core": "5.16.1", + "@rjsf/material-ui": "5.16.1", + "@rjsf/utils": "5.16.1", + "@rjsf/validator-ajv8": "5.16.1", "@types/react": "^16.13.1 || ^17.0.0", "lodash": "^4.17.21", "luxon": "^3.4.3", "react-grid-layout": "1.3.4", "react-resizable": "^3.0.4", "react-use": "^17.2.4", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0", @@ -87,8 +86,7 @@ "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^14.0.0", - "@types/react-grid-layout": "^1.3.2", - "msw": "^1.0.0" + "@types/react-grid-layout": "^1.3.2" }, "files": [ "dist", diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index c8308f7c0a..e9ff26c9e1 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -24,6 +24,7 @@ import { createPlugin, createRouteRef, } from '@backstage/frontend-plugin-api'; +import { compatWrapper } from '@backstage/core-compat-api'; const rootRouteRef = createRouteRef(); @@ -32,8 +33,7 @@ const rootRouteRef = createRouteRef(); */ export const titleExtensionDataRef = createExtensionDataRef('title'); -const HomepageCompositionRootExtension = createPageExtension({ - id: 'home', +const homePage = createPageExtension({ defaultPath: '/home', routeRef: rootRouteRef, inputs: { @@ -50,12 +50,14 @@ const HomepageCompositionRootExtension = createPageExtension({ ), }, loader: ({ inputs }) => - import('./components/').then(m => ( - - )), + import('./components/').then(m => + compatWrapper( + , + ), + ), }); /** @@ -63,5 +65,5 @@ const HomepageCompositionRootExtension = createPageExtension({ */ export default createPlugin({ id: 'home', - extensions: [HomepageCompositionRootExtension], + extensions: [homePage], }); diff --git a/plugins/home/src/api/VisitsStorageApi.ts b/plugins/home/src/api/VisitsStorageApi.ts index c72d4c5d64..a7cd063455 100644 --- a/plugins/home/src/api/VisitsStorageApi.ts +++ b/plugins/home/src/api/VisitsStorageApi.ts @@ -130,16 +130,16 @@ export class VisitsStorageApi implements VisitsApi { } return new Promise((resolve, reject) => { - const subsription = this.storageApi + const subscription = this.storageApi .observe$(storageKey) .subscribe({ next: next => { const visits = next.value ?? []; - subsription.unsubscribe(); + subscription.unsubscribe(); resolve(visits); }, error: err => { - subsription.unsubscribe(); + subscription.unsubscribe(); reject(err); }, }); diff --git a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx index 590a96bf09..07e21ff8e2 100644 --- a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx +++ b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx @@ -206,10 +206,11 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { availableWidgetsFilter, [props], ); - - const defaultLayout = props.config - ? convertConfigToDefaultWidgets(props.config, availableWidgets) - : []; + const defaultLayout = useMemo(() => { + return props.config + ? convertConfigToDefaultWidgets(props.config, availableWidgets) + : []; + }, [props.config, availableWidgets]); const [widgets, setWidgets] = useHomeStorage(defaultLayout); const [addWidgetDialogOpen, setAddWidgetDialogOpen] = React.useState(false); const editModeOn = widgets.find(w => w.layout.isResizable) !== undefined; diff --git a/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx b/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx index aec4b3dcf2..735720ac13 100644 --- a/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx +++ b/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx @@ -91,6 +91,9 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => { setSettingsDialogOpen(false); } }} + experimental_defaultFormStateBehavior={{ + allOf: 'populateDefaults', + }} /> diff --git a/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx b/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx new file mode 100644 index 0000000000..2a8960e469 --- /dev/null +++ b/plugins/home/src/components/StarredEntityListItem/StarredEntityListItem.tsx @@ -0,0 +1,60 @@ +/* + * 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 { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { entityRouteParams } from '@backstage/plugin-catalog-react'; +import { + ListItem, + ListItemIcon, + Tooltip, + IconButton, + ListItemText, +} from '@material-ui/core'; +import React from 'react'; +import { Link } from 'react-router-dom'; +import { entityRouteRef } from '@backstage/plugin-catalog-react'; +import { useRouteRef } from '@backstage/core-plugin-api'; +import StarIcon from '@material-ui/icons/Star'; + +type EntityListItemProps = { + entity: Entity; + onToggleStarredEntity: (entity: Entity) => void; +}; + +export const StarredEntityListItem = ({ + entity, + onToggleStarredEntity, +}: EntityListItemProps) => { + const catalogEntityRoute = useRouteRef(entityRouteRef); + + return ( + + + + onToggleStarredEntity(entity)} + > + + + + + + + + + ); +}; diff --git a/plugins/home/src/components/StarredEntityListItem/index.ts b/plugins/home/src/components/StarredEntityListItem/index.ts new file mode 100644 index 0000000000..5419e0cdf7 --- /dev/null +++ b/plugins/home/src/components/StarredEntityListItem/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { StarredEntityListItem } from './StarredEntityListItem'; diff --git a/plugins/home/src/homePageComponents/StarredEntities/Content.test.tsx b/plugins/home/src/homePageComponents/StarredEntities/Content.test.tsx index 695406aab5..c41d7f586c 100644 --- a/plugins/home/src/homePageComponents/StarredEntities/Content.test.tsx +++ b/plugins/home/src/homePageComponents/StarredEntities/Content.test.tsx @@ -49,7 +49,7 @@ describe('StarredEntitiesContent', () => { mockedApi.toggleStarred('component:default/mock-starred-entity-3'); const mockCatalogApi = { - getEntities: jest + getEntitiesByRefs: jest .fn() .mockImplementation(async () => ({ items: entities })), }; @@ -87,7 +87,7 @@ describe('StarredEntitiesContent', () => { const mockedApi = new MockStarredEntitiesApi(); const mockCatalogApi = { - getEntities: jest + getEntitiesByRefs: jest .fn() .mockImplementation(async () => ({ items: entities })), }; @@ -117,7 +117,7 @@ describe('StarredEntitiesContent', () => { const mockedApi = new MockStarredEntitiesApi(); const mockCatalogApi = { - getEntities: jest + getEntitiesByRefs: jest .fn() .mockImplementation(async () => ({ items: entities })), }; diff --git a/plugins/home/src/homePageComponents/StarredEntities/Content.tsx b/plugins/home/src/homePageComponents/StarredEntities/Content.tsx index 4654a7f860..b3d4caf44e 100644 --- a/plugins/home/src/homePageComponents/StarredEntities/Content.tsx +++ b/plugins/home/src/homePageComponents/StarredEntities/Content.tsx @@ -17,24 +17,14 @@ import { catalogApiRef, useStarredEntities, - entityRouteParams, - entityRouteRef, } from '@backstage/plugin-catalog-react'; -import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; -import { useApi, useRouteRef } from '@backstage/core-plugin-api'; -import { Link, Progress, ResponseErrorPanel } from '@backstage/core-components'; -import { - List, - ListItem, - ListItemSecondaryAction, - IconButton, - ListItemText, - Tooltip, - Typography, -} from '@material-ui/core'; -import StarIcon from '@material-ui/icons/Star'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { useApi } from '@backstage/core-plugin-api'; +import { Progress, ResponseErrorPanel } from '@backstage/core-components'; +import { List, Typography, Tabs, Tab } from '@material-ui/core'; import React from 'react'; import useAsync from 'react-use/lib/useAsync'; +import { StarredEntityListItem } from '../../components/StarredEntityListItem/StarredEntityListItem'; /** * A component to display a list of starred entities for the user. @@ -42,12 +32,18 @@ import useAsync from 'react-use/lib/useAsync'; * @public */ -export const Content = (props: { +export type StarredEntitiesProps = { noStarredEntitiesMessage?: React.ReactNode | undefined; -}) => { + groupByKind?: boolean; +}; + +export const Content = ({ + noStarredEntitiesMessage, + groupByKind, +}: StarredEntitiesProps) => { const catalogApi = useApi(catalogApiRef); - const catalogEntityRoute = useRouteRef(entityRouteRef); const { starredEntities, toggleStarredEntity } = useStarredEntities(); + const [activeTab, setActiveTab] = React.useState(0); // Grab starred entities from catalog to ensure they still exist and also retrieve display titles const entities = useAsync(async () => { @@ -55,17 +51,9 @@ export const Content = (props: { return []; } - const filter = [...starredEntities] - .map(ent => parseEntityRef(ent)) - .map(ref => ({ - kind: ref.kind, - 'metadata.namespace': ref.namespace, - 'metadata.name': ref.name, - })); - return ( - await catalogApi.getEntities({ - filter, + await catalogApi.getEntitiesByRefs({ + entityRefs: [...starredEntities], fields: [ 'kind', 'metadata.namespace', @@ -73,13 +61,13 @@ export const Content = (props: { 'metadata.title', ], }) - ).items; + ).items.filter((e): e is Entity => !!e); }, [catalogApi, starredEntities]); if (starredEntities.size === 0) return ( - {props.noStarredEntitiesMessage || + {noStarredEntitiesMessage || 'Click the star beside an entity name to add it to this list!'} ); @@ -88,36 +76,73 @@ export const Content = (props: { return ; } + const groupedEntities: { [kind: string]: Entity[] } = {}; + entities.value?.forEach(entity => { + const kind = entity.kind; + if (!groupedEntities[kind]) { + groupedEntities[kind] = []; + } + groupedEntities[kind].push(entity); + }); + + const groupByKindEntries = Object.entries(groupedEntities); + return entities.error ? ( ) : ( - - {entities.value - ?.sort((a, b) => - (a.metadata.title ?? a.metadata.name).localeCompare( - b.metadata.title ?? b.metadata.name, - ), - ) - .map(entity => ( - - - + {!groupByKind && ( + + {entities.value + ?.sort((a, b) => + (a.metadata.title ?? a.metadata.name).localeCompare( + b.metadata.title ?? b.metadata.name, + ), + ) + .map(entity => ( + - - - - toggleStarredEntity(entity)} - > - - - - - + ))} + + )} + + {groupByKind && ( + setActiveTab(newValue)} + variant="scrollable" + scrollButtons="auto" + aria-label="entity-tabs" + > + {groupByKindEntries.map(([kind]) => ( + + ))} + + )} + + {groupByKind && + groupByKindEntries.map(([kind, entitiesByKind], index) => ( + ))} - +
    ); }; diff --git a/plugins/home/src/homePageComponents/StarredEntities/index.ts b/plugins/home/src/homePageComponents/StarredEntities/index.ts index 1faa9a2426..32e9213ea6 100644 --- a/plugins/home/src/homePageComponents/StarredEntities/index.ts +++ b/plugins/home/src/homePageComponents/StarredEntities/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - export { Content } from './Content'; +export type { StarredEntitiesProps } from './Content'; diff --git a/plugins/home/src/homePageComponents/index.ts b/plugins/home/src/homePageComponents/index.ts index dc611291fb..e260982e8e 100644 --- a/plugins/home/src/homePageComponents/index.ts +++ b/plugins/home/src/homePageComponents/index.ts @@ -19,3 +19,4 @@ export type { ClockConfig } from './HeaderWorldClock'; export type { WelcomeTitleLanguageProps } from './WelcomeTitle'; export type { VisitedByTypeProps, VisitedByTypeKind } from './VisitedByType'; export type { FeaturedDocsCardProps } from './FeaturedDocsCard'; +export type { StarredEntitiesProps } from './StarredEntities'; diff --git a/plugins/home/src/plugin.ts b/plugins/home/src/plugin.ts index 5361ecd9c0..cb9f129a72 100644 --- a/plugins/home/src/plugin.ts +++ b/plugins/home/src/plugin.ts @@ -30,6 +30,7 @@ import { } from './homePageComponents'; import { rootRouteRef } from './routes'; import { VisitsStorageApi, visitsApiRef } from './api'; +import { StarredEntitiesProps } from './homePageComponents/StarredEntities/Content'; /** @public */ export const homePlugin = createPlugin({ @@ -168,7 +169,7 @@ export const HomePageToolkit = homePlugin.provide( * @public */ export const HomePageStarredEntities = homePlugin.provide( - createCardExtension({ + createCardExtension>({ name: 'HomePageStarredEntities', title: 'Your Starred Entities', components: () => import('./homePageComponents/StarredEntities'), diff --git a/plugins/home/src/setupTests.ts b/plugins/home/src/setupTests.ts index 7435872609..d56608cdc8 100644 --- a/plugins/home/src/setupTests.ts +++ b/plugins/home/src/setupTests.ts @@ -13,4 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import '@testing-library/jest-dom'; +import { Settings } from 'luxon'; + +Settings.defaultLocale = 'en'; diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md index a410255f8d..f7b769c3e5 100644 --- a/plugins/ilert/CHANGELOG.md +++ b/plugins/ilert/CHANGELOG.md @@ -1,5 +1,106 @@ # @backstage/plugin-ilert +## 0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## 0.2.18 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.18-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.2.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.2.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.2.17-next.0 ### Patch Changes diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index 7123186991..de1dd4797a 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-ilert", "description": "A Backstage plugin that integrates towards iLert", - "version": "0.2.17-next.0", + "version": "0.2.19-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,7 +35,6 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@date-io/luxon": "1.x", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -53,14 +52,10 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist", diff --git a/plugins/jenkins-backend/CHANGELOG.md b/plugins/jenkins-backend/CHANGELOG.md index af36fa4d88..01502bc0a2 100644 --- a/plugins/jenkins-backend/CHANGELOG.md +++ b/plugins/jenkins-backend/CHANGELOG.md @@ -1,5 +1,154 @@ # @backstage/plugin-jenkins-backend +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.23 + - @backstage/plugin-permission-common@0.7.12 + +## 0.3.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.23 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-jenkins-common@0.1.22 + - @backstage/plugin-permission-common@0.7.11 + +## 0.3.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-jenkins-common@0.1.22 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.22 + +## 0.3.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-jenkins-common@0.1.21 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.21 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-jenkins-common@0.1.21 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + ## 0.3.2-next.0 ### Patch Changes diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index 41739ad4eb..c7a97f56ef 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins-backend", "description": "A Backstage backend plugin that integrates towards Jenkins", - "version": "0.3.2-next.0", + "version": "0.3.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -52,9 +52,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@types/jenkins": "^1.0.0", - "@types/supertest": "^2.0.8", - "msw": "^1.0.0", - "supertest": "^6.1.6" + "@types/supertest": "^2.0.8" }, "files": [ "dist", diff --git a/plugins/jenkins-common/CHANGELOG.md b/plugins/jenkins-common/CHANGELOG.md index 47d90761b5..6d364dc40b 100644 --- a/plugins/jenkins-common/CHANGELOG.md +++ b/plugins/jenkins-common/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-jenkins-common +## 0.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-catalog-common@1.0.19 + ## 0.1.21 ### Patch Changes diff --git a/plugins/jenkins-common/package.json b/plugins/jenkins-common/package.json index 7b6a333c3b..d61bd6cede 100644 --- a/plugins/jenkins-common/package.json +++ b/plugins/jenkins-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins-common", - "version": "0.1.21", + "version": "0.1.23", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index f98c22df3e..d781ac91f4 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,114 @@ # @backstage/plugin-jenkins +## 0.9.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.23 + +## 0.9.4 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.23 + +## 0.9.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.9.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.22 + +## 0.9.4-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.22 + +## 0.9.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.22 + +## 0.9.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-jenkins-common@0.1.21 + +## 0.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-jenkins-common@0.1.21 + +## 0.9.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-jenkins-common@0.1.21 + ## 0.9.3-next.0 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index cc45d43590..5a94f1f3ec 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins", "description": "A Backstage plugin that integrates towards Jenkins", - "version": "0.9.3-next.0", + "version": "0.9.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -40,10 +40,8 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-jenkins-common": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "luxon": "^3.0.0", "react-use": "^17.2.4" @@ -55,16 +53,12 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "@types/testing-library__jest-dom": "^5.9.1", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@types/testing-library__jest-dom": "^5.9.1" }, "files": [ "dist" diff --git a/plugins/kafka-backend/CHANGELOG.md b/plugins/kafka-backend/CHANGELOG.md index 076b38956b..187033c6de 100644 --- a/plugins/kafka-backend/CHANGELOG.md +++ b/plugins/kafka-backend/CHANGELOG.md @@ -1,5 +1,99 @@ # @backstage/plugin-kafka-backend +## 0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.3.7 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.3.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## 0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + +## 0.3.7-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.3.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + ## 0.3.6-next.0 ### Patch Changes diff --git a/plugins/kafka-backend/alpha-api-report.md b/plugins/kafka-backend/api-report-alpha.md similarity index 100% rename from plugins/kafka-backend/alpha-api-report.md rename to plugins/kafka-backend/api-report-alpha.md diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index 93c817cd65..283f24f001 100644 --- a/plugins/kafka-backend/package.json +++ b/plugins/kafka-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka-backend", "description": "A Backstage backend plugin that integrates towards Kafka", - "version": "0.3.6-next.0", + "version": "0.3.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -49,7 +49,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@types/express": "^4.17.6", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index 9dd1951c1d..29c761cddc 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,102 @@ # @backstage/plugin-kafka +## 0.3.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.3.29 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.3.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.3.29-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## 0.3.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.3.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + ## 0.3.28-next.0 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 7e7a1bbbd7..16b2d5124b 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka", "description": "A Backstage plugin that integrates towards Kafka", - "version": "0.3.28-next.0", + "version": "0.3.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,14 +32,11 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "react-use": "^17.2.4" }, @@ -50,15 +47,12 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "jest-when": "^3.1.0", - "msw": "^1.0.0" + "jest-when": "^3.1.0" }, "files": [ "dist", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 8a2f9b49bf..f3ab4fcab6 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,224 @@ # @backstage/plugin-kubernetes-backend +## 0.14.2-next.0 + +### Patch Changes + +- 7233f57: Fixed an issue where a misleading error message would be logged when an + unsupported service locator method was specified. +- a775596: Enabled a way to include custom auth metadata info on the clusters endpoint. If you want to implement a Kubernetes auth strategy which requires surfacing custom auth metadata to the frontend, use the new presentAuthMetadata method on the AuthenticationStrategy interface. +- 7278d80: The purpose of this patch is to add a new login method which is `googleServiceAccount` configuring the kubernetes properties in the app-config.yaml file with authProvider key +- daad576: Clusters configured with the `aws` authentication strategy can now customize the + `x-k8s-aws-id` header value used to generate tokens. This value can be specified + specified via the `kubernetes.io/x-k8s-aws-id` parameter (in + `metadata.annotations` for clusters in the catalog, or the `authMetadata` block + on clusters in the app-config). This is particularly helpful when a Backstage + instance contains multiple AWS clusters with the same name in different regions + -- using this new parameter, the clusters can be given different logical names + to distinguish them but still use the same ID for the purposes of generating + tokens. +- f180cba: Enabling authentication to kubernetes clusters with mTLS x509 client certs +- 7f6ff25: Custom per-cluster auth metadata (mainly for use with custom `AuthenticationStrategy` implementations) can now be specified in the `authMetadata` property of clusters in the app-config. +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-kubernetes-node@0.1.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + +## 0.14.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-node@0.1.3 + +## 0.14.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-kubernetes-node@0.1.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## 0.14.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration-aws-node@0.1.8 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/plugin-kubernetes-node@0.1.3-next.1 + - @backstage/plugin-permission-common@0.7.11 + +## 0.14.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-kubernetes-node@0.1.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## 0.14.0 + +### Minor Changes + +- 52050ad: You can now select `single` kubernetes cluster that the entity is part-of from all your defined kubernetes clusters, by passing `backstage.io/kubernetes-cluster` annotation with the defined cluster name. + + If you do not specify the annotation by `default it fetches all` defined kubernetes cluster. + + To apply + + catalog-info.yaml + + ```diff + annotations: + 'backstage.io/kubernetes-id': dice-roller + 'backstage.io/kubernetes-namespace': dice-space + + 'backstage.io/kubernetes-cluster': dice-cluster + 'backstage.io/kubernetes-label-selector': 'app=my-app,component=front-end' + ``` + +### Patch Changes + +- 6010564: The `kubernetes-node` plugin has been modified to house a new extension points for Kubernetes backend plugin; + `KubernetesClusterSupplierExtensionPoint` is introduced . + `kubernetesAuthStrategyExtensionPoint` is introduced . + `kubernetesFetcherExtensionPoint` is introduced . + `kubernetesServiceLocatorExtensionPoint` is introduced . + + The `kubernetes-backend` plugin was modified to use this new extension point. + +- 706fc3a: Updated dependency `@kubernetes/client-node` to `0.20.0`. +- ae94d3c: Updated dependency `@aws-crypto/sha256-js` to `^5.0.0`. +- 99fb541: Updated dependency `@azure/identity` to `^4.0.0`. +- 42c1aee: Updated dependency `@google-cloud/container` to `^5.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-kubernetes-node@0.1.2 + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + +## 0.14.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/plugin-kubernetes-node@0.1.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## 0.14.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/plugin-kubernetes-node@0.1.2-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## 0.14.0-next.1 + +### Patch Changes + +- ae94d3ce6f: Updated dependency `@aws-crypto/sha256-js` to `^5.0.0`. +- 99fb54183b: Updated dependency `@azure/identity` to `^4.0.0`. +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration-aws-node@0.1.8 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-kubernetes-node@0.1.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + ## 0.14.0-next.0 ### Minor Changes diff --git a/plugins/kubernetes-backend/alpha-api-report.md b/plugins/kubernetes-backend/api-report-alpha.md similarity index 100% rename from plugins/kubernetes-backend/alpha-api-report.md rename to plugins/kubernetes-backend/api-report-alpha.md diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index f79b14ede0..afdb3bcf9a 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -3,153 +3,140 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AuthenticationStrategy as AuthenticationStrategy_2 } from '@backstage/plugin-kubernetes-node'; +import { AuthMetadata as AuthMetadata_2 } from '@backstage/plugin-kubernetes-node'; import { CatalogApi } from '@backstage/catalog-client'; +import { ClusterDetails as ClusterDetails_2 } from '@backstage/plugin-kubernetes-node'; import { Config } from '@backstage/config'; -import type { CustomResourceMatcher } from '@backstage/plugin-kubernetes-common'; -import { CustomResourcesByEntity } from '@backstage/plugin-kubernetes-node'; +import { CustomResource as CustomResource_2 } from '@backstage/plugin-kubernetes-node'; import { Duration } from 'luxon'; -import { Entity } from '@backstage/catalog-model'; import express from 'express'; -import type { FetchResponse } from '@backstage/plugin-kubernetes-common'; -import type { JsonObject } from '@backstage/types'; -import type { KubernetesFetchError } from '@backstage/plugin-kubernetes-common'; -import { KubernetesObjectsByEntity } from '@backstage/plugin-kubernetes-node'; -import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node'; +import * as k8sAuthTypes from '@backstage/plugin-kubernetes-node'; +import { KubernetesClustersSupplier as KubernetesClustersSupplier_2 } from '@backstage/plugin-kubernetes-node'; +import { KubernetesCredential as KubernetesCredential_2 } from '@backstage/plugin-kubernetes-node'; +import { KubernetesFetcher as KubernetesFetcher_2 } from '@backstage/plugin-kubernetes-node'; +import { KubernetesObjectsProvider as KubernetesObjectsProvider_2 } from '@backstage/plugin-kubernetes-node'; import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; import type { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; +import { KubernetesServiceLocator as KubernetesServiceLocator_2 } from '@backstage/plugin-kubernetes-node'; import { Logger } from 'winston'; +import { ObjectToFetch as ObjectToFetch_2 } from '@backstage/plugin-kubernetes-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { RequestHandler } from 'http-proxy-middleware'; import { TokenCredential } from '@azure/identity'; // @public (undocumented) -export class AksStrategy implements AuthenticationStrategy { +export class AksStrategy implements AuthenticationStrategy_2 { // (undocumented) getCredential( - _: ClusterDetails, + _: ClusterDetails_2, requestAuth: KubernetesRequestAuth, - ): Promise; + ): Promise; + // (undocumented) + presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; // (undocumented) validateCluster(): Error[]; } // @public (undocumented) -export class AnonymousStrategy implements AuthenticationStrategy { +export class AnonymousStrategy implements AuthenticationStrategy_2 { // (undocumented) - getCredential(): Promise; + getCredential(): Promise; + // (undocumented) + presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; // (undocumented) validateCluster(): Error[]; } -// @public (undocumented) -export interface AuthenticationStrategy { - // (undocumented) - getCredential( - clusterDetails: ClusterDetails, - authConfig: KubernetesRequestAuth, - ): Promise; - // (undocumented) - validateCluster(authMetadata: AuthMetadata): Error[]; -} +// @public @deprecated (undocumented) +export type AuthenticationStrategy = k8sAuthTypes.AuthenticationStrategy; -// @public -export type AuthMetadata = Record; +// @public @deprecated (undocumented) +export type AuthMetadata = k8sAuthTypes.AuthMetadata; // @public (undocumented) -export class AwsIamStrategy implements AuthenticationStrategy { +export class AwsIamStrategy implements AuthenticationStrategy_2 { constructor(opts: { config: Config }); // (undocumented) - getCredential(clusterDetails: ClusterDetails): Promise; + getCredential( + clusterDetails: ClusterDetails_2, + ): Promise; + // (undocumented) + presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; // (undocumented) validateCluster(): Error[]; } // @public (undocumented) -export class AzureIdentityStrategy implements AuthenticationStrategy { +export class AzureIdentityStrategy implements AuthenticationStrategy_2 { constructor(logger: Logger, tokenCredential?: TokenCredential); // (undocumented) - getCredential(): Promise; + getCredential(): Promise; + // (undocumented) + presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; // (undocumented) validateCluster(): Error[]; } -// @public (undocumented) -export interface ClusterDetails { - // (undocumented) - authMetadata: AuthMetadata; - // (undocumented) - caData?: string | undefined; - // (undocumented) - caFile?: string | undefined; - customResources?: CustomResourceMatcher[]; - dashboardApp?: string; - dashboardParameters?: JsonObject; - dashboardUrl?: string; - name: string; - skipMetricsLookup?: boolean; - // (undocumented) - skipTLSVerify?: boolean; - // (undocumented) - url: string; -} +// @public @deprecated (undocumented) +export type ClusterDetails = k8sAuthTypes.ClusterDetails; // @public @deprecated export function createRouter(options: RouterOptions): Promise; -// @public (undocumented) -export interface CustomResource extends ObjectToFetch { - // (undocumented) - objectType: 'customresources'; -} +// @public @deprecated (undocumented) +export type CustomResource = k8sAuthTypes.CustomResource; -export { CustomResourcesByEntity }; +// @public @deprecated (undocumented) +export type CustomResourcesByEntity = k8sAuthTypes.CustomResourcesByEntity; // @public (undocumented) export const DEFAULT_OBJECTS: ObjectToFetch[]; // @public -export class DispatchStrategy implements AuthenticationStrategy { +export class DispatchStrategy implements AuthenticationStrategy_2 { constructor(options: DispatchStrategyOptions); // (undocumented) getCredential( - clusterDetails: ClusterDetails, + clusterDetails: ClusterDetails_2, auth: KubernetesRequestAuth, - ): Promise; + ): Promise; // (undocumented) - validateCluster(authMetadata: AuthMetadata): Error[]; + presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; + // (undocumented) + validateCluster(authMetadata: AuthMetadata_2): Error[]; } // @public (undocumented) export type DispatchStrategyOptions = { authStrategyMap: { - [key: string]: AuthenticationStrategy; + [key: string]: AuthenticationStrategy_2; }; }; -// @public (undocumented) -export interface FetchResponseWrapper { - // (undocumented) - errors: KubernetesFetchError[]; - // (undocumented) - responses: FetchResponse[]; -} +// @public @deprecated (undocumented) +export type FetchResponseWrapper = k8sAuthTypes.FetchResponseWrapper; // @public (undocumented) -export class GoogleServiceAccountStrategy implements AuthenticationStrategy { +export class GoogleServiceAccountStrategy implements AuthenticationStrategy_2 { // (undocumented) - getCredential(): Promise; + getCredential(): Promise; + // (undocumented) + presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; // (undocumented) validateCluster(): Error[]; } // @public (undocumented) -export class GoogleStrategy implements AuthenticationStrategy { +export class GoogleStrategy implements AuthenticationStrategy_2 { // (undocumented) getCredential( - _: ClusterDetails, + _: ClusterDetails_2, requestAuth: KubernetesRequestAuth, - ): Promise; + ): Promise; + // (undocumented) + presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; // (undocumented) validateCluster(): Error[]; } @@ -164,42 +151,42 @@ export const HEADER_KUBERNETES_CLUSTER: string; export class KubernetesBuilder { constructor(env: KubernetesEnvironment); // (undocumented) - addAuthStrategy(key: string, strategy: AuthenticationStrategy): this; + addAuthStrategy(key: string, strategy: AuthenticationStrategy_2): this; // (undocumented) build(): KubernetesBuilderReturn; // (undocumented) protected buildAuthStrategyMap(): { - [key: string]: AuthenticationStrategy; + [key: string]: AuthenticationStrategy_2; }; // (undocumented) protected buildClusterSupplier( refreshInterval: Duration, - ): KubernetesClustersSupplier; + ): KubernetesClustersSupplier_2; // (undocumented) - protected buildCustomResources(): CustomResource[]; + protected buildCustomResources(): CustomResource_2[]; // (undocumented) - protected buildFetcher(): KubernetesFetcher; + protected buildFetcher(): KubernetesFetcher_2; // (undocumented) protected buildHttpServiceLocator( - _clusterSupplier: KubernetesClustersSupplier, - ): KubernetesServiceLocator; + _clusterSupplier: KubernetesClustersSupplier_2, + ): KubernetesServiceLocator_2; // (undocumented) protected buildMultiTenantServiceLocator( - clusterSupplier: KubernetesClustersSupplier, - ): KubernetesServiceLocator; + clusterSupplier: KubernetesClustersSupplier_2, + ): KubernetesServiceLocator_2; // (undocumented) protected buildObjectsProvider( options: KubernetesObjectsProviderOptions, - ): KubernetesObjectsProvider; + ): KubernetesObjectsProvider_2; // (undocumented) protected buildProxy( logger: Logger, - clusterSupplier: KubernetesClustersSupplier, + clusterSupplier: KubernetesClustersSupplier_2, ): KubernetesProxy; // (undocumented) protected buildRouter( - objectsProvider: KubernetesObjectsProvider, - clusterSupplier: KubernetesClustersSupplier, + objectsProvider: KubernetesObjectsProvider_2, + clusterSupplier: KubernetesClustersSupplier_2, catalogApi: CatalogApi, proxy: KubernetesProxy, permissionApi: PermissionEvaluator, @@ -207,89 +194,81 @@ export class KubernetesBuilder { // (undocumented) protected buildServiceLocator( method: ServiceLocatorMethod, - clusterSupplier: KubernetesClustersSupplier, - ): KubernetesServiceLocator; + clusterSupplier: KubernetesClustersSupplier_2, + ): KubernetesServiceLocator_2; // (undocumented) protected buildSingleTenantServiceLocator( - clusterSupplier: KubernetesClustersSupplier, - ): KubernetesServiceLocator; + clusterSupplier: KubernetesClustersSupplier_2, + ): KubernetesServiceLocator_2; // (undocumented) static createBuilder(env: KubernetesEnvironment): KubernetesBuilder; // (undocumented) protected readonly env: KubernetesEnvironment; // (undocumented) protected fetchClusterDetails( - clusterSupplier: KubernetesClustersSupplier, - ): Promise; + clusterSupplier: KubernetesClustersSupplier_2, + ): Promise; // (undocumented) protected getAuthStrategyMap(): { - [key: string]: AuthenticationStrategy; + [key: string]: AuthenticationStrategy_2; }; // (undocumented) - protected getClusterSupplier(): KubernetesClustersSupplier; + protected getClusterSupplier(): KubernetesClustersSupplier_2; // (undocumented) - protected getFetcher(): KubernetesFetcher; + protected getFetcher(): KubernetesFetcher_2; // (undocumented) protected getObjectsProvider( options: KubernetesObjectsProviderOptions, - ): KubernetesObjectsProvider; + ): KubernetesObjectsProvider_2; // (undocumented) - protected getObjectTypesToFetch(): ObjectToFetch[] | undefined; + protected getObjectTypesToFetch(): ObjectToFetch_2[] | undefined; // (undocumented) protected getProxy( logger: Logger, - clusterSupplier: KubernetesClustersSupplier, + clusterSupplier: KubernetesClustersSupplier_2, ): KubernetesProxy; // (undocumented) - protected getServiceLocator(): KubernetesServiceLocator; + protected getServiceLocator(): KubernetesServiceLocator_2; // (undocumented) protected getServiceLocatorMethod(): ServiceLocatorMethod; // (undocumented) setAuthStrategyMap(authStrategyMap: { - [key: string]: AuthenticationStrategy; + [key: string]: AuthenticationStrategy_2; }): void; // (undocumented) - setClusterSupplier(clusterSupplier?: KubernetesClustersSupplier): this; + setClusterSupplier(clusterSupplier?: KubernetesClustersSupplier_2): this; // (undocumented) setDefaultClusterRefreshInterval(refreshInterval: Duration): this; // (undocumented) - setFetcher(fetcher?: KubernetesFetcher): this; + setFetcher(fetcher?: KubernetesFetcher_2): this; // (undocumented) - setObjectsProvider(objectsProvider?: KubernetesObjectsProvider): this; + setObjectsProvider(objectsProvider?: KubernetesObjectsProvider_2): this; // (undocumented) setProxy(proxy?: KubernetesProxy): this; // (undocumented) - setServiceLocator(serviceLocator?: KubernetesServiceLocator): this; + setServiceLocator(serviceLocator?: KubernetesServiceLocator_2): this; } // @public export type KubernetesBuilderReturn = Promise<{ router: express.Router; - clusterSupplier: KubernetesClustersSupplier; - customResources: CustomResource[]; - fetcher: KubernetesFetcher; + clusterSupplier: KubernetesClustersSupplier_2; + customResources: CustomResource_2[]; + fetcher: KubernetesFetcher_2; proxy: KubernetesProxy; - objectsProvider: KubernetesObjectsProvider; - serviceLocator: KubernetesServiceLocator; + objectsProvider: KubernetesObjectsProvider_2; + serviceLocator: KubernetesServiceLocator_2; authStrategyMap: { - [key: string]: AuthenticationStrategy; + [key: string]: AuthenticationStrategy_2; }; }>; -// @public -export interface KubernetesClustersSupplier { - getClusters(): Promise; -} +// @public @deprecated (undocumented) +export type KubernetesClustersSupplier = + k8sAuthTypes.KubernetesClustersSupplier; -// @public -export type KubernetesCredential = - | { - type: 'bearer token'; - token: string; - } - | { - type: 'anonymous'; - }; +// @public @deprecated (undocumented) +export type KubernetesCredential = k8sAuthTypes.KubernetesCredential; // @public (undocumented) export interface KubernetesEnvironment { @@ -303,57 +282,30 @@ export interface KubernetesEnvironment { permissions: PermissionEvaluator; } -// @public -export interface KubernetesFetcher { - // (undocumented) - fetchObjectsForService( - params: ObjectFetchParams, - ): Promise; - // (undocumented) - fetchPodMetricsByNamespaces( - clusterDetails: ClusterDetails, - credential: KubernetesCredential, - namespaces: Set, - labelSelector?: string, - ): Promise; -} +// @public @deprecated (undocumented) +export type KubernetesFetcher = k8sAuthTypes.KubernetesFetcher; -export { KubernetesObjectsByEntity }; - -export { KubernetesObjectsProvider }; +// @public @deprecated (undocumented) +export type KubernetesObjectsProvider = k8sAuthTypes.KubernetesObjectsProvider; // @public (undocumented) export interface KubernetesObjectsProviderOptions { // (undocumented) config: Config; // (undocumented) - customResources: CustomResource[]; + customResources: k8sAuthTypes.CustomResource[]; // (undocumented) - fetcher: KubernetesFetcher; + fetcher: k8sAuthTypes.KubernetesFetcher; // (undocumented) logger: Logger; // (undocumented) - objectTypesToFetch?: ObjectToFetch[]; + objectTypesToFetch?: k8sAuthTypes.ObjectToFetch[]; // (undocumented) - serviceLocator: KubernetesServiceLocator; + serviceLocator: k8sAuthTypes.KubernetesServiceLocator; } -// @public (undocumented) -export type KubernetesObjectTypes = - | 'pods' - | 'services' - | 'configmaps' - | 'deployments' - | 'limitranges' - | 'resourcequotas' - | 'replicasets' - | 'horizontalpodautoscalers' - | 'jobs' - | 'cronjobs' - | 'ingresses' - | 'customresources' - | 'statefulsets' - | 'daemonsets'; +// @public @deprecated (undocumented) +export type KubernetesObjectTypes = k8sAuthTypes.KubernetesObjectTypes; // @public export class KubernetesProxy { @@ -376,59 +328,29 @@ export type KubernetesProxyOptions = { authStrategy: AuthenticationStrategy; }; -// @public -export interface KubernetesServiceLocator { - // (undocumented) - getClustersByEntity( - entity: Entity, - requestContext: ServiceLocatorRequestContext, - ): Promise<{ - clusters: ClusterDetails[]; - }>; -} +// @public @deprecated (undocumented) +export type KubernetesServiceLocator = k8sAuthTypes.KubernetesServiceLocator; -// @public (undocumented) -export interface ObjectFetchParams { - // (undocumented) - clusterDetails: ClusterDetails; - // (undocumented) - credential: KubernetesCredential; - // (undocumented) - customResources: CustomResource[]; - // (undocumented) - labelSelector?: string; - // (undocumented) - namespace?: string; - // (undocumented) - objectTypesToFetch: Set; - // (undocumented) - serviceId: string; -} +// @public @deprecated (undocumented) +export type ObjectFetchParams = k8sAuthTypes.ObjectFetchParams; // @public (undocumented) export type ObjectsByEntityRequest = KubernetesRequestBody; -// @public (undocumented) -export interface ObjectToFetch { - // (undocumented) - apiVersion: string; - // (undocumented) - group: string; - // (undocumented) - objectType: KubernetesObjectTypes; - // (undocumented) - plural: string; -} +// @public @deprecated (undocumented) +export type ObjectToFetch = k8sAuthTypes.ObjectToFetch; // @public (undocumented) -export class OidcStrategy implements AuthenticationStrategy { +export class OidcStrategy implements AuthenticationStrategy_2 { // (undocumented) getCredential( - clusterDetails: ClusterDetails, + clusterDetails: ClusterDetails_2, authConfig: KubernetesRequestAuth, - ): Promise; + ): Promise; // (undocumented) - validateCluster(authMetadata: AuthMetadata): Error[]; + presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; + // (undocumented) + validateCluster(authMetadata: AuthMetadata_2): Error[]; } // @public (undocumented) @@ -448,9 +370,13 @@ export interface RouterOptions { } // @public (undocumented) -export class ServiceAccountStrategy implements AuthenticationStrategy { +export class ServiceAccountStrategy implements AuthenticationStrategy_2 { // (undocumented) - getCredential(clusterDetails: ClusterDetails): Promise; + getCredential( + clusterDetails: ClusterDetails_2, + ): Promise; + // (undocumented) + presentAuthMetadata(_authMetadata: AuthMetadata_2): AuthMetadata_2; // (undocumented) validateCluster(): Error[]; } @@ -458,13 +384,9 @@ export class ServiceAccountStrategy implements AuthenticationStrategy { // @public (undocumented) export type ServiceLocatorMethod = 'multiTenant' | 'singleTenant' | 'http'; -// @public (undocumented) -export interface ServiceLocatorRequestContext { - // (undocumented) - customResources: CustomResourceMatcher[]; - // (undocumented) - objectTypesToFetch: Set; -} +// @public @deprecated (undocumented) +export type ServiceLocatorRequestContext = + k8sAuthTypes.ServiceLocatorRequestContext; // @public (undocumented) export type SigningCreds = { diff --git a/plugins/kubernetes-backend/config.d.ts b/plugins/kubernetes-backend/config.d.ts index e9b5878920..de7f79b66c 100644 --- a/plugins/kubernetes-backend/config.d.ts +++ b/plugins/kubernetes-backend/config.d.ts @@ -46,13 +46,16 @@ export interface Config { /** @visibility secret */ serviceAccountToken?: string; /** @visibility frontend */ - authProvider: + authProvider?: + | 'aks' | 'aws' - | 'google' - | 'serviceAccount' | 'azure' + | 'google' + | 'googleServiceAccount' | 'oidc' - | 'googleServiceAccount'; + | 'serviceAccount'; + /** @visibility secret */ + authMetadata?: object; /** @visibility frontend */ oidcTokenProvider?: string; /** @visibility frontend */ @@ -81,6 +84,8 @@ export interface Config { /** @visibility frontend */ region?: string; /** @visibility frontend */ + authProvider?: string; + /** @visibility frontend */ skipTLSVerify?: boolean; /** @visibility frontend */ skipMetricsLookup?: boolean; diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index ee18c9c983..307bb1c42c 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.14.0-next.0", + "version": "0.14.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -47,10 +47,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@aws-crypto/sha256-js": "^3.0.0", + "@aws-crypto/sha256-js": "^5.0.0", "@aws-sdk/credential-providers": "^3.350.0", "@aws-sdk/signature-v4": "^3.347.0", - "@azure/identity": "^3.2.1", + "@azure/identity": "^4.0.0", "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-client": "workspace:^", @@ -90,6 +90,8 @@ "@backstage/backend-app-api": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", + "@backstage/plugin-permission-backend": "workspace:^", + "@backstage/plugin-permission-backend-module-allow-all-policy": "workspace:^", "@types/aws4": "^1.5.1", "msw": "^1.0.0", "supertest": "^6.1.3", diff --git a/plugins/kubernetes-backend/src/auth/AksStrategy.ts b/plugins/kubernetes-backend/src/auth/AksStrategy.ts index 07bdf2efe0..40151314a8 100644 --- a/plugins/kubernetes-backend/src/auth/AksStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/AksStrategy.ts @@ -13,8 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ClusterDetails } from '../types/types'; -import { AuthenticationStrategy, KubernetesCredential } from './types'; +import { + AuthMetadata, + AuthenticationStrategy, + ClusterDetails, + KubernetesCredential, +} from '@backstage/plugin-kubernetes-node'; import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; /** @@ -31,7 +35,12 @@ export class AksStrategy implements AuthenticationStrategy { ? { type: 'bearer token', token: token as string } : { type: 'anonymous' }; } + public validateCluster(): Error[] { return []; } + + public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata { + return {}; + } } diff --git a/plugins/kubernetes-backend/src/auth/AnonymousStrategy.ts b/plugins/kubernetes-backend/src/auth/AnonymousStrategy.ts index 30a9593773..0f1aad398a 100644 --- a/plugins/kubernetes-backend/src/auth/AnonymousStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/AnonymousStrategy.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { AuthenticationStrategy, KubernetesCredential } from './types'; +import { + AuthMetadata, + AuthenticationStrategy, + KubernetesCredential, +} from '@backstage/plugin-kubernetes-node'; /** * @@ -28,4 +32,8 @@ export class AnonymousStrategy implements AuthenticationStrategy { public validateCluster(): Error[] { return []; } + + public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata { + return {}; + } } diff --git a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts index 873c78e177..e6e69cc05d 100644 --- a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.test.ts @@ -16,16 +16,11 @@ import { ConfigReader } from '@backstage/config'; import { ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE, + ANNOTATION_KUBERNETES_AWS_CLUSTER_ID, ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID, } from '@backstage/plugin-kubernetes-common'; import { AwsIamStrategy } from './AwsIamStrategy'; -let presign = jest.fn(async () => ({ - hostname: 'https://example.com', - query: {}, - path: '/asdf', -})); - const credsManager = { getCredentialProvider: async () => ({ sdkCredentialProvider: { @@ -40,12 +35,16 @@ jest.mock('@backstage/integration-aws-node', () => ({ }, })); -const config = new ConfigReader({}); +const signer = { + presign: jest.fn().mockResolvedValue({ + hostname: 'https://example.com', + query: {}, + path: '/asdf', + }), +}; jest.mock('@aws-sdk/signature-v4', () => ({ - SignatureV4: jest.fn().mockImplementation(() => ({ - presign, - })), + SignatureV4: jest.fn().mockImplementation(() => signer), })); const fromTemporaryCredentials = jest.fn(); @@ -55,9 +54,10 @@ jest.mock('@aws-sdk/credential-providers', () => ({ }, })); -describe('AwsIamStrategy tests', () => { - beforeEach(() => {}); - it('returns a signed url for AWS credentials without assume role', async () => { +describe('AwsIamStrategy#getCredential', () => { + const config = new ConfigReader({}); + + it('returns a presigned url', async () => { const strategy = new AwsIamStrategy({ config }); const credential = await strategy.getCredential({ @@ -65,13 +65,43 @@ describe('AwsIamStrategy tests', () => { url: '', authMetadata: {}, }); + expect(credential).toEqual({ type: 'bearer token', token: 'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_', }); + expect(signer.presign).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ 'x-k8s-aws-id': 'test-cluster' }), + }), + expect.anything(), + ); }); - it('returns a signed url for AWS credentials with assume role', async () => { + it('returns a presigned url for specified cluster ID', async () => { + const strategy = new AwsIamStrategy({ config }); + + const credential = await strategy.getCredential({ + name: 'cluster-name', + url: '', + authMetadata: { + [ANNOTATION_KUBERNETES_AWS_CLUSTER_ID]: 'other-name', + }, + }); + + expect(credential).toEqual({ + type: 'bearer token', + token: 'k8s-aws-v1.aHR0cHM6Ly9odHRwczovL2V4YW1wbGUuY29tL2FzZGY_', + }); + expect(signer.presign).toHaveBeenCalledWith( + expect.objectContaining({ + headers: expect.objectContaining({ 'x-k8s-aws-id': 'other-name' }), + }), + expect.anything(), + ); + }); + + it('returns a presigned url for AWS credentials with assumed role', async () => { const strategy = new AwsIamStrategy({ config }); const credential = await strategy.getCredential({ @@ -100,7 +130,7 @@ describe('AwsIamStrategy tests', () => { }); }); - it('returns a signed url for AWS credentials and passes the external id', async () => { + it('returns a presigned url for AWS credentials and passes the external id', async () => { const strategy = new AwsIamStrategy({ config }); const credential = await strategy.getCredential({ @@ -129,21 +159,17 @@ describe('AwsIamStrategy tests', () => { }); }); - describe('When the credentials is failing', () => { - beforeEach(() => { - presign = jest.fn(async () => { - throw new Error('no way'); - }); - }); - it('throws the right error', async () => { - const strategy = new AwsIamStrategy({ config }); - await expect( - strategy.getCredential({ - name: 'test-cluster', - url: '', - authMetadata: {}, - }), - ).rejects.toThrow('no way'); - }); + it('fails on signer error', () => { + signer.presign.mockRejectedValue(new Error('no way')); + + const strategy = new AwsIamStrategy({ config }); + + return expect( + strategy.getCredential({ + name: 'test-cluster', + url: '', + authMetadata: {}, + }), + ).rejects.toThrow('no way'); }); }); diff --git a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts index 53b880d60c..4c56f86a30 100644 --- a/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/AwsIamStrategy.ts @@ -23,10 +23,15 @@ import { import { Config } from '@backstage/config'; import { ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE, + ANNOTATION_KUBERNETES_AWS_CLUSTER_ID, ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID, } from '@backstage/plugin-kubernetes-common'; -import { ClusterDetails } from '../types/types'; -import { AuthenticationStrategy, KubernetesCredential } from './types'; +import { + AuthMetadata, + AuthenticationStrategy, + ClusterDetails, + KubernetesCredential, +} from '@backstage/plugin-kubernetes-node'; /** * @@ -57,7 +62,8 @@ export class AwsIamStrategy implements AuthenticationStrategy { return { type: 'bearer token', token: await this.getBearerToken( - clusterDetails.name, + clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_CLUSTER_ID] ?? + clusterDetails.name, clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE], clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID], ), @@ -69,7 +75,7 @@ export class AwsIamStrategy implements AuthenticationStrategy { } private async getBearerToken( - clusterName: string, + clusterId: string, assumeRole?: string, externalId?: string, ): Promise { @@ -101,7 +107,7 @@ export class AwsIamStrategy implements AuthenticationStrategy { { headers: { host: `sts.${region}.amazonaws.com`, - 'x-k8s-aws-id': clusterName, + 'x-k8s-aws-id': clusterId, }, hostname: `sts.${region}.amazonaws.com`, method: 'GET', @@ -128,4 +134,8 @@ export class AwsIamStrategy implements AuthenticationStrategy { return `k8s-aws-v1.${Buffer.from(url).toString('base64url')}`; } + + public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata { + return {}; + } } diff --git a/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.ts b/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.ts index d0eea3c13c..a1b29a10a1 100644 --- a/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/AzureIdentityStrategy.ts @@ -15,12 +15,16 @@ */ import { Logger } from 'winston'; -import { AuthenticationStrategy, KubernetesCredential } from './types'; import { AccessToken, DefaultAzureCredential, TokenCredential, } from '@azure/identity'; +import { + AuthMetadata, + AuthenticationStrategy, + KubernetesCredential, +} from '@backstage/plugin-kubernetes-node'; const aksScope = '6dae42f8-4368-4678-94ff-3960e28e3630/.default'; // This scope is the same for all Azure Managed Kubernetes @@ -89,4 +93,8 @@ export class AzureIdentityStrategy implements AuthenticationStrategy { private tokenExpired(): boolean { return Date.now() >= this.accessToken.expiresOnTimestamp; } + + public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata { + return {}; + } } diff --git a/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts b/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts index 7202311d6f..773e464168 100644 --- a/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts +++ b/plugins/kubernetes-backend/src/auth/DispatchStrategy.test.ts @@ -31,6 +31,7 @@ describe('getCredential', () => { mockStrategy = { getCredential: jest.fn(), validateCluster: jest.fn(), + presentAuthMetadata: jest.fn(), }; strategy = new DispatchStrategy({ authStrategyMap: { google: mockStrategy }, diff --git a/plugins/kubernetes-backend/src/auth/DispatchStrategy.ts b/plugins/kubernetes-backend/src/auth/DispatchStrategy.ts index f12e49b577..14a3009a65 100644 --- a/plugins/kubernetes-backend/src/auth/DispatchStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/DispatchStrategy.ts @@ -14,12 +14,16 @@ * limitations under the License. */ -import { AuthenticationStrategy, KubernetesCredential } from './types'; -import { AuthMetadata, ClusterDetails } from '../types'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, KubernetesRequestAuth, } from '@backstage/plugin-kubernetes-common'; +import { + AuthMetadata, + AuthenticationStrategy, + ClusterDetails, + KubernetesCredential, +} from '@backstage/plugin-kubernetes-node'; /** * @@ -67,4 +71,8 @@ export class DispatchStrategy implements AuthenticationStrategy { } return strategy.validateCluster(authMetadata); } + + public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata { + return {}; + } } diff --git a/plugins/kubernetes-backend/src/auth/GoogleServiceAccountStrategy.ts b/plugins/kubernetes-backend/src/auth/GoogleServiceAccountStrategy.ts index 2a44a31d6c..f6757d6a64 100644 --- a/plugins/kubernetes-backend/src/auth/GoogleServiceAccountStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/GoogleServiceAccountStrategy.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AuthenticationStrategy, KubernetesCredential } from './types'; + +import { + AuthMetadata, + AuthenticationStrategy, + KubernetesCredential, +} from '@backstage/plugin-kubernetes-node'; import * as container from '@google-cloud/container'; /** @@ -36,4 +41,8 @@ export class GoogleServiceAccountStrategy implements AuthenticationStrategy { public validateCluster(): Error[] { return []; } + + public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata { + return {}; + } } diff --git a/plugins/kubernetes-backend/src/auth/GoogleStrategy.ts b/plugins/kubernetes-backend/src/auth/GoogleStrategy.ts index c1290dc1fe..c48086f7f8 100644 --- a/plugins/kubernetes-backend/src/auth/GoogleStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/GoogleStrategy.ts @@ -14,9 +14,13 @@ * limitations under the License. */ -import { AuthenticationStrategy, KubernetesCredential } from './types'; -import { ClusterDetails } from '../types/types'; import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; +import { + AuthMetadata, + AuthenticationStrategy, + ClusterDetails, + KubernetesCredential, +} from '@backstage/plugin-kubernetes-node'; /** * @@ -35,7 +39,12 @@ export class GoogleStrategy implements AuthenticationStrategy { } return { type: 'bearer token', token: token as string }; } + public validateCluster(): Error[] { return []; } + + public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata { + return {}; + } } diff --git a/plugins/kubernetes-backend/src/auth/OidcStrategy.ts b/plugins/kubernetes-backend/src/auth/OidcStrategy.ts index 445799cbb8..1039f1d40c 100644 --- a/plugins/kubernetes-backend/src/auth/OidcStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/OidcStrategy.ts @@ -18,8 +18,12 @@ import { ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER, KubernetesRequestAuth, } from '@backstage/plugin-kubernetes-common'; -import { AuthenticationStrategy, KubernetesCredential } from './types'; -import { AuthMetadata, ClusterDetails } from '../types/types'; +import { + AuthMetadata, + AuthenticationStrategy, + ClusterDetails, + KubernetesCredential, +} from '@backstage/plugin-kubernetes-node'; /** * @@ -57,4 +61,8 @@ export class OidcStrategy implements AuthenticationStrategy { } return []; } + + public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata { + return {}; + } } diff --git a/plugins/kubernetes-backend/src/auth/ServiceAccountStrategy.ts b/plugins/kubernetes-backend/src/auth/ServiceAccountStrategy.ts index 714b2a94d1..aff6e61a64 100644 --- a/plugins/kubernetes-backend/src/auth/ServiceAccountStrategy.ts +++ b/plugins/kubernetes-backend/src/auth/ServiceAccountStrategy.ts @@ -14,10 +14,14 @@ * limitations under the License. */ +import { + AuthMetadata, + AuthenticationStrategy, + ClusterDetails, + KubernetesCredential, +} from '@backstage/plugin-kubernetes-node'; import { KubeConfig, User } from '@kubernetes/client-node'; import fs from 'fs-extra'; -import { AuthenticationStrategy, KubernetesCredential } from './types'; -import { ClusterDetails } from '../types/types'; /** * @@ -45,4 +49,8 @@ export class ServiceAccountStrategy implements AuthenticationStrategy { public validateCluster(): Error[] { return []; } + + public presentAuthMetadata(_authMetadata: AuthMetadata): AuthMetadata { + return {}; + } } diff --git a/plugins/kubernetes-backend/src/auth/types.ts b/plugins/kubernetes-backend/src/auth/types.ts index fff9952bae..9b33265448 100644 --- a/plugins/kubernetes-backend/src/auth/types.ts +++ b/plugins/kubernetes-backend/src/auth/types.ts @@ -14,25 +14,15 @@ * limitations under the License. */ -import { AuthMetadata, ClusterDetails } from '../types/types'; -import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; +import * as k8sAuthTypes from '@backstage/plugin-kubernetes-node'; + +// TODO remove this re-export as a breaking change after a couple of releases +/** + * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type AuthenticationStrategy = k8sAuthTypes.AuthenticationStrategy; /** - * Authentication data used to make a request to Kubernetes - * @public + * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead */ -export type KubernetesCredential = - | { type: 'bearer token'; token: string } - | { type: 'anonymous' }; - -/** - * - * @public - */ -export interface AuthenticationStrategy { - getCredential( - clusterDetails: ClusterDetails, - authConfig: KubernetesRequestAuth, - ): Promise; - validateCluster(authMetadata: AuthMetadata): Error[]; -} +export type KubernetesCredential = k8sAuthTypes.KubernetesCredential; diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts index 258cddcd66..ad3203a16d 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.test.ts @@ -32,6 +32,7 @@ describe('ConfigClusterLocator', () => { authStrategy = { getCredential: jest.fn(), validateCluster: jest.fn().mockReturnValue([]), + presentAuthMetadata: jest.fn(), }; }); @@ -177,6 +178,97 @@ describe('ConfigClusterLocator', () => { ]); }); + it('reads custom authMetadata', async () => { + const config: Config = new ConfigReader({ + clusters: [ + { + name: 'cluster', + url: 'http://url', + authProvider: 'authProvider', + authMetadata: { 'custom-key': 'custom-value' }, + }, + ], + }); + + const result = await ConfigClusterLocator.fromConfig( + config, + authStrategy, + ).getClusters(); + + expect(result).toMatchObject([ + { + authMetadata: expect.objectContaining({ 'custom-key': 'custom-value' }), + }, + ]); + }); + + it('reads authProvider from metadata block', async () => { + const config: Config = new ConfigReader({ + clusters: [ + { + name: 'cluster', + url: 'http://url', + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'serviceAccount', + }, + }, + ], + }); + + const result = await ConfigClusterLocator.fromConfig( + config, + authStrategy, + ).getClusters(); + + expect(result).toMatchObject([ + { + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'serviceAccount', + }, + }, + ]); + }); + + it('prefers authMetadata block to top-level keys', async () => { + const sut = ConfigClusterLocator.fromConfig( + new ConfigReader({ + clusters: [ + { + name: 'cluster', + url: 'http://url', + authProvider: 'aws', + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'serviceAccount', + }, + }, + ], + }), + authStrategy, + ); + + const result = await sut.getClusters(); + + expect(result).toMatchObject([ + { + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'serviceAccount', + }, + }, + ]); + }); + + it('forbids cluster without auth provider', () => { + const config: Config = new ConfigReader({ + clusters: [{ name: 'cluster', url: 'http://url' }], + }); + + expect(() => ConfigClusterLocator.fromConfig(config, authStrategy)).toThrow( + `cluster 'cluster' has no auth provider configured; this must be specified` + + ` via the 'authProvider' or ` + + `'authMetadata.${ANNOTATION_KUBERNETES_AUTH_PROVIDER}' parameter`, + ); + }); + it('one cluster with dashboardParameters', async () => { const config: Config = new ConfigReader({ clusters: [ diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts index 7899d7762d..19ba8634ad 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts @@ -37,9 +37,22 @@ export class ConfigClusterLocator implements KubernetesClustersSupplier { ): ConfigClusterLocator { return new ConfigClusterLocator( config.getConfigArray('clusters').map(c => { - const authProvider = c.getString('authProvider'); + const authMetadataBlock = c.getOptional<{ + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]?: string; + }>('authMetadata'); + const name = c.getString('name'); + const authProvider = + authMetadataBlock?.[ANNOTATION_KUBERNETES_AUTH_PROVIDER] ?? + c.getOptionalString('authProvider'); + if (!authProvider) { + throw new Error( + `cluster '${name}' has no auth provider configured; this must be ` + + `specified via the 'authProvider' or ` + + `'authMetadata.${ANNOTATION_KUBERNETES_AUTH_PROVIDER}' parameter`, + ); + } const clusterDetails: ClusterDetails = { - name: c.getString('name'), + name, url: c.getString('url'), skipTLSVerify: c.getOptionalBoolean('skipTLSVerify') ?? false, skipMetricsLookup: c.getOptionalBoolean('skipMetricsLookup') ?? false, @@ -48,6 +61,7 @@ export class ConfigClusterLocator implements KubernetesClustersSupplier { authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: authProvider, ...ConfigClusterLocator.parseAuthMetadata(c), + ...authMetadataBlock, }, }; diff --git a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts index f919561490..849a7839e2 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.test.ts @@ -382,5 +382,108 @@ describe('GkeClusterLocator', () => { parent: 'projects/some-project/locations/some-region', }); }); + it('return google login when no authProvider is specified', async () => { + mockedListClusters.mockReturnValueOnce([ + { + clusters: [ + { + name: 'some-cluster', + endpoint: '1.2.3.4', + }, + ], + }, + ]); + + const config: Config = new ConfigReader({ + type: 'gke', + projectId: 'some-project', + }); + + const sut = GkeClusterLocator.fromConfigWithClient(config, { + listClusters: mockedListClusters, + } as any); + + const result = await sut.getClusters(); + + expect(result).toStrictEqual([ + { + name: 'some-cluster', + url: 'https://1.2.3.4', + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, + skipTLSVerify: false, + skipMetricsLookup: false, + }, + ]); + }); + it('return googleServiceAccount login when authProvider is specified', async () => { + mockedListClusters.mockReturnValueOnce([ + { + clusters: [ + { + name: 'some-cluster', + endpoint: '1.2.3.4', + }, + ], + }, + ]); + + const config: Config = new ConfigReader({ + type: 'gke', + projectId: 'some-project', + authProvider: 'googleServiceAccount', + }); + + const sut = GkeClusterLocator.fromConfigWithClient(config, { + listClusters: mockedListClusters, + } as any); + + const result = await sut.getClusters(); + + expect(result).toStrictEqual([ + { + name: 'some-cluster', + url: 'https://1.2.3.4', + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'googleServiceAccount', + }, + skipTLSVerify: false, + skipMetricsLookup: false, + }, + ]); + }); + it('return google login when authProvider property has invalid value', async () => { + mockedListClusters.mockReturnValueOnce([ + { + clusters: [ + { + name: 'some-cluster', + endpoint: '1.2.3.4', + }, + ], + }, + ]); + + const config: Config = new ConfigReader({ + type: 'gke', + projectId: 'some-project', + authProvider: 'differentValue', + }); + + const sut = GkeClusterLocator.fromConfigWithClient(config, { + listClusters: mockedListClusters, + } as any); + + const result = await sut.getClusters(); + + expect(result).toStrictEqual([ + { + name: 'some-cluster', + url: 'https://1.2.3.4', + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, + skipTLSVerify: false, + skipMetricsLookup: false, + }, + ]); + }); }); }); diff --git a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts index 74e7ebc9af..03f5bda103 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/GkeClusterLocator.ts @@ -29,6 +29,7 @@ interface MatchResourceLabelEntry { type GkeClusterLocatorOptions = { projectId: string; + authProvider: string; region?: string; skipTLSVerify?: boolean; skipMetricsLookup?: boolean; @@ -54,8 +55,14 @@ export class GkeClusterLocator implements KubernetesClustersSupplier { return { key: mrl.getString('key'), value: mrl.getString('value') }; }) ?? []; + const storeAuthProviderString = + config.getOptionalString('authProvider') === 'googleServiceAccount' + ? 'googleServiceAccount' + : 'google'; + const options = { projectId: config.getString('projectId'), + authProvider: storeAuthProviderString, region: config.getOptionalString('region') ?? '-', skipTLSVerify: config.getOptionalBoolean('skipTLSVerify') ?? false, skipMetricsLookup: @@ -97,6 +104,7 @@ export class GkeClusterLocator implements KubernetesClustersSupplier { const { projectId, region, + authProvider, skipTLSVerify, skipMetricsLookup, exposeDashboard, @@ -121,7 +129,7 @@ export class GkeClusterLocator implements KubernetesClustersSupplier { // TODO filter out clusters which don't have name or endpoint name: r.name ?? 'unknown', url: `https://${r.endpoint ?? ''}`, - authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: authProvider }, skipTLSVerify, skipMetricsLookup, ...(exposeDashboard diff --git a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts index ded0faac5b..beb9fc9cfe 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/index.test.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/index.test.ts @@ -53,6 +53,7 @@ describe('getCombinedClusterSupplier', () => { const mockStrategy: jest.Mocked = { getCredential: jest.fn(), validateCluster: jest.fn().mockReturnValue([]), + presentAuthMetadata: jest.fn(), }; const clusterSupplier = getCombinedClusterSupplier( diff --git a/plugins/kubernetes-backend/src/plugin.ts b/plugins/kubernetes-backend/src/plugin.ts index 83dbae15a0..1b4aca4160 100644 --- a/plugins/kubernetes-backend/src/plugin.ts +++ b/plugins/kubernetes-backend/src/plugin.ts @@ -26,6 +26,20 @@ import { KubernetesObjectsProviderExtensionPoint, kubernetesObjectsProviderExtensionPoint, KubernetesObjectsProvider, + KubernetesClusterSupplierExtensionPoint, + kubernetesClusterSupplierExtensionPoint, + KubernetesClustersSupplier, + KubernetesAuthStrategyExtensionPoint, + AuthenticationStrategy, + kubernetesAuthStrategyExtensionPoint, + KubernetesFetcher, + KubernetesServiceLocatorExtensionPoint, + KubernetesServiceLocator, + kubernetesServiceLocatorExtensionPoint, +} from '@backstage/plugin-kubernetes-node'; +import { + KubernetesFetcherExtensionPoint, + kubernetesFetcherExtensionPoint, } from '@backstage/plugin-kubernetes-node'; class ObjectsProvider implements KubernetesObjectsProviderExtensionPoint { @@ -45,6 +59,86 @@ class ObjectsProvider implements KubernetesObjectsProviderExtensionPoint { } } +class ClusterSuplier implements KubernetesClusterSupplierExtensionPoint { + private clusterSupplier: KubernetesClustersSupplier | undefined; + + getClusterSupplier() { + return this.clusterSupplier; + } + + addClusterSupplier(clusterSupplier: KubernetesClustersSupplier) { + if (this.clusterSupplier) { + throw new Error( + 'Multiple Kubernetes Cluster Suppliers is not supported at this time', + ); + } + this.clusterSupplier = clusterSupplier; + } +} + +class Fetcher implements KubernetesFetcherExtensionPoint { + private fetcher: KubernetesFetcher | undefined; + + getFetcher() { + return this.fetcher; + } + + addFetcher(fetcher: KubernetesFetcher) { + if (this.fetcher) { + throw new Error( + 'Multiple Kubernetes Fetchers is not supported at this time', + ); + } + this.fetcher = fetcher; + } +} + +class ServiceLocator implements KubernetesServiceLocatorExtensionPoint { + private serviceLocator: KubernetesServiceLocator | undefined; + + getServiceLocator() { + return this.serviceLocator; + } + + addServiceLocator(serviceLocator: KubernetesServiceLocator) { + if (this.serviceLocator) { + throw new Error( + 'Multiple Kubernetes Service Locators is not supported at this time', + ); + } + this.serviceLocator = serviceLocator; + } +} + +class AuthStrategy implements KubernetesAuthStrategyExtensionPoint { + private authStrategies: Array<{ + key: string; + strategy: AuthenticationStrategy; + }>; + + constructor() { + this.authStrategies = new Array<{ + key: string; + strategy: AuthenticationStrategy; + }>(); + } + + static addAuthStrategiesFromArray( + authStrategies: Array<{ key: string; strategy: AuthenticationStrategy }>, + builder: KubernetesBuilder, + ) { + authStrategies.forEach(st => builder.addAuthStrategy(st.key, st.strategy)); + } + + getAuthenticationStrategies() { + return this.authStrategies; + } + + addAuthStrategy(key: string, authStrategy: AuthenticationStrategy) { + this.authStrategies.push({ key, strategy: authStrategy }); + } +} + /** * This is the backend plugin that provides the Kubernetes integration. * @alpha @@ -53,10 +147,31 @@ class ObjectsProvider implements KubernetesObjectsProviderExtensionPoint { export const kubernetesPlugin = createBackendPlugin({ pluginId: 'kubernetes', register(env) { - const extensionPoint = new ObjectsProvider(); + const extPointObjectsProvider = new ObjectsProvider(); + const extPointClusterSuplier = new ClusterSuplier(); + const extPointAuthStrategy = new AuthStrategy(); + const extPointFetcher = new Fetcher(); + const extPointServiceLocator = new ServiceLocator(); + env.registerExtensionPoint( kubernetesObjectsProviderExtensionPoint, - extensionPoint, + extPointObjectsProvider, + ); + env.registerExtensionPoint( + kubernetesClusterSupplierExtensionPoint, + extPointClusterSuplier, + ); + env.registerExtensionPoint( + kubernetesAuthStrategyExtensionPoint, + extPointAuthStrategy, + ); + env.registerExtensionPoint( + kubernetesFetcherExtensionPoint, + extPointFetcher, + ); + env.registerExtensionPoint( + kubernetesServiceLocatorExtensionPoint, + extPointServiceLocator, ); env.registerInit({ @@ -70,14 +185,22 @@ export const kubernetesPlugin = createBackendPlugin({ async init({ http, logger, config, catalogApi, permissions }) { const winstonLogger = loggerToWinstonLogger(logger); // TODO: expose all of the customization & extension points of the builder here - const { router } = await KubernetesBuilder.createBuilder({ + const builder: KubernetesBuilder = KubernetesBuilder.createBuilder({ logger: winstonLogger, config, catalogApi, permissions, }) - .setObjectsProvider(extensionPoint.getObjectsProvider()) - .build(); + .setObjectsProvider(extPointObjectsProvider.getObjectsProvider()) + .setClusterSupplier(extPointClusterSuplier.getClusterSupplier()) + .setFetcher(extPointFetcher.getFetcher()) + .setServiceLocator(extPointServiceLocator.getServiceLocator()); + + AuthStrategy.addAuthStrategiesFromArray( + extPointAuthStrategy.getAuthenticationStrategies(), + builder, + ); + const { router } = await builder.build(); http.use(router); }, }); diff --git a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts index b8254d781c..6d69606316 100644 --- a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts +++ b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts @@ -107,7 +107,7 @@ describe('resourcesRoutes', () => { import('@backstage/plugin-kubernetes-backend/alpha'), createBackendModule({ pluginId: 'kubernetes', - moduleId: 'testObjectsProvider', + moduleId: 'test-objects-provider', register(env) { env.registerInit({ deps: { extension: kubernetesObjectsProviderExtensionPoint }, diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index c676cc6246..5d19c6d94d 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -14,106 +14,157 @@ * limitations under the License. */ -import { getVoidLogger } from '@backstage/backend-common'; -import { Entity } from '@backstage/catalog-model'; -import { Config, ConfigReader } from '@backstage/config'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, + ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE, + ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID, + ANNOTATION_KUBERNETES_DASHBOARD_URL, ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER, - ObjectsByEntityResponse, KubernetesRequestAuth, } from '@backstage/plugin-kubernetes-common'; -import express from 'express'; import request from 'supertest'; import { ClusterDetails, - FetchResponseWrapper, - KubernetesClustersSupplier, KubernetesFetcher, KubernetesServiceLocator, - ObjectFetchParams, } from '../types/types'; import { KubernetesCredential } from '../auth/types'; -import { KubernetesBuilder } from './KubernetesBuilder'; -import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; -import { CatalogApi } from '@backstage/catalog-client'; import { HEADER_KUBERNETES_CLUSTER, HEADER_KUBERNETES_AUTH, } from './KubernetesProxy'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; -import { rest } from 'msw'; import { - AuthorizeResult, - PermissionEvaluator, -} from '@backstage/plugin-permission-common'; + ServiceMock, + mockServices, + setupRequestMockHandlers, + startTestBackend, +} from '@backstage/backend-test-utils'; +import { rest } from 'msw'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; +import { + PermissionsService, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { + AuthMetadata, + KubernetesObjectsProvider, + kubernetesAuthStrategyExtensionPoint, + kubernetesClusterSupplierExtensionPoint, + kubernetesObjectsProviderExtensionPoint, + kubernetesFetcherExtensionPoint, + kubernetesServiceLocatorExtensionPoint, +} from '@backstage/plugin-kubernetes-node'; +import { ExtendedHttpServer } from '@backstage/backend-app-api'; -describe('KubernetesBuilder', () => { - let app: express.Express; - let kubernetesFanOutHandler: jest.Mocked; - let config: Config; - let catalogApi: CatalogApi; - let permissions: jest.Mocked; - - beforeEach(async () => { - jest.resetAllMocks(); - const logger = getVoidLogger(); - config = new ConfigReader({ +describe('API integration tests', () => { + let app: ExtendedHttpServer; + let objectsProviderMock: jest.Mocked; + const happyK8SResult = { + items: [{ clusterOne: { pods: [{ metadata: { name: 'pod1' } }] } }], + }; + const permissionsMock: ServiceMock = + mockServices.permissions.mock({ + authorize: jest.fn(), + authorizeConditional: jest.fn(), + }); + const minimalValidConfigService = mockServices.rootConfig.factory({ + data: { kubernetes: { serviceLocatorMethod: { type: 'multiTenant' }, - clusterLocatorMethods: [{ type: 'config', clusters: [] }], + clusterLocatorMethods: [], + }, + }, + }); + const withClusters = (clusters: ClusterDetails[]) => + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testClusterSupplier', + register(env) { + env.registerInit({ + deps: { extension: kubernetesClusterSupplierExtensionPoint }, + async init({ extension }) { + extension.addClusterSupplier({ + getClusters: jest.fn().mockResolvedValue(clusters), + }); + }, + }); }, }); - const clusters: ClusterDetails[] = [ - { - name: 'some-cluster', - url: 'https://localhost:1234', - authMetadata: { - [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'serviceAccount', - }, - }, - { - name: 'some-other-cluster', - url: 'https://localhost:1235', - authMetadata: { - [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'oidc', - [ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]: 'google', - }, - }, - ]; - const clusterSupplier: KubernetesClustersSupplier = { - async getClusters() { - return clusters; - }, + beforeEach(async () => { + jest.resetAllMocks(); + + objectsProviderMock = { + getKubernetesObjectsByEntity: jest.fn().mockResolvedValue(happyK8SResult), + getCustomResourcesByEntity: jest.fn().mockResolvedValue(happyK8SResult), }; - kubernetesFanOutHandler = { - getKubernetesObjectsByEntity: jest.fn(), - } as any; + jest.mock('@backstage/catalog-client', () => ({ + CatalogClient: jest.fn().mockReturnValue({ + getEntityByRef: jest.fn().mockImplementation(async entityRef => { + if (entityRef.name === 'noentity') { + return undefined; + } + return { + kind: entityRef.kind, + metadata: { + name: entityRef.name, + namespace: entityRef.namespace, + }, + }; + }), + }), + })); - permissions = { - authorize: jest.fn(), - authorizeConditional: jest.fn(), - }; + const { server } = await startTestBackend({ + features: [ + minimalValidConfigService, + import('@backstage/plugin-kubernetes-backend/alpha'), + import('@backstage/plugin-permission-backend/alpha'), + import('@backstage/plugin-permission-backend-module-allow-all-policy'), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testObjectsProvider', + register(env) { + env.registerInit({ + deps: { extension: kubernetesObjectsProviderExtensionPoint }, + async init({ extension }) { + extension.addObjectsProvider(objectsProviderMock); + }, + }); + }, + }), + withClusters([ + { + name: 'some-cluster', + url: 'https://localhost:1234', + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'serviceAccount', + }, + }, + { + name: 'some-other-cluster', + url: 'https://localhost:1235', + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'oidc', + [ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]: 'google', + }, + }, + ]), + ], + }); - const { router } = await KubernetesBuilder.createBuilder({ - config, - logger, - catalogApi, - permissions, - }) - .setObjectsProvider(kubernetesFanOutHandler) - .setClusterSupplier(clusterSupplier) - .build(); + app = server; + }); - app = express().use(router); + afterEach(() => { + app.stop(); }); describe('get /clusters', () => { it('happy path: lists clusters', async () => { - const response = await request(app).get('/clusters'); + const response = await request(app).get('/api/kubernetes/clusters'); expect(response.status).toEqual(200); expect(response.body).toStrictEqual({ @@ -130,73 +181,138 @@ describe('KubernetesBuilder', () => { ], }); }); - }); - describe('post /services/:serviceId', () => { - it('happy path: lists kubernetes objects without auth in request body', async () => { - const result = { - clusterOne: { - pods: [ + + it('happy path: lists clusters with custom AuthStrategy and custom auth metadata', async () => { + const { server } = await startTestBackend({ + features: [ + minimalValidConfigService, + import('@backstage/plugin-kubernetes-backend/alpha'), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testObjectsProvider', + register(env) { + env.registerInit({ + deps: { extension: kubernetesObjectsProviderExtensionPoint }, + async init({ extension }) { + extension.addObjectsProvider(objectsProviderMock); + }, + }); + }, + }), + withClusters([ { - metadata: { - name: 'pod1', + name: 'some-cluster', + url: 'https://localhost:1234', + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'customAuth', + [ANNOTATION_KUBERNETES_DASHBOARD_URL]: + 'https://127.0.0.1:8443/dashboard', + [ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID]: '12650152165654', + [ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE]: 'my_aws_role', }, }, - ], - }, - } as any; - kubernetesFanOutHandler.getKubernetesObjectsByEntity.mockReturnValueOnce( - Promise.resolve(result), - ); + ]), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testAuthStrategy', + register(env) { + env.registerInit({ + deps: { extension: kubernetesAuthStrategyExtensionPoint }, + async init({ extension }) { + extension.addAuthStrategy('customAuth', { + getCredential: jest + .fn< + Promise, + [ClusterDetails, KubernetesRequestAuth] + >() + .mockImplementation(async (_, requestAuth) => ({ + type: 'bearer token', + token: requestAuth.custom as string, + })), + validateCluster: jest.fn().mockReturnValue([]), + presentAuthMetadata: ( + authMetadata: AuthMetadata, + ): AuthMetadata => { + const authMetadataFilter = Object.entries(authMetadata) + .filter(([key, _value]) => { + return [ + ANNOTATION_KUBERNETES_DASHBOARD_URL, + ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE, + ].includes(key); + }) + .reduce( + ( + accumulator: AuthMetadata, + currentValue: [string, string], + ) => { + accumulator[currentValue[0]] = currentValue[1]; + return accumulator; + }, + {}, + ); - const response = await request(app).post('/services/test-service'); + return authMetadataFilter; + }, + }); + }, + }); + }, + }), + ], + }); + app = server; + + const response = await request(app).get('/api/kubernetes/clusters'); expect(response.status).toEqual(200); - expect(response.body).toEqual(result); + expect(response.body).toStrictEqual({ + items: [ + { + name: 'some-cluster', + authProvider: 'customAuth', + auth: { + 'kubernetes.io/aws-assume-role': 'my_aws_role', + 'kubernetes.io/dashboard-url': 'https://127.0.0.1:8443/dashboard', + }, + }, + ], + }); + }); + }); + + describe('post /services/:serviceId', () => { + it('happy path: lists kubernetes objects without auth in request body', async () => { + const response = await request(app).post( + '/api/kubernetes/services/test-service', + ); + expect(response.status).toEqual(200); + expect(response.body).toEqual(happyK8SResult); }); it('happy path: lists kubernetes objects with auth in request body', async () => { - const result = { - clusterOne: { - pods: [ - { - metadata: { - name: 'pod1', - }, - }, - ], - }, - } as any; - kubernetesFanOutHandler.getKubernetesObjectsByEntity.mockReturnValueOnce( - Promise.resolve(result), - ); - const response = await request(app) - .post('/services/test-service') - .send({ - auth: { - google: 'google_token_123', - }, - }) + .post('/api/kubernetes/services/test-service') + .send({ auth: { google: 'google_token_123' } }) .set('Content-Type', 'application/json'); - expect(response.status).toEqual(200); - expect(response.body).toEqual(result); + expect(response.body).toEqual(happyK8SResult); }); it('internal error: lists kubernetes objects', async () => { - kubernetesFanOutHandler.getKubernetesObjectsByEntity.mockRejectedValue( + objectsProviderMock.getKubernetesObjectsByEntity.mockRejectedValue( Error('some internal error'), ); - const response = await request(app).post('/services/test-service'); + const response = await request(app).post( + '/api/kubernetes/services/test-service', + ); expect(response.status).toEqual(500); expect(response.body).toEqual({ error: 'some internal error' }); }); it('custom service locator', async () => { - const logger = getVoidLogger(); - const someCluster: ClusterDetails = { + const someCluster = { name: 'some-cluster', url: 'https://localhost:1234', authMetadata: { @@ -212,95 +328,76 @@ describe('KubernetesBuilder', () => { authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'google' }, }, ]; - const clusterSupplier: KubernetesClustersSupplier = { - async getClusters() { - return clusters; - }, + + const pod = { metadata: { name: 'pod1' } }; + + const mockServiceLocator: jest.Mocked = { + getClustersByEntity: jest.fn().mockResolvedValue({ + clusters: [someCluster], + }), }; - const pod = { - metadata: { - name: 'pod1', - }, + + const mockFetcher: jest.Mocked = { + fetchPodMetricsByNamespaces: jest + .fn() + .mockResolvedValue({ errors: [], responses: [] }), + fetchObjectsForService: jest.fn().mockResolvedValue({ + errors: [], + responses: [{ type: 'pods', resources: [pod] }], + }), }; - const result: ObjectsByEntityResponse = { - items: [ - { - cluster: { - name: someCluster.name, + + const { server } = await startTestBackend({ + features: [ + minimalValidConfigService, + import('@backstage/plugin-kubernetes-backend/alpha'), + withClusters(clusters), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testFetcher', + register(env) { + env.registerInit({ + deps: { extension: kubernetesFetcherExtensionPoint }, + async init({ extension }) { + extension.addFetcher(mockFetcher); + }, + }); }, - errors: [], - podMetrics: [], - resources: [ - { - type: 'pods', - resources: [pod], - }, - ], - }, + }), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testServiceLocator', + register(env) { + env.registerInit({ + deps: { extension: kubernetesServiceLocatorExtensionPoint }, + async init({ extension }) { + extension.addServiceLocator(mockServiceLocator); + }, + }); + }, + }), ], - }; - - const serviceLocator: KubernetesServiceLocator = { - getClustersByEntity( - _entity: Entity, - ): Promise<{ clusters: ClusterDetails[] }> { - return Promise.resolve({ clusters: [someCluster] }); - }, - }; - - const fetcher: KubernetesFetcher = { - fetchPodMetricsByNamespaces( - _clusterDetails: ClusterDetails, - _credential: KubernetesCredential, - _namespaces: Set, - ): Promise { - return Promise.resolve({ errors: [], responses: [] }); - }, - fetchObjectsForService( - _params: ObjectFetchParams, - ): Promise { - return Promise.resolve({ - errors: [], - responses: [ - { - type: 'pods', - resources: [pod], - }, - ], - }); - }, - }; - - const { router } = await KubernetesBuilder.createBuilder({ - logger, - config, - catalogApi, - permissions, - }) - .setClusterSupplier(clusterSupplier) - .setServiceLocator(serviceLocator) - .setFetcher(fetcher) - .build(); - app = express().use(router); + }); + app = server; const response = await request(app) - .post('/services/test-service') - .send({ - entity: { - metadata: { - name: 'thing', - }, - }, - }); + .post('/api/kubernetes/services/test-service') + .send({ entity: { metadata: { name: 'thing' } } }); - expect(response.body).toEqual(result); + expect(response.body).toEqual({ + items: [ + { + cluster: { name: someCluster.name }, + errors: [], + podMetrics: [], + resources: [{ type: 'pods', resources: [pod] }], + }, + ], + }); expect(response.status).toEqual(200); }); it('reads auth data for custom strategy', async () => { - permissions.authorize.mockResolvedValue([ - { result: AuthorizeResult.ALLOW }, - ]); const mockFetcher = { fetchPodMetricsByNamespaces: jest .fn() @@ -312,49 +409,65 @@ describe('KubernetesBuilder', () => { ], }), }; - const { router } = await KubernetesBuilder.createBuilder({ - logger: getVoidLogger(), - config, - catalogApi, - permissions, - }) - .addAuthStrategy('custom', { - getCredential: jest - .fn< - Promise, - [ClusterDetails, KubernetesRequestAuth] - >() - .mockImplementation(async (_, requestAuth) => ({ - type: 'bearer token', - token: requestAuth.custom as string, - })), - validateCluster: jest.fn().mockReturnValue([]), - }) - .setClusterSupplier({ - getClusters: jest - .fn, []>() - .mockResolvedValue([ - { - name: 'custom-cluster', - url: 'http://my.cluster.url', - authMetadata: { - [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'custom', - }, + + const { server } = await startTestBackend({ + features: [ + minimalValidConfigService, + import('@backstage/plugin-kubernetes-backend/alpha'), + withClusters([ + { + name: 'custom-cluster', + url: 'http://my.cluster.url', + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'custom', }, - ]), - }) - .setFetcher(mockFetcher) - .build(); - app = express().use(router); + }, + ]), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testAuthStrategy', + register(env) { + env.registerInit({ + deps: { extension: kubernetesAuthStrategyExtensionPoint }, + async init({ extension }) { + extension.addAuthStrategy('custom', { + getCredential: jest + .fn< + Promise, + [ClusterDetails, KubernetesRequestAuth] + >() + .mockImplementation(async (_, requestAuth) => ({ + type: 'bearer token', + token: requestAuth.custom as string, + })), + validateCluster: jest.fn().mockReturnValue([]), + presentAuthMetadata: jest.fn().mockReturnValue({}), + }); + }, + }); + }, + }), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testFetcher', + register(env) { + env.registerInit({ + deps: { extension: kubernetesFetcherExtensionPoint }, + async init({ extension }) { + extension.addFetcher(mockFetcher); + }, + }); + }, + }), + ], + }); + + app = server; await request(app) - .post('/services/test-service') + .post('/api/kubernetes/services/test-service') .send({ - entity: { - metadata: { - name: 'thing', - }, - }, + entity: { metadata: { name: 'thing' } }, auth: { custom: 'custom-token' }, }); @@ -391,75 +504,68 @@ describe('KubernetesBuilder', () => { ); }); - it('returns the given request body with permission set to allow', async () => { - const requestBody = { + it('forwards request body to k8s', async () => { + const namespaceManifest = { kind: 'Namespace', apiVersion: 'v1', - metadata: { - name: 'new-ns', - }, + metadata: { name: 'new-ns' }, }; - permissions.authorize.mockReturnValue( - Promise.resolve([{ result: AuthorizeResult.ALLOW }]), - ); - const proxyEndpointRequest = request(app) - .post('/proxy/api/v1/namespaces') + .post('/api/kubernetes/proxy/api/v1/namespaces') .set(HEADER_KUBERNETES_CLUSTER, 'some-cluster') .set(HEADER_KUBERNETES_AUTH, 'randomtoken') - .send(requestBody); - + .send(namespaceManifest); worker.use(rest.all(proxyEndpointRequest.url, req => req.passthrough())); - const response = await proxyEndpointRequest; - expect(response.body).toStrictEqual(requestBody); + expect(response.body).toStrictEqual(namespaceManifest); }); - it('supports yaml content type with permission set to allow', async () => { - const requestBody = `--- + it('supports yaml content type', async () => { + const yamlManifest = `--- kind: Namespace apiVersion: v1 metadata: name: new-ns `; - permissions.authorize.mockReturnValue( - Promise.resolve([{ result: AuthorizeResult.ALLOW }]), - ); - const proxyEndpointRequest = request(app) - .post('/proxy/api/v1/namespaces') + .post('/api/kubernetes/proxy/api/v1/namespaces') .set(HEADER_KUBERNETES_CLUSTER, 'some-cluster') .set(HEADER_KUBERNETES_AUTH, 'randomtoken') .set('content-type', 'application/yaml') - .send(requestBody); + .send(yamlManifest); worker.use(rest.all(proxyEndpointRequest.url, req => req.passthrough())); const response = await proxyEndpointRequest; - expect(response.text).toEqual(requestBody); + expect(response.text).toEqual(yamlManifest); }); - it('returns a 403 response if Permission Policy is in place that blocks endpoint', async () => { - const requestBody = { - kind: 'Namespace', - apiVersion: 'v1', - metadata: { - name: 'new-ns', - }, - }; + it('returns 403 response when permission blocks endpoint', async () => { + permissionsMock.authorize.mockResolvedValue([ + { result: AuthorizeResult.DENY }, + ]); - permissions.authorize.mockReturnValue( - Promise.resolve([{ result: AuthorizeResult.DENY }]), - ); + const { server } = await startTestBackend({ + features: [ + minimalValidConfigService, + permissionsMock.factory, + import('@backstage/plugin-kubernetes-backend/alpha'), + ], + }); + app = server; const proxyEndpointRequest = request(app) - .post('/proxy/api/v1/namespaces') + .post('/api/kubernetes/proxy/api/v1/namespaces') .set(HEADER_KUBERNETES_CLUSTER, 'some-cluster') .set(HEADER_KUBERNETES_AUTH, 'randomtoken') - .send(requestBody); + .send({ + kind: 'Namespace', + apiVersion: 'v1', + metadata: { name: 'new-ns' }, + }); worker.use(rest.all(proxyEndpointRequest.url, req => req.passthrough())); @@ -477,42 +583,44 @@ metadata: return res(ctx.json({ items: [] })); }), ); - permissions.authorize.mockResolvedValue([ - { result: AuthorizeResult.ALLOW }, - ]); - const { router } = await KubernetesBuilder.createBuilder({ - logger: getVoidLogger(), - config, - catalogApi, - permissions, - }) - .addAuthStrategy('custom', { - getCredential: jest - .fn< - Promise, - [ClusterDetails, KubernetesRequestAuth] - >() - .mockResolvedValue({ type: 'anonymous' }), - validateCluster: jest.fn().mockReturnValue([]), - }) - .setClusterSupplier({ - getClusters: jest - .fn, []>() - .mockResolvedValue([ - { - name: 'custom-cluster', - url: 'http://my.cluster.url', - authMetadata: { - [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'custom', - }, - }, - ]), - }) - .build(); - app = express().use(router); - const proxyEndpointRequest = request(app) - .get('/proxy/api/v1/namespaces') + const { server } = await startTestBackend({ + features: [ + minimalValidConfigService, + import('@backstage/plugin-kubernetes-backend/alpha'), + withClusters([ + { + name: 'custom-cluster', + url: 'http://my.cluster.url', + authMetadata: { [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'custom' }, + }, + ]), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testAuthStrategy', + register(env) { + env.registerInit({ + deps: { extension: kubernetesAuthStrategyExtensionPoint }, + async init({ extension }) { + extension.addAuthStrategy('custom', { + getCredential: jest + .fn< + Promise, + [ClusterDetails, KubernetesRequestAuth] + >() + .mockResolvedValue({ type: 'anonymous' }), + validateCluster: jest.fn().mockReturnValue([]), + presentAuthMetadata: jest.fn().mockReturnValue({}), + }); + }, + }); + }, + }), + ], + }); + + const proxyEndpointRequest = request(server) + .get('/api/kubernetes/proxy/api/v1/namespaces') .set(HEADER_KUBERNETES_CLUSTER, 'custom-cluster') .set(HEADER_KUBERNETES_AUTH, 'custom-token'); worker.use(rest.all(proxyEndpointRequest.url, req => req.passthrough())); @@ -521,43 +629,138 @@ metadata: expect(response.body).toStrictEqual({ items: [] }); }); - it('should not permit custom auth strategies with dashes', async () => { - const throwError = () => - KubernetesBuilder.createBuilder({ - logger: getVoidLogger(), - config, - catalogApi, - permissions, - }).addAuthStrategy('custom-strategy', { - getCredential: jest - .fn< - Promise, - [ClusterDetails, KubernetesRequestAuth] - >() - .mockResolvedValue({ type: 'anonymous' }), - validateCluster: jest.fn().mockReturnValue([]), - }); + it('reads custom auth metadata from config', async () => { + const authStrategy = { + getCredential: jest.fn().mockResolvedValue({ type: 'anonymous' }), + validateCluster: jest.fn().mockReturnValue([]), + presentAuthMetadata: jest.fn().mockReturnValue({}), + }; + worker.use( + rest.get('http://my.cluster/api', (_req, res, ctx) => + res(ctx.json({})), + ), + ); + const { server } = await startTestBackend({ + features: [ + mockServices.rootConfig.factory({ + data: { + kubernetes: { + serviceLocatorMethod: { type: 'multiTenant' }, + clusterLocatorMethods: [ + { + type: 'config', + clusters: [ + { + name: 'cluster', + url: 'http://my.cluster', + authProvider: 'custom', + authMetadata: { 'custom-key': 'custom-value' }, + }, + ], + }, + ], + }, + }, + }), + import('@backstage/plugin-kubernetes-backend/alpha'), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testAuthStrategy', + register(env) { + env.registerInit({ + deps: { extension: kubernetesAuthStrategyExtensionPoint }, + async init({ extension }) { + extension.addAuthStrategy('custom', authStrategy); + }, + }); + }, + }), + ], + }); + app = server; - expect(throwError).toThrow('Strategy name can not include dashes'); + const proxyEndpointRequest = request(app).get( + '/api/kubernetes/proxy/api', + ); + worker.use(rest.all(proxyEndpointRequest.url, req => req.passthrough())); + const response = await proxyEndpointRequest; + + expect(response.body).toStrictEqual({}); + expect(authStrategy.getCredential).toHaveBeenCalledWith( + expect.objectContaining({ + authMetadata: expect.objectContaining({ + 'custom-key': 'custom-value', + }), + }), + expect.anything(), + ); }); }); - describe('get /.well-known/backstage/permissions/metadata', () => { - it('lists permissions supported by the kubernetes plugin', async () => { - const response = await request(app).get( - '/.well-known/backstage/permissions/metadata', - ); - expect(response.status).toEqual(200); - expect(response.body).toMatchObject({ - permissions: [ - { - type: 'basic', - name: 'kubernetes.proxy', - attributes: {}, - }, + it('forbids custom auth strategies with dashes', () => { + const throwError = () => + startTestBackend({ + features: [ + import('@backstage/plugin-kubernetes-backend/alpha'), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testAuthStrategy', + register(env) { + env.registerInit({ + deps: { extension: kubernetesAuthStrategyExtensionPoint }, + async init({ extension }) { + extension.addAuthStrategy('custom-strategy', { + getCredential: jest + .fn< + Promise, + [ClusterDetails, KubernetesRequestAuth] + >() + .mockResolvedValue({ type: 'anonymous' }), + validateCluster: jest.fn().mockReturnValue([]), + presentAuthMetadata: jest.fn().mockReturnValue({}), + }); + }, + }); + }, + }), ], - rules: [], }); + return expect(throwError).rejects.toThrow( + 'Strategy name can not include dashes', + ); + }); + + it('serves permission integration endpoint', async () => { + const response = await request(app).get( + '/api/kubernetes/.well-known/backstage/permissions/metadata', + ); + + expect(response.status).toEqual(200); + expect(response.body).toMatchObject({ + permissions: [ + { type: 'basic', name: 'kubernetes.proxy', attributes: {} }, + ], + rules: [], }); }); + + it('fails when an unsupported serviceLocator type is specified', () => { + return expect(() => + startTestBackend({ + features: [ + mockServices.rootConfig.factory({ + data: { + kubernetes: { + serviceLocatorMethod: { type: 'unsupported' }, + clusterLocatorMethods: [{ type: 'config', clusters: [] }], + }, + }, + }), + import('@backstage/plugin-kubernetes-backend/alpha'), + ], + }), + ).rejects.toThrow( + 'Unsupported kubernetes.serviceLocatorMethod "unsupported"', + ); + }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts index 3bc7402179..81cc3d471d 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts @@ -29,7 +29,6 @@ import { Logger } from 'winston'; import { getCombinedClusterSupplier } from '../cluster-locator'; import { - AuthenticationStrategy, AnonymousStrategy, DispatchStrategy, GoogleStrategy, @@ -45,16 +44,20 @@ import { addResourceRoutesToRouter } from '../routes/resourcesRoutes'; import { MultiTenantServiceLocator } from '../service-locator/MultiTenantServiceLocator'; import { SingleTenantServiceLocator } from '../service-locator/SingleTenantServiceLocator'; import { - CustomResource, - KubernetesClustersSupplier, - KubernetesFetcher, KubernetesObjectsProviderOptions, - KubernetesObjectTypes, - KubernetesServiceLocator, ObjectsByEntityRequest, ServiceLocatorMethod, } from '../types/types'; -import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node'; +import { + AuthenticationStrategy, + AuthMetadata, + CustomResource, + KubernetesClustersSupplier, + KubernetesFetcher, + KubernetesObjectsProvider, + KubernetesObjectTypes, + KubernetesServiceLocator, +} from '@backstage/plugin-kubernetes-node'; import { DEFAULT_OBJECTS, KubernetesFanOutHandler, @@ -286,7 +289,7 @@ export class KubernetesBuilder { break; default: throw new Error( - `Unsupported kubernetes.clusterLocatorMethod "${method}"`, + `Unsupported kubernetes.serviceLocatorMethod "${method}"`, ); } @@ -367,11 +370,20 @@ export class KubernetesBuilder { items: clusterDetails.map(cd => { const oidcTokenProvider = cd.authMetadata[ANNOTATION_KUBERNETES_OIDC_TOKEN_PROVIDER]; + const authProvider = + cd.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER]; + const strategy = this.getAuthStrategyMap()[authProvider]; + let auth: AuthMetadata = {}; + if (strategy) { + auth = strategy.presentAuthMetadata(cd.authMetadata); + } + return { name: cd.name, dashboardUrl: cd.dashboardUrl, - authProvider: cd.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER], + authProvider, ...(oidcTokenProvider && { oidcTokenProvider }), + ...(auth && Object.keys(auth).length !== 0 && { auth }), }; }), }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index 1faf2f0673..a50d0b37a2 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -196,6 +196,7 @@ describe('KubernetesFanOutHandler', () => { >() .mockResolvedValue({ type: 'anonymous' }), validateCluster: jest.fn().mockReturnValue([]), + presentAuthMetadata: jest.fn(), }, config, }); @@ -1147,6 +1148,7 @@ describe('KubernetesFanOutHandler', () => { >() .mockResolvedValue({ type: 'bearer token', token: 'token' }), validateCluster: jest.fn().mockReturnValue([]), + presentAuthMetadata: jest.fn(), }, config, }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 3ea06e061e..7a8ff1bf2d 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -17,7 +17,6 @@ import { Entity } from '@backstage/catalog-model'; import { Logger } from 'winston'; import { - ClusterDetails, KubernetesFetcher, KubernetesObjectsProviderOptions, KubernetesServiceLocator, @@ -26,7 +25,6 @@ import { ObjectToFetch, CustomResource, } from '../types/types'; -import { AuthenticationStrategy, KubernetesCredential } from '../auth/types'; import { ClientContainerStatus, ClientCurrentResourceUsage, @@ -45,7 +43,10 @@ import { PodStatus, } from '@kubernetes/client-node'; import { + AuthenticationStrategy, + ClusterDetails, CustomResourcesByEntity, + KubernetesCredential, KubernetesObjectsByEntity, } from '@backstage/plugin-kubernetes-node'; diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index e388589e74..3b435fad74 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -812,6 +812,129 @@ describe('KubernetesFetcher', () => { const [[{ agent }]] = httpsRequest.mock.calls; expect(agent.options.rejectUnauthorized).toBe(false); }); + + it('fetchObjectsForService authenticates with k8s using x509 client cert from authentication strategy', async () => { + worker.use( + rest.get('https://localhost:9999/api/v1/pods', (req, res, ctx) => + res( + checkToken(req, ctx, 'token'), + withLabels(req, ctx, { + items: [{ metadata: { name: 'pod-name' } }], + }), + ), + ), + ); + + const myCert = 'MOCKCert'; + const myKey = 'MOCKKey'; + + const result = sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'https://localhost:9999', + authMetadata: {}, + caData: 'MOCKCA', + }, + credential: { + type: 'x509 client certificate', + cert: myCert, + key: myKey, + }, + objectTypesToFetch: new Set([ + { + group: '', + apiVersion: 'v1', + plural: 'pods', + objectType: 'pods', + }, + ]), + labelSelector: '', + customResources: [], + }); + + await expect(result).rejects.toThrow(/PEM/); + + expect(httpsRequest).toHaveBeenCalledTimes(1); + const [[{ agent }]] = httpsRequest.mock.calls; + expect(agent.options.ca.toString('base64')).toMatch('MOCKCA'); + expect(agent.options.cert).toEqual(myCert); + expect(agent.options.key).toEqual(myKey); + }); + + it('fetchPodMetricsByNamespaces authenticates with k8s using x509 client cert from authentication strategy', async () => { + worker.use( + rest.get( + 'https://localhost:9999/api/v1/namespaces/:namespace/pods', + (req, res, ctx) => + res( + withLabels(req, ctx, { + items: [ + { + metadata: { name: 'pod-name' }, + spec: { + containers: [ + { + name: 'container-name', + resources: { + requests: { cpu: '500m', memory: '512M' }, + limits: { cpu: '1000m', memory: '1G' }, + }, + }, + ], + }, + }, + ], + }), + ), + ), + rest.get( + 'https://localhost:9999/apis/metrics.k8s.io/v1beta1/namespaces/:namespace/pods', + (req, res, ctx) => + res( + withLabels(req, ctx, { + items: [ + { + metadata: { name: 'pod-name' }, + containers: [ + { + name: 'container-name', + usage: { cpu: '0', memory: '0' }, + }, + ], + }, + ], + }), + ), + ), + ); + + const myCert = 'MOCKCert'; + const myKey = 'MOCKKey'; + + const result = sut.fetchPodMetricsByNamespaces( + { + name: 'cluster1', + url: 'https://localhost:9999', + authMetadata: {}, + caData: 'MOCKCA', + }, + { + type: 'x509 client certificate', + cert: myCert, + key: myKey, + }, + new Set(['ns-a']), + ); + + await expect(result).rejects.toThrow(/PEM/); + + expect(httpsRequest).toHaveBeenCalledTimes(2); + const [[{ agent }]] = httpsRequest.mock.calls; + expect(agent.options.ca.toString('base64')).toMatch('MOCKCA'); + expect(agent.options.cert).toEqual(myCert); + expect(agent.options.key).toEqual(myKey); + }); }); it('should use namespace if provided', async () => { @@ -895,7 +1018,7 @@ describe('KubernetesFetcher', () => { customResources: [], }); return expect(result).rejects.toThrow( - "no bearer token for cluster 'unauthenticated-cluster' and not running in Kubernetes", + "no bearer token or client cert for cluster 'unauthenticated-cluster' and not running in Kubernetes", ); }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index d5c945a864..b7354d54d4 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -26,12 +26,10 @@ import { import lodash, { Dictionary } from 'lodash'; import { Logger } from 'winston'; import { - ClusterDetails, FetchResponseWrapper, KubernetesFetcher, ObjectFetchParams, } from '../types/types'; -import { KubernetesCredential } from '../auth/types'; import { ANNOTATION_KUBERNETES_AUTH_PROVIDER, FetchResponse, @@ -43,6 +41,10 @@ import fetch, { RequestInit, Response } from 'node-fetch'; import * as https from 'https'; import fs from 'fs-extra'; import { JsonObject } from '@backstage/types'; +import { + ClusterDetails, + KubernetesCredential, +} from '@backstage/plugin-kubernetes-node'; export interface KubernetesClientBasedFetcherOptions { logger: Logger; @@ -214,21 +216,15 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { let requestInit: RequestInit; const authProvider = clusterDetails.authMetadata[ANNOTATION_KUBERNETES_AUTH_PROVIDER]; - if ( - authProvider === 'serviceAccount' && - !clusterDetails.authMetadata.serviceAccountToken && - fs.pathExistsSync(Config.SERVICEACCOUNT_CA_PATH) - ) { + + if (this.isServiceAccountAuthentication(authProvider, clusterDetails)) { [url, requestInit] = this.fetchArgsInCluster(credential); - } else if ( - credential.type === 'bearer token' || - authProvider === 'localKubectlProxy' - ) { + } else if (!this.isCredentialMissing(authProvider, credential)) { [url, requestInit] = this.fetchArgs(clusterDetails, credential); } else { return Promise.reject( new Error( - `no bearer token for cluster '${clusterDetails.name}' and not running in Kubernetes`, + `no bearer token or client cert for cluster '${clusterDetails.name}' and not running in Kubernetes`, ), ); } @@ -246,6 +242,26 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { return fetch(url, requestInit); } + private isServiceAccountAuthentication( + authProvider: string, + clusterDetails: ClusterDetails, + ) { + return ( + authProvider === 'serviceAccount' && + !clusterDetails.authMetadata.serviceAccountToken && + fs.pathExistsSync(Config.SERVICEACCOUNT_CA_PATH) + ); + } + + private isCredentialMissing( + authProvider: string, + credential: KubernetesCredential, + ) { + return ( + authProvider !== 'localKubectlProxy' && credential.type === 'anonymous' + ); + } + private fetchArgs( clusterDetails: ClusterDetails, credential: KubernetesCredential, @@ -270,6 +286,10 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { clusterDetails.caData, ) ?? undefined, rejectUnauthorized: !clusterDetails.skipTLSVerify, + ...(credential.type === 'x509 client certificate' && { + cert: credential.cert, + key: credential.key, + }), }); } return [url, requestInit]; diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts index 9bee21867d..f2eb61e7f9 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts @@ -131,6 +131,7 @@ describe('KubernetesProxy', () => { >() .mockResolvedValue({ type: 'anonymous' }), validateCluster: jest.fn(), + presentAuthMetadata: jest.fn(), }; proxy = new KubernetesProxy({ logger, clusterSupplier, authStrategy }); permissionApi.authorize.mockResolvedValue([ @@ -514,6 +515,7 @@ describe('KubernetesProxy', () => { .fn() .mockReturnValue({ type: 'bearer token', token: 'MY_TOKEN3' }), validateCluster: jest.fn(), + presentAuthMetadata: jest.fn(), }; proxy = new KubernetesProxy({ @@ -820,6 +822,67 @@ describe('KubernetesProxy', () => { expect(ca).toMatch('MOCKCA'); }); }); + + it('should use a x509 client cert authentication strategy to consume kubeapi when backstage-kubernetes-auth field is not provided and the authStrategy enables x509 client cert authentication', async () => { + worker.use( + rest.get( + 'https://localhost:9999/api/v1/namespaces', + (req: any, res: any, ctx: any) => { + if (req.headers.get('Authorization')) { + return res(ctx.status(403)); + } + + return res( + ctx.status(200), + ctx.json({ + kind: 'NamespaceList', + apiVersion: 'v1', + items: [], + }), + ); + }, + ), + ); + + clusterSupplier.getClusters.mockResolvedValue([ + { + name: 'cluster1', + url: 'https://localhost:9999', + authMetadata: {}, + }, + ]); + + const myCert = 'MOCKCert'; + const myKey = 'MOCKKey'; + + authStrategy.getCredential.mockResolvedValue({ + type: 'x509 client certificate', + cert: myCert, + key: myKey, + }); + + const requestPromise = setupProxyPromise({ + proxyPath: '/mountpath', + requestPath: '/api/v1/namespaces', + + headers: { [HEADER_KUBERNETES_CLUSTER]: 'cluster1' }, + }); + + const response = await requestPromise; + + expect(authStrategy.getCredential).toHaveBeenCalledTimes(1); + expect(authStrategy.getCredential).toHaveBeenCalledWith( + expect.anything(), + {}, + ); + + const [[{ key, cert }]] = httpsRequest.mock.calls; + expect(cert).toEqual(myCert); + expect(key).toEqual(myKey); + + // 500 Since the key and cert are fake + expect(response.status).toEqual(500); + }); }); describe('WebSocket', () => { diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts index 8a07267cde..209d01b703 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.ts @@ -113,24 +113,6 @@ export class KubernetesProxy { return; } - const authHeader = req.header(HEADER_KUBERNETES_AUTH); - if (authHeader) { - req.headers.authorization = authHeader; - } else { - // Map Backstage-Kubernetes-Authorization-X-X headers to a KubernetesRequestAuth object - const authObj = KubernetesProxy.authHeadersToKubernetesRequestAuth( - req.headers, - ); - - const credential = await this.getClusterForRequest(req).then(cd => { - return this.authStrategy.getCredential(cd, authObj); - }); - - if (credential.type === 'bearer token') { - req.headers.authorization = `Bearer ${credential.token}`; - } - } - const middleware = await this.getMiddleware(req); // If req is an upgrade handshake, use middleware upgrade instead of http request handler https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade @@ -173,7 +155,7 @@ export class KubernetesProxy { const cluster = await this.getClusterForRequest(req); const url = new URL(cluster.url); - return { + const target: any = { protocol: url.protocol, host: url.hostname, port: url.port, @@ -182,6 +164,29 @@ export class KubernetesProxy { cluster.caData, )?.toString(), }; + + const authHeader = req.header(HEADER_KUBERNETES_AUTH); + if (authHeader) { + req.headers.authorization = authHeader; + } else { + // Map Backstage-Kubernetes-Authorization-X-X headers to a KubernetesRequestAuth object + const authObj = KubernetesProxy.authHeadersToKubernetesRequestAuth( + req.headers, + ); + + const credential = await this.getClusterForRequest(req).then(cd => { + return this.authStrategy.getCredential(cd, authObj); + }); + + if (credential.type === 'bearer token') { + req.headers.authorization = `Bearer ${credential.token}`; + } else if (credential.type === 'x509 client certificate') { + target.key = credential.key; + target.cert = credential.cert; + } + } + + return target; }, onError: (error, req, res) => { const wrappedError = new ForwardedError( diff --git a/plugins/kubernetes-backend/src/types/index.ts b/plugins/kubernetes-backend/src/types/index.ts index c8413c1aa9..db229eae34 100644 --- a/plugins/kubernetes-backend/src/types/index.ts +++ b/plugins/kubernetes-backend/src/types/index.ts @@ -15,9 +15,3 @@ */ export * from './types'; - -export type { - CustomResourcesByEntity, - KubernetesObjectsByEntity, - KubernetesObjectsProvider, -} from '@backstage/plugin-kubernetes-node'; diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 6781dc1161..57a12bb58a 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -14,131 +14,10 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { Logger } from 'winston'; -import type { JsonObject } from '@backstage/types'; -import type { - CustomResourceMatcher, - FetchResponse, - KubernetesFetchError, - KubernetesRequestBody, -} from '@backstage/plugin-kubernetes-common'; +import type { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { Config } from '@backstage/config'; -import { KubernetesCredential } from '../auth/types'; - -/** - * - * @public - */ -export interface ObjectFetchParams { - serviceId: string; - clusterDetails: ClusterDetails; - credential: KubernetesCredential; - objectTypesToFetch: Set; - labelSelector?: string; - customResources: CustomResource[]; - namespace?: string; -} - -/** - * Fetches information from a kubernetes cluster using the cluster details object to target a specific cluster - * - * @public - */ -export interface KubernetesFetcher { - fetchObjectsForService( - params: ObjectFetchParams, - ): Promise; - fetchPodMetricsByNamespaces( - clusterDetails: ClusterDetails, - credential: KubernetesCredential, - namespaces: Set, - labelSelector?: string, - ): Promise; -} - -/** - * - * @public - */ -export interface FetchResponseWrapper { - errors: KubernetesFetchError[]; - responses: FetchResponse[]; -} - -/** - * - * @public - */ -export interface ObjectToFetch { - objectType: KubernetesObjectTypes; - group: string; - apiVersion: string; - plural: string; -} - -/** - * - * @public - */ -export interface CustomResource extends ObjectToFetch { - objectType: 'customresources'; -} - -/** - * - * @public - */ -export type KubernetesObjectTypes = - | 'pods' - | 'services' - | 'configmaps' - | 'deployments' - | 'limitranges' - | 'resourcequotas' - | 'replicasets' - | 'horizontalpodautoscalers' - | 'jobs' - | 'cronjobs' - | 'ingresses' - | 'customresources' - | 'statefulsets' - | 'daemonsets'; -// If updating this list, also make sure to update -// `objectTypes` and `apiVersionOverrides` in config.d.ts! - -/** - * Used to load cluster details from different sources - * @public - */ -export interface KubernetesClustersSupplier { - /** - * Returns the cached list of clusters. - * - * Implementations _should_ cache the clusters and refresh them periodically, - * as getClusters is called whenever the list of clusters is needed. - */ - getClusters(): Promise; -} - -/** - * @public - */ -export interface ServiceLocatorRequestContext { - objectTypesToFetch: Set; - customResources: CustomResourceMatcher[]; -} - -/** - * Used to locate which cluster(s) a service is running on - * @public - */ -export interface KubernetesServiceLocator { - getClustersByEntity( - entity: Entity, - requestContext: ServiceLocatorRequestContext, - ): Promise<{ clusters: ClusterDetails[] }>; -} +import * as k8sTypes from '@backstage/plugin-kubernetes-node'; /** * @@ -146,70 +25,6 @@ export interface KubernetesServiceLocator { */ export type ServiceLocatorMethod = 'multiTenant' | 'singleTenant' | 'http'; // TODO implement http -/** - * Provider-specific authentication configuration - * @public - */ -export type AuthMetadata = Record; - -/** - * - * @public - */ -export interface ClusterDetails { - /** - * Specifies the name of the Kubernetes cluster. - */ - name: string; - url: string; - authMetadata: AuthMetadata; - skipTLSVerify?: boolean; - /** - * Whether to skip the lookup to the metrics server to retrieve pod resource usage. - * It is not guaranteed that the Kubernetes distro has the metrics server installed. - */ - skipMetricsLookup?: boolean; - caData?: string | undefined; - caFile?: string | undefined; - /** - * Specifies the link to the Kubernetes dashboard managing this cluster. - * @remarks - * Note that you should specify the app used for the dashboard - * using the dashboardApp property, in order to properly format - * links to kubernetes resources, otherwise it will assume that you're running the standard one. - * @see dashboardApp - * @see dashboardParameters - */ - dashboardUrl?: string; - /** - * Specifies the app that provides the Kubernetes dashboard. - * This will be used for formatting links to kubernetes objects inside the dashboard. - * @remarks - * The existing apps are: standard, rancher, openshift, gke, aks, eks - * Note that it will default to the regular dashboard provided by the Kubernetes project (standard). - * Note that you can add your own formatter by registering it to the clusterLinksFormatters dictionary. - * @defaultValue standard - * @see dashboardUrl - * @example - * ```ts - * import { clusterLinksFormatters } from '@backstage/plugin-kubernetes'; - * clusterLinksFormatters.myDashboard = (options) => ...; - * ``` - */ - dashboardApp?: string; - /** - * Specifies specific parameters used by some dashboard URL formatters. - * This is used by the GKE formatter which requires the project, region and cluster name. - * @see dashboardApp - */ - dashboardParameters?: JsonObject; - /** - * Specifies which custom resources to look for when returning an entity's - * Kubernetes resources. - */ - customResources?: CustomResourceMatcher[]; -} - /** * * @public @@ -217,10 +32,10 @@ export interface ClusterDetails { export interface KubernetesObjectsProviderOptions { logger: Logger; config: Config; - fetcher: KubernetesFetcher; - serviceLocator: KubernetesServiceLocator; - customResources: CustomResource[]; - objectTypesToFetch?: ObjectToFetch[]; + fetcher: k8sTypes.KubernetesFetcher; + serviceLocator: k8sTypes.KubernetesServiceLocator; + customResources: k8sTypes.CustomResource[]; + objectTypesToFetch?: k8sTypes.ObjectToFetch[]; } /** @@ -228,3 +43,73 @@ export interface KubernetesObjectsProviderOptions { * @public */ export type ObjectsByEntityRequest = KubernetesRequestBody; + +// TODO remove this re-export as a breaking change after a couple of releases +/** + * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type KubernetesObjectsProvider = k8sTypes.KubernetesObjectsProvider; + +/** + * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type CustomResourcesByEntity = k8sTypes.CustomResourcesByEntity; + +/** + * @public + * @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type AuthMetadata = k8sTypes.AuthMetadata; + +/** + * @public + * @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type ClusterDetails = k8sTypes.ClusterDetails; + +/** + * @public + * @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type KubernetesClustersSupplier = k8sTypes.KubernetesClustersSupplier; + +/** + * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type KubernetesObjectTypes = k8sTypes.KubernetesObjectTypes; + +/** + * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type ObjectToFetch = k8sTypes.ObjectToFetch; + +/** + * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type CustomResource = k8sTypes.CustomResource; + +/** + * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type ObjectFetchParams = k8sTypes.ObjectFetchParams; + +/** + * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type FetchResponseWrapper = k8sTypes.FetchResponseWrapper; + +/** + * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type KubernetesFetcher = k8sTypes.KubernetesFetcher; + +/** + * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type ServiceLocatorRequestContext = + k8sTypes.ServiceLocatorRequestContext; + +/** + * @public @deprecated Import it from \@backstage/plugin-kubernetes-node instead + */ +export type KubernetesServiceLocator = k8sTypes.KubernetesServiceLocator; diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index 969996da08..d68450a87c 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,122 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-kubernetes-react@0.3.0-next.0 + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.0.5 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-kubernetes-react@0.2.1 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-kubernetes-react@0.2.1-next.1 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + +## 0.0.5-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-kubernetes-react@0.2.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-kubernetes-react@0.2.0 + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.0.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/plugin-kubernetes-react@0.2.0-next.3 + +## 0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-kubernetes-react@0.2.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## 0.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/plugin-kubernetes-react@0.1.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.0.4-next.0 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 6429f99d3f..0d7b7dce47 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-cluster", "description": "A Backstage plugin that shows details of Kubernetes clusters", - "version": "0.0.4-next.0", + "version": "0.0.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,18 +35,14 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-kubernetes-common": "workspace:^", "@backstage/plugin-kubernetes-react": "workspace:^", - "@backstage/theme": "workspace:^", "@kubernetes-models/apimachinery": "^1.1.0", "@kubernetes-models/base": "^4.0.1", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "cronstrue": "^2.2.0", @@ -63,15 +59,11 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", - "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "@types/node": "^16.11.26", - "msw": "^1.0.0" + "@types/node": "^16.11.26" }, "files": [ "dist" diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 7eeb148d94..ab8fd5cb9a 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,66 @@ # @backstage/plugin-kubernetes-common +## 0.7.4-next.0 + +### Patch Changes + +- daad576: Clusters configured with the `aws` authentication strategy can now customize the + `x-k8s-aws-id` header value used to generate tokens. This value can be specified + specified via the `kubernetes.io/x-k8s-aws-id` parameter (in + `metadata.annotations` for clusters in the catalog, or the `authMetadata` block + on clusters in the app-config). This is particularly helpful when a Backstage + instance contains multiple AWS clusters with the same name in different regions + -- using this new parameter, the clusters can be given different logical names + to distinguish them but still use the same ID for the purposes of generating + tokens. +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + +## 0.7.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## 0.7.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.11 + +## 0.7.2 + +### Patch Changes + +- 706fc3a: Updated dependency `@kubernetes/client-node` to `0.20.0`. +- 5d79682: Remove unused dependency +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.7.2-next.1 + +### Patch Changes + +- 5d796829bb: Remove unused dependency +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + ## 0.7.2-next.0 ### Patch Changes diff --git a/plugins/kubernetes-common/api-report.md b/plugins/kubernetes-common/api-report.md index abd340f368..e4dbf79f4a 100644 --- a/plugins/kubernetes-common/api-report.md +++ b/plugins/kubernetes-common/api-report.md @@ -39,6 +39,10 @@ export const ANNOTATION_KUBERNETES_AUTH_PROVIDER = export const ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE = 'kubernetes.io/aws-assume-role'; +// @public +export const ANNOTATION_KUBERNETES_AWS_CLUSTER_ID = + 'kubernetes.io/x-k8s-aws-id'; + // @public export const ANNOTATION_KUBERNETES_AWS_EXTERNAL_ID = 'kubernetes.io/aws-external-id'; diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index c3df439c8f..647618a62d 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-common", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", - "version": "0.7.2-next.0", + "version": "0.7.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -40,8 +40,6 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/core-plugin-api": "workspace:^", - "@backstage/errors": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/types": "workspace:^", "@kubernetes/client-node": "0.20.0", @@ -50,10 +48,7 @@ "luxon": "^3.0.0" }, "devDependencies": { - "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", - "@backstage/test-utils": "workspace:^", - "msw": "^1.3.1" + "@backstage/cli": "workspace:^" }, "jest": { "roots": [ diff --git a/plugins/kubernetes-common/src/catalog-entity-constants.ts b/plugins/kubernetes-common/src/catalog-entity-constants.ts index e7e5cddc25..996faf670d 100644 --- a/plugins/kubernetes-common/src/catalog-entity-constants.ts +++ b/plugins/kubernetes-common/src/catalog-entity-constants.ts @@ -84,6 +84,7 @@ export const ANNOTATION_KUBERNETES_DASHBOARD_APP = */ export const ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS = 'kubernetes.io/dashboard-parameters'; + /** * Annotation for specifying the assume role use to authenticate with AWS. * @@ -92,6 +93,14 @@ export const ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS = export const ANNOTATION_KUBERNETES_AWS_ASSUME_ROLE = 'kubernetes.io/aws-assume-role'; +/** + * Annotation for specifying the AWS ID of a cluster when signing STS tokens + * + * @public + */ +export const ANNOTATION_KUBERNETES_AWS_CLUSTER_ID = + 'kubernetes.io/x-k8s-aws-id'; + /** * Annotation for specifying an external id when communicating with AWS * diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index 28619caaf9..8f320e583c 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,99 @@ # @backstage/plugin-kubernetes-node +## 0.1.4-next.0 + +### Patch Changes + +- a775596: Enabled a way to include custom auth metadata info on the clusters endpoint. If you want to implement a Kubernetes auth strategy which requires surfacing custom auth metadata to the frontend, use the new presentAuthMetadata method on the AuthenticationStrategy interface. +- f180cba: Enabling authentication to kubernetes clusters with mTLS x509 client certs +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + +## 0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## 0.1.2 + +### Patch Changes + +- 6010564: The `kubernetes-node` plugin has been modified to house a new extension points for Kubernetes backend plugin; + `KubernetesClusterSupplierExtensionPoint` is introduced . + `kubernetesAuthStrategyExtensionPoint` is introduced . + `kubernetesFetcherExtensionPoint` is introduced . + `kubernetesServiceLocatorExtensionPoint` is introduced . + + The `kubernetes-backend` plugin was modified to use this new extension point. + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## 0.1.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + ## 0.1.2-next.0 ### Patch Changes diff --git a/plugins/kubernetes-node/api-report.md b/plugins/kubernetes-node/api-report.md index d2981b32d1..0521ed72aa 100644 --- a/plugins/kubernetes-node/api-report.md +++ b/plugins/kubernetes-node/api-report.md @@ -3,19 +3,140 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AuthenticationStrategy as AuthenticationStrategy_2 } from '@backstage/plugin-kubernetes-node'; +import { ClusterDetails as ClusterDetails_2 } from '@backstage/plugin-kubernetes-node'; import { CustomResourceMatcher } from '@backstage/plugin-kubernetes-common'; import { Entity } from '@backstage/catalog-model'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { FetchResponse } from '@backstage/plugin-kubernetes-common'; +import { JsonObject } from '@backstage/types'; +import { KubernetesClustersSupplier as KubernetesClustersSupplier_2 } from '@backstage/plugin-kubernetes-node'; +import { KubernetesFetcher as KubernetesFetcher_2 } from '@backstage/plugin-kubernetes-node'; +import { KubernetesFetchError } from '@backstage/plugin-kubernetes-common'; import { KubernetesObjectsProvider as KubernetesObjectsProvider_2 } from '@backstage/plugin-kubernetes-node'; import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common'; +import { KubernetesServiceLocator as KubernetesServiceLocator_2 } from '@backstage/plugin-kubernetes-node'; +import { Logger } from 'winston'; import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; +// @public (undocumented) +export interface AuthenticationStrategy { + // (undocumented) + getCredential( + clusterDetails: ClusterDetails, + authConfig: KubernetesRequestAuth, + ): Promise; + // (undocumented) + presentAuthMetadata(authMetadata: AuthMetadata): AuthMetadata; + // (undocumented) + validateCluster(authMetadata: AuthMetadata): Error[]; +} + +// @public +export type AuthMetadata = Record; + +// @public (undocumented) +export interface ClusterDetails { + // (undocumented) + authMetadata: AuthMetadata; + // (undocumented) + caData?: string | undefined; + // (undocumented) + caFile?: string | undefined; + customResources?: CustomResourceMatcher[]; + dashboardApp?: string; + dashboardParameters?: JsonObject; + dashboardUrl?: string; + name: string; + skipMetricsLookup?: boolean; + // (undocumented) + skipTLSVerify?: boolean; + // (undocumented) + url: string; +} + +// @public (undocumented) +export interface CustomResource extends ObjectToFetch { + // (undocumented) + objectType: 'customresources'; +} + // @public (undocumented) export interface CustomResourcesByEntity extends KubernetesObjectsByEntity { // (undocumented) customResources: CustomResourceMatcher[]; } +// @public (undocumented) +export interface FetchResponseWrapper { + // (undocumented) + errors: KubernetesFetchError[]; + // (undocumented) + responses: FetchResponse[]; +} + +// @public +export interface KubernetesAuthStrategyExtensionPoint { + // (undocumented) + addAuthStrategy(key: string, strategy: AuthenticationStrategy_2): void; +} + +// @public +export const kubernetesAuthStrategyExtensionPoint: ExtensionPoint; + +// @public +export interface KubernetesClustersSupplier { + getClusters(): Promise; +} + +// @public +export interface KubernetesClusterSupplierExtensionPoint { + // (undocumented) + addClusterSupplier(clusterSupplier: KubernetesClustersSupplier_2): void; +} + +// @public +export const kubernetesClusterSupplierExtensionPoint: ExtensionPoint; + +// @public +export type KubernetesCredential = + | { + type: 'bearer token'; + token: string; + } + | { + type: 'x509 client certificate'; + cert: string; + key: string; + } + | { + type: 'anonymous'; + }; + +// @public +export interface KubernetesFetcher { + // (undocumented) + fetchObjectsForService( + params: ObjectFetchParams, + ): Promise; + // (undocumented) + fetchPodMetricsByNamespaces( + clusterDetails: ClusterDetails, + credential: KubernetesCredential, + namespaces: Set, + labelSelector?: string, + ): Promise; +} + +// @public +export interface KubernetesFetcherExtensionPoint { + // (undocumented) + addFetcher(fetcher: KubernetesFetcher_2): void; +} + +// @public +export const kubernetesFetcherExtensionPoint: ExtensionPoint; + // @public (undocumented) export interface KubernetesObjectsByEntity { // (undocumented) @@ -44,4 +165,109 @@ export interface KubernetesObjectsProviderExtensionPoint { // @public export const kubernetesObjectsProviderExtensionPoint: ExtensionPoint; + +// @public (undocumented) +export type KubernetesObjectTypes = + | 'pods' + | 'services' + | 'configmaps' + | 'deployments' + | 'limitranges' + | 'resourcequotas' + | 'replicasets' + | 'horizontalpodautoscalers' + | 'jobs' + | 'cronjobs' + | 'ingresses' + | 'customresources' + | 'statefulsets' + | 'daemonsets'; + +// @public +export interface KubernetesServiceLocator { + // (undocumented) + getClustersByEntity( + entity: Entity, + requestContext: ServiceLocatorRequestContext, + ): Promise<{ + clusters: ClusterDetails[]; + }>; +} + +// @public +export interface KubernetesServiceLocatorExtensionPoint { + // (undocumented) + addServiceLocator(serviceLocator: KubernetesServiceLocator_2): void; +} + +// @public +export const kubernetesServiceLocatorExtensionPoint: ExtensionPoint; + +// @public (undocumented) +export interface ObjectFetchParams { + // (undocumented) + clusterDetails: ClusterDetails; + // (undocumented) + credential: KubernetesCredential; + // (undocumented) + customResources: CustomResource[]; + // (undocumented) + labelSelector?: string; + // (undocumented) + namespace?: string; + // (undocumented) + objectTypesToFetch: Set; + // (undocumented) + serviceId: string; +} + +// @public (undocumented) +export interface ObjectToFetch { + // (undocumented) + apiVersion: string; + // (undocumented) + group: string; + // (undocumented) + objectType: KubernetesObjectTypes; + // (undocumented) + plural: string; +} + +// @public (undocumented) +export type PinnipedClientCerts = { + key: string; + cert: string; + expirationTimestamp: string; +}; + +// @public (undocumented) +export class PinnipedHelper { + constructor(logger: Logger); + // (undocumented) + tokenCredentialRequest( + clusterDetails: ClusterDetails_2, + pinnipedParams: PinnipedParameters, + ): Promise; +} + +// @public (undocumented) +export type PinnipedParameters = { + clusterScopedIdToken: string; + authenticator: { + apiGroup: string; + kind: string; + name: string; + }; + tokenCredentialRequest?: { + apiGroup?: string; + }; +}; + +// @public (undocumented) +export interface ServiceLocatorRequestContext { + // (undocumented) + customResources: CustomResourceMatcher[]; + // (undocumented) + objectTypesToFetch: Set; +} ``` diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index 2f8b3237fc..f09d1165e2 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-node", "description": "Node.js library for the kubernetes plugin", - "version": "0.1.2-next.0", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,7 +22,13 @@ "postpack": "backstage-cli package postpack" }, "devDependencies": { - "@backstage/cli": "workspace:^" + "@backstage/backend-app-api": "workspace:^", + "@backstage/backend-common": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@backstage/plugin-kubernetes-backend": "workspace:^", + "msw": "^1.3.1", + "supertest": "^6.1.3" }, "files": [ "dist" @@ -30,6 +36,10 @@ "dependencies": { "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-model": "workspace:^", - "@backstage/plugin-kubernetes-common": "workspace:^" + "@backstage/plugin-kubernetes-common": "workspace:^", + "@backstage/types": "workspace:^", + "@kubernetes/client-node": "^0.20.0", + "node-fetch": "^2.6.7", + "winston": "^3.2.1" } } diff --git a/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts b/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts new file mode 100644 index 0000000000..3cb25d7934 --- /dev/null +++ b/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts @@ -0,0 +1,399 @@ +/* + * Copyright 2024 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 { ExtendedHttpServer } from '@backstage/backend-app-api'; +import { ClusterDetails } from '../types'; +import { + mockServices, + setupRequestMockHandlers, + startTestBackend, +} from '@backstage/backend-test-utils'; +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + kubernetesAuthStrategyExtensionPoint, + kubernetesClusterSupplierExtensionPoint, +} from '../extensions'; +import request from 'supertest'; +import { + ANNOTATION_KUBERNETES_AUTH_PROVIDER, + KubernetesRequestAuth, +} from '@backstage/plugin-kubernetes-common'; +import { PinnipedHelper, PinnipedParameters } from './PinnipedHelper'; +import { getVoidLogger } from '@backstage/backend-common'; +import { HEADER_KUBERNETES_CLUSTER } from '@backstage/plugin-kubernetes-backend'; +import { JsonObject } from '@backstage/types'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; + +describe('Pinniped - tokenCredentialRequest', () => { + let app: ExtendedHttpServer; + const logger = getVoidLogger(); + let httpsRequest: jest.SpyInstance; + const worker = setupServer(); + setupRequestMockHandlers(worker); + + beforeAll(() => { + httpsRequest = jest.spyOn( + // this is pretty egregious reverse engineering of msw. + // If the SetupServerApi constructor was exported, we wouldn't need + // to be quite so hacky here + (worker as any).interceptor.interceptors[0].modules.get('https'), + 'request', + ); + }); + + beforeEach(async () => { + httpsRequest.mockClear(); + + const clusterSupplierMock = { + getClusters: jest.fn().mockImplementation(_ => { + return Promise.resolve([ + { + name: 'custom-cluster', + url: 'https://my.cluster.url', + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'pinniped', + }, + skipTLSVerify: true, + }, + ]); + }), + }; + + const { server } = await startTestBackend({ + features: [ + mockServices.rootConfig.factory({ + data: { + kubernetes: { + serviceLocatorMethod: { + type: 'multiTenant', + }, + clusterLocatorMethods: [ + { + type: 'config', + clusters: [], + }, + ], + }, + }, + }), + import('@backstage/plugin-kubernetes-backend/alpha'), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testClusterSupplier', + register(env) { + env.registerInit({ + deps: { extension: kubernetesClusterSupplierExtensionPoint }, + async init({ extension }) { + extension.addClusterSupplier(clusterSupplierMock); + }, + }); + }, + }), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'PinnipedAuthStrategy', + register(env) { + env.registerInit({ + deps: { extension: kubernetesAuthStrategyExtensionPoint }, + async init({ extension }) { + extension.addAuthStrategy('pinniped', { + getCredential: async ( + clusterDetails: ClusterDetails, + authConfig: KubernetesRequestAuth, + ) => { + const pinnipedHelper = new PinnipedHelper(logger); + const pinnipedParams: PinnipedParameters = { + clusterScopedIdToken: + ((authConfig.pinniped as JsonObject) + ?.clusteridtoken as string) || '', + authenticator: { + apiGroup: 'authentication.concierge.pinniped.dev', + kind: 'JWTAuthenticator', + name: 'supervisor', + }, + }; + const clientCerts = + await pinnipedHelper.tokenCredentialRequest( + clusterDetails, + pinnipedParams, + ); + return { + type: 'x509 client certificate', + key: clientCerts.key, + cert: clientCerts.cert, + }; + }, + validateCluster: jest.fn().mockReturnValue([]), + presentAuthMetadata: jest.fn().mockReturnValue({}), + }); + }, + }); + }, + }), + ], + }); + + app = server; + }); + + describe('TLS Clusters', () => { + it('Should get certs data from Concierge', async () => { + worker.use( + rest.get('https://my.cluster.url/api/v1/namespaces', (_, res, ctx) => { + return res(ctx.json({ items: [] })); + }), + ); + + const myCert = 'MOCKCert'; + const myKey = 'MOCKKey'; + + worker.use( + rest.post( + 'https://my.cluster.url/apis/login.concierge.pinniped.dev/v1alpha1/tokencredentialrequests', + (_, res, ctx) => { + return res( + ctx.json({ + status: { + credential: { + clientKeyData: myKey, + clientCertificateData: myCert, + expirationTimestamp: '2024-01-04T14:30:30.373Z', + }, + }, + }), + ); + }, + ), + ); + + const proxyEndpointRequest = request(app) + .get('/api/kubernetes/proxy/api/v1/namespaces') + .set(HEADER_KUBERNETES_CLUSTER, 'custom-cluster') + .set( + 'Backstage-Kubernetes-Authorization-Pinniped-ClusterIDToken', + 'ClusterID Specific Token', + ); + + worker.use(rest.all(proxyEndpointRequest.url, req => req.passthrough())); + + const result = await proxyEndpointRequest; + + expect(JSON.stringify(result)).toMatch(/PEM/); + + expect(httpsRequest).toHaveBeenCalledTimes(2); + const [{ cert, key }] = httpsRequest.mock.calls[1]; + expect(cert).toEqual(myCert); + expect(key).toEqual(myKey); + }); + + it('Should get certs data from TMC-flavoured Pinniped', async () => { + worker.use( + rest.get('https://my.cluster.url/api/v1/namespaces', (_, res, ctx) => { + return res(ctx.json({ items: [] })); + }), + ); + + const myCert = 'MOCKCert2'; + const myKey = 'MOCKKey2'; + + worker.use( + rest.post( + 'https://my.cluster.url/apis/login.concierge.pinniped.tmc.cloud.vmware.com/v1alpha1/tokencredentialrequests', + (_, res, ctx) => { + return res( + ctx.json({ + status: { + credential: { + clientKeyData: myKey, + clientCertificateData: myCert, + expirationTimestamp: '2024-01-04T14:30:30.373Z', + }, + }, + }), + ); + }, + ), + ); + + const clusterSupplierMock = { + getClusters: jest.fn().mockImplementation(_ => { + return Promise.resolve([ + { + name: 'tmc-cluster', + url: 'https://my.cluster.url', + authMetadata: { + [ANNOTATION_KUBERNETES_AUTH_PROVIDER]: 'pinnipedtmc', + }, + skipTLSVerify: true, + }, + ]); + }), + }; + + const { server } = await startTestBackend({ + features: [ + mockServices.rootConfig.factory({ + data: { + kubernetes: { + serviceLocatorMethod: { + type: 'multiTenant', + }, + clusterLocatorMethods: [ + { + type: 'config', + clusters: [], + }, + ], + }, + }, + }), + import('@backstage/plugin-kubernetes-backend/alpha'), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'testClusterSupplier', + register(env) { + env.registerInit({ + deps: { extension: kubernetesClusterSupplierExtensionPoint }, + async init({ extension }) { + extension.addClusterSupplier(clusterSupplierMock); + }, + }); + }, + }), + createBackendModule({ + pluginId: 'kubernetes', + moduleId: 'PinnipedAuthStrategy', + register(env) { + env.registerInit({ + deps: { extension: kubernetesAuthStrategyExtensionPoint }, + async init({ extension }) { + extension.addAuthStrategy('pinnipedtmc', { + getCredential: async ( + clusterDetails: ClusterDetails, + authConfig: KubernetesRequestAuth, + ) => { + const pinnipedHelper = new PinnipedHelper(logger); + const pinnipedParams: PinnipedParameters = { + clusterScopedIdToken: + ((authConfig.pinniped as JsonObject) + ?.clusteridtoken as string) || '', + authenticator: { + apiGroup: + 'authentication.concierge.pinniped.tmc.cloud.vmware.com', + kind: 'WebhookAuthenticator', + name: 'supervisor', + }, + tokenCredentialRequest: { + apiGroup: + 'login.concierge.pinniped.tmc.cloud.vmware.com/v1alpha1', + }, + }; + const clientCerts = + await pinnipedHelper.tokenCredentialRequest( + clusterDetails, + pinnipedParams, + ); + return { + type: 'x509 client certificate', + key: clientCerts.key, + cert: clientCerts.cert, + }; + }, + validateCluster: jest.fn().mockReturnValue([]), + presentAuthMetadata: jest.fn().mockReturnValue({}), + }); + }, + }); + }, + }), + ], + }); + + app = server; + + const proxyEndpointRequest = request(app) + .get('/api/kubernetes/proxy/api/v1/namespaces') + .set(HEADER_KUBERNETES_CLUSTER, 'tmc-cluster') + .set( + 'Backstage-Kubernetes-Authorization-Pinniped-ClusterIDToken', + 'ClusterID Specific Token', + ); + + worker.use(rest.all(proxyEndpointRequest.url, req => req.passthrough())); + + const result = await proxyEndpointRequest; + + expect(JSON.stringify(result)).toMatch(/PEM/); + + expect(httpsRequest).toHaveBeenCalledTimes(2); + const [{ cert, key }] = httpsRequest.mock.calls[1]; + expect(cert).toEqual(myCert); + expect(key).toEqual(myKey); + }); + + it('Should get an error when Concierge return an error', async () => { + worker.use( + rest.get('https://my.cluster.url/api/v1/namespaces', (_, res, ctx) => { + return res(ctx.json({ items: [] })); + }), + ); + + worker.use( + rest.post( + 'https://my.cluster.url/apis/login.concierge.pinniped.dev/v1alpha1/tokencredentialrequests', + (_, res, ctx) => { + return res( + ctx.json({ + kind: 'TokenCredentialRequest', + apiVersion: 'login.concierge.pinniped.dev/v1alpha1', + metadata: { + creationTimestamp: null, + }, + spec: { + authenticator: { + apiGroup: null, + kind: '', + name: '', + }, + }, + status: { + message: 'authentication failed', + }, + }), + ); + }, + ), + ); + + const proxyEndpointRequest = request(app) + .get('/api/kubernetes/proxy/api/v1/namespaces') + .set(HEADER_KUBERNETES_CLUSTER, 'custom-cluster') + .set( + 'Backstage-Kubernetes-Authorization-Pinniped-ClusterIDToken', + 'ClusterID Specific Token', + ); + + worker.use(rest.all(proxyEndpointRequest.url, req => req.passthrough())); + + const result = await proxyEndpointRequest; + + expect(JSON.stringify(result)).toMatch(/error/); + + expect(httpsRequest).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/plugins/kubernetes-node/src/auth/PinnipedHelper.ts b/plugins/kubernetes-node/src/auth/PinnipedHelper.ts new file mode 100644 index 0000000000..1f824bae33 --- /dev/null +++ b/plugins/kubernetes-node/src/auth/PinnipedHelper.ts @@ -0,0 +1,147 @@ +/* + * Copyright 2024 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 { ClusterDetails } from '@backstage/plugin-kubernetes-node'; +import * as https from 'https'; +import { bufferFromFileOrString } from '@kubernetes/client-node'; +import fetch, { RequestInit } from 'node-fetch'; +import { Logger } from 'winston'; + +/** + * + * @public + */ +export type PinnipedClientCerts = { + key: string; + cert: string; + expirationTimestamp: string; +}; + +/** + * + * @public + */ +export type PinnipedParameters = { + clusterScopedIdToken: string; + authenticator: { + apiGroup: string; + kind: string; + name: string; + }; + tokenCredentialRequest?: { + apiGroup?: string; + }; +}; + +/** + * + * @public + */ +export class PinnipedHelper { + constructor(private readonly logger: Logger) {} + + public async tokenCredentialRequest( + clusterDetails: ClusterDetails, + pinnipedParams: PinnipedParameters, + ): Promise { + this.logger.debug('Pinniped: Requesting client Certs to Concierge'); + return await this.exchangeClusterTokentoClientCerts( + clusterDetails, + pinnipedParams, + ); + } + + private async exchangeClusterTokentoClientCerts( + clusterDetails: ClusterDetails, + pinnipedParams: PinnipedParameters, + ): Promise { + const url: URL = new URL(clusterDetails.url); + const apiGroup = + pinnipedParams.tokenCredentialRequest?.apiGroup ?? + 'login.concierge.pinniped.dev/v1alpha1'; + + url.pathname = `/apis/${apiGroup}/tokencredentialrequests`; + + const requestInit: RequestInit = this.buildRequestForPinniped( + url, + clusterDetails, + pinnipedParams, + ); + + this.logger.info( + 'Fetching client certs for mTLS authentication on Pinniped', + ); + let response; + try { + response = await fetch(url, requestInit); + } catch (error) { + this.logger.error('Pinniped request error', error); + throw error; + } + + const data: any = await response.json(); + + if (data.status.credential) { + const result = { + key: data.status.credential.clientKeyData, + cert: data.status.credential.clientCertificateData, + expirationTimestamp: data.status.credential.expirationTimestamp, + }; + return Promise.resolve(result); + } + + this.logger.error('Unable to fetch client certs,', data.status); + return Promise.reject(data.status.message); + } + + private buildRequestForPinniped( + url: URL, + clusterDetails: ClusterDetails, + pinnipedParams: PinnipedParameters, + ): RequestInit { + const body = { + apiVersion: + pinnipedParams.tokenCredentialRequest?.apiGroup ?? + 'login.concierge.pinniped.dev/v1alpha1', + kind: 'TokenCredentialRequest', + spec: { + authenticator: pinnipedParams.authenticator, + token: pinnipedParams.clusterScopedIdToken, + }, + }; + const requestInit: RequestInit = { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }; + + if (url.protocol === 'https:') { + requestInit.agent = new https.Agent({ + ca: + bufferFromFileOrString( + clusterDetails.caFile, + clusterDetails.caData, + ) ?? undefined, + rejectUnauthorized: !clusterDetails.skipTLSVerify, + }); + } + + return requestInit; + } +} diff --git a/plugins/kubernetes-node/src/auth/index.ts b/plugins/kubernetes-node/src/auth/index.ts new file mode 100644 index 0000000000..8459f04f81 --- /dev/null +++ b/plugins/kubernetes-node/src/auth/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './PinnipedHelper'; diff --git a/plugins/kubernetes-node/src/extensions.ts b/plugins/kubernetes-node/src/extensions.ts index 152622cf96..d898244161 100644 --- a/plugins/kubernetes-node/src/extensions.ts +++ b/plugins/kubernetes-node/src/extensions.ts @@ -14,7 +14,13 @@ * limitations under the License. */ import { createExtensionPoint } from '@backstage/backend-plugin-api'; -import { KubernetesObjectsProvider } from '@backstage/plugin-kubernetes-node'; +import { + AuthenticationStrategy, + KubernetesClustersSupplier, + KubernetesFetcher, + KubernetesObjectsProvider, + KubernetesServiceLocator, +} from '@backstage/plugin-kubernetes-node'; /** * The interface for {@link kubernetesObjectsProviderExtensionPoint}. @@ -34,3 +40,79 @@ export const kubernetesObjectsProviderExtensionPoint = createExtensionPoint({ id: 'kubernetes.objects-provider', }); + +/** + * The interface for {@link kubernetesClusterSupplierExtensionPoint}. + * + * @public + */ +export interface KubernetesClusterSupplierExtensionPoint { + addClusterSupplier(clusterSupplier: KubernetesClustersSupplier): void; +} + +/** + * An extension point the exposes the ability to configure a cluster supplier. + * + * @public + */ +export const kubernetesClusterSupplierExtensionPoint = + createExtensionPoint({ + id: 'kubernetes.cluster-supplier', + }); + +/** + * The interface for {@link kubernetesAuthStrategyExtensionPoint}. + * + * @public + */ +export interface KubernetesAuthStrategyExtensionPoint { + addAuthStrategy(key: string, strategy: AuthenticationStrategy): void; +} + +/** + * An extension point the exposes the ability to add an Auth Strategy. + * + * @public + */ +export const kubernetesAuthStrategyExtensionPoint = + createExtensionPoint({ + id: 'kubernetes.auth-strategy', + }); + +/** + * The interface for {@link kubernetesFetcherExtensionPoint}. + * + * @public + */ +export interface KubernetesFetcherExtensionPoint { + addFetcher(fetcher: KubernetesFetcher): void; +} + +/** + * An extension point the exposes the ability to configure a kubernetes fetcher. + * + * @public + */ +export const kubernetesFetcherExtensionPoint = + createExtensionPoint({ + id: 'kubernetes.fetcher', + }); + +/** + * The interface for {@link kubernetesServiceLocatorExtensionPoint}. + * + * @public + */ +export interface KubernetesServiceLocatorExtensionPoint { + addServiceLocator(serviceLocator: KubernetesServiceLocator): void; +} + +/** + * An extension point the exposes the ability to configure a kubernetes service locator. + * + * @public + */ +export const kubernetesServiceLocatorExtensionPoint = + createExtensionPoint({ + id: 'kubernetes.service-locator', + }); diff --git a/plugins/kubernetes-node/src/index.ts b/plugins/kubernetes-node/src/index.ts index 09ea4123ce..e0f2688ca2 100644 --- a/plugins/kubernetes-node/src/index.ts +++ b/plugins/kubernetes-node/src/index.ts @@ -29,9 +29,6 @@ * @packageDocumentation */ -export { - kubernetesObjectsProviderExtensionPoint, - type KubernetesObjectsProviderExtensionPoint, -} from './extensions'; - +export * from './extensions'; export * from './types'; +export * from './auth'; diff --git a/plugins/kubernetes-node/src/types/types.ts b/plugins/kubernetes-node/src/types/types.ts index 182f3ecfeb..2881338868 100644 --- a/plugins/kubernetes-node/src/types/types.ts +++ b/plugins/kubernetes-node/src/types/types.ts @@ -16,9 +16,12 @@ import { Entity } from '@backstage/catalog-model'; import { CustomResourceMatcher, + FetchResponse, + KubernetesFetchError, KubernetesRequestAuth, ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-common'; +import { JsonObject } from '@backstage/types'; /** * @@ -51,3 +54,202 @@ export interface KubernetesObjectsByEntity { export interface CustomResourcesByEntity extends KubernetesObjectsByEntity { customResources: CustomResourceMatcher[]; } + +/** + * Provider-specific authentication configuration + * @public + */ +export type AuthMetadata = Record; + +/** + * + * @public + */ +export interface ClusterDetails { + /** + * Specifies the name of the Kubernetes cluster. + */ + name: string; + url: string; + authMetadata: AuthMetadata; + skipTLSVerify?: boolean; + /** + * Whether to skip the lookup to the metrics server to retrieve pod resource usage. + * It is not guaranteed that the Kubernetes distro has the metrics server installed. + */ + skipMetricsLookup?: boolean; + caData?: string | undefined; + caFile?: string | undefined; + /** + * Specifies the link to the Kubernetes dashboard managing this cluster. + * @remarks + * Note that you should specify the app used for the dashboard + * using the dashboardApp property, in order to properly format + * links to kubernetes resources, otherwise it will assume that you're running the standard one. + * @see dashboardApp + * @see dashboardParameters + */ + dashboardUrl?: string; + /** + * Specifies the app that provides the Kubernetes dashboard. + * This will be used for formatting links to kubernetes objects inside the dashboard. + * @remarks + * The existing apps are: standard, rancher, openshift, gke, aks, eks + * Note that it will default to the regular dashboard provided by the Kubernetes project (standard). + * Note that you can add your own formatter by registering it to the clusterLinksFormatters dictionary. + * @defaultValue standard + * @see dashboardUrl + * @example + * ```ts + * import { clusterLinksFormatters } from '@backstage/plugin-kubernetes'; + * clusterLinksFormatters.myDashboard = (options) => ...; + * ``` + */ + dashboardApp?: string; + /** + * Specifies specific parameters used by some dashboard URL formatters. + * This is used by the GKE formatter which requires the project, region and cluster name. + * @see dashboardApp + */ + dashboardParameters?: JsonObject; + /** + * Specifies which custom resources to look for when returning an entity's + * Kubernetes resources. + */ + customResources?: CustomResourceMatcher[]; +} + +/** + * Used to load cluster details from different sources + * @public + */ +export interface KubernetesClustersSupplier { + /** + * Returns the cached list of clusters. + * + * Implementations _should_ cache the clusters and refresh them periodically, + * as getClusters is called whenever the list of clusters is needed. + */ + getClusters(): Promise; +} + +/** + * Authentication data used to make a request to Kubernetes + * @public + */ +export type KubernetesCredential = + | { type: 'bearer token'; token: string } + | { type: 'x509 client certificate'; cert: string; key: string } + | { type: 'anonymous' }; + +/** + * + * @public + */ +export interface AuthenticationStrategy { + getCredential( + clusterDetails: ClusterDetails, + authConfig: KubernetesRequestAuth, + ): Promise; + validateCluster(authMetadata: AuthMetadata): Error[]; + presentAuthMetadata(authMetadata: AuthMetadata): AuthMetadata; +} + +/** + * + * @public + */ +export type KubernetesObjectTypes = + | 'pods' + | 'services' + | 'configmaps' + | 'deployments' + | 'limitranges' + | 'resourcequotas' + | 'replicasets' + | 'horizontalpodautoscalers' + | 'jobs' + | 'cronjobs' + | 'ingresses' + | 'customresources' + | 'statefulsets' + | 'daemonsets'; +// If updating this list, also make sure to update +// `objectTypes` and `apiVersionOverrides` in config.d.ts on @backstage/plugin-kubernetes-backend! + +/** + * + * @public + */ +export interface ObjectToFetch { + objectType: KubernetesObjectTypes; + group: string; + apiVersion: string; + plural: string; +} + +/** + * + * @public + */ +export interface CustomResource extends ObjectToFetch { + objectType: 'customresources'; +} + +/** + * + * @public + */ +export interface ObjectFetchParams { + serviceId: string; + clusterDetails: ClusterDetails; + credential: KubernetesCredential; + objectTypesToFetch: Set; + labelSelector?: string; + customResources: CustomResource[]; + namespace?: string; +} + +/** + * + * @public + */ +export interface FetchResponseWrapper { + errors: KubernetesFetchError[]; + responses: FetchResponse[]; +} + +/** + * Fetches information from a kubernetes cluster using the cluster details object to target a specific cluster + * + * @public + */ +export interface KubernetesFetcher { + fetchObjectsForService( + params: ObjectFetchParams, + ): Promise; + fetchPodMetricsByNamespaces( + clusterDetails: ClusterDetails, + credential: KubernetesCredential, + namespaces: Set, + labelSelector?: string, + ): Promise; +} +/** + * @public + */ +export interface ServiceLocatorRequestContext { + objectTypesToFetch: Set; + customResources: CustomResourceMatcher[]; +} + +/** + * Used to locate which cluster(s) a service is running on + * @public + */ +export interface KubernetesServiceLocator { + getClustersByEntity( + entity: Entity, + requestContext: ServiceLocatorRequestContext, + ): Promise<{ clusters: ClusterDetails[] }>; +} diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index fbc09b0b90..643fea0159 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,131 @@ # @backstage/plugin-kubernetes-react +## 0.3.0-next.0 + +### Minor Changes + +- 0d526c8: **BREAKING** The pod exec terminal is now disabled by default since there are several scenarios where it is known not to work. It can be re-enabled at your own risk by setting the config parameter `kubernetes.podExecTerminal.enabled` to `true`. + +### Patch Changes + +- 536f67d: Fix broken XtermJS CSS import +- db1054b: Fixed a bug where the logs dialog and any other functionality depending on the proxy endpoint would fail for clusters configured with the OIDC auth provider. +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.1 + +### Patch Changes + +- d5d2c67: Add `authuser` search parameter to GKE cluster link formatter in k8s plugin + + Thanks to this, people with multiple simultaneously logged-in accounts in their GCP console will automatically view objects with the same email as the one signed in to the Google auth provider in Backstage. + +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.1-next.1 + +### Patch Changes + +- d5d2c67: Add `authuser` search parameter to GKE cluster link formatter in k8s plugin + + Thanks to this, people with multiple simultaneously logged-in accounts in their GCP console will automatically view objects with the same email as the one signed in to the Google auth provider in Backstage. + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.0 + +### Minor Changes + +- 899d71a: Change `formatClusterLink` to be an API and make it async for further customization possibilities. + + **BREAKING** + If you have a custom k8s page and used `formatClusterLink` directly, you need to migrate to new `kubernetesClusterLinkFormatterApiRef` + +### Patch Changes + +- b5ae2e5: Add ID property to the table displaying kubernetes pods to avoid closing the info sidebar when the data reloads and needs to rerender. +- 706fc3a: Updated dependency `@kubernetes/client-node` to `0.20.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## 0.2.0-next.2 + +### Minor Changes + +- 899d71a: Change `formatClusterLink` to be an API and make it async for further customization possibilities. + + **BREAKING** + If you have a custom k8s page and used `formatClusterLink` directly, you need to migrate to new `kubernetesClusterLinkFormatterApiRef` + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + ## 0.1.2-next.0 ### Patch Changes diff --git a/plugins/kubernetes-react/api-report.md b/plugins/kubernetes-react/api-report.md index ca1df34b40..7e9c2fbd61 100644 --- a/plugins/kubernetes-react/api-report.md +++ b/plugins/kubernetes-react/api-report.md @@ -22,13 +22,14 @@ import { IContainerStatus } from 'kubernetes-models/v1'; import { IdentityApi } from '@backstage/core-plugin-api'; import { IIoK8sApimachineryPkgApisMetaV1ObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta'; import { IObjectMeta } from '@kubernetes-models/apimachinery/apis/meta/v1/ObjectMeta'; -import type { JsonObject } from '@backstage/types'; +import { JsonObject } from '@backstage/types'; import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { OAuthApi } from '@backstage/core-plugin-api'; import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; import { OpenIdConnectApi } from '@backstage/core-plugin-api'; import { Pod } from 'kubernetes-models/v1'; import { Pod as Pod_2 } from 'kubernetes-models/v1/Pod'; +import { ProfileInfoApi } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import * as React_3 from 'react'; import { TypeMeta } from '@kubernetes-models/base'; @@ -38,6 +39,12 @@ import { V1ObjectMeta } from '@kubernetes/client-node'; import { V1Pod } from '@kubernetes/client-node'; import { WorkloadsByEntityRequest } from '@backstage/plugin-kubernetes-common'; +// @public (undocumented) +export class AksClusterLinksFormatter implements ClusterLinksFormatter { + // (undocumented) + formatClusterLink(options: ClusterLinksFormatterOptions): Promise; +} + // @public (undocumented) export class AksKubernetesAuthProvider implements KubernetesAuthProvider { constructor(microsoftAuthApi: OAuthApi); @@ -61,9 +68,10 @@ export const Cluster: ({ export const ClusterContext: React_2.Context; // @public (undocumented) -export type ClusterLinksFormatter = ( - options: ClusterLinksFormatterOptions, -) => URL; +export interface ClusterLinksFormatter { + // (undocumented) + formatClusterLink(options: ClusterLinksFormatterOptions): Promise; +} // @public (undocumented) export interface ClusterLinksFormatterOptions { @@ -77,9 +85,6 @@ export interface ClusterLinksFormatterOptions { object: any; } -// @public (undocumented) -export const clusterLinksFormatters: Record; - // @public export type ClusterProps = { clusterObjects: ClusterObjects; @@ -125,9 +130,18 @@ export interface CustomResourcesProps { children?: React_2.ReactNode; } +// @public (undocumented) +export const DEFAULT_FORMATTER_NAME = 'standard'; + // @public export const DetectedErrorsContext: React_2.Context; +// @public (undocumented) +export class EksClusterLinksFormatter implements ClusterLinksFormatter { + // (undocumented) + formatClusterLink(_options: ClusterLinksFormatterOptions): Promise; +} + // @public export const ErrorList: ({ podAndErrors, @@ -228,11 +242,6 @@ export interface FixDialogProps { pod: Pod_2; } -// @public (undocumented) -export function formatClusterLink( - options: FormatClusterLinkOptions, -): string | undefined; - // @public (undocumented) export type FormatClusterLinkOptions = { dashboardUrl?: string; @@ -242,6 +251,18 @@ export type FormatClusterLinkOptions = { kind: string; }; +// @public (undocumented) +export function getDefaultFormatters(deps: { + googleAuthApi: ProfileInfoApi; +}): Record; + +// @public (undocumented) +export class GkeClusterLinksFormatter implements ClusterLinksFormatter { + constructor(googleAuthApi: ProfileInfoApi | undefined); + // (undocumented) + formatClusterLink(options: ClusterLinksFormatterOptions): Promise; +} + // @public (undocumented) export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider { constructor(authProvider: OAuthApi); @@ -415,6 +436,31 @@ export class KubernetesBackendClient implements KubernetesApi { }): Promise; } +// @public (undocumented) +export class KubernetesClusterLinkFormatter + implements KubernetesClusterLinkFormatterApi +{ + constructor(options: { + formatters: Record; + defaultFormatterName: string; + }); + // (undocumented) + formatClusterLink( + options: FormatClusterLinkOptions, + ): Promise; +} + +// @public (undocumented) +export interface KubernetesClusterLinkFormatterApi { + // (undocumented) + formatClusterLink( + options: FormatClusterLinkOptions, + ): Promise; +} + +// @public (undocumented) +export const kubernetesClusterLinkFormatterApiRef: ApiRef; + // @public export const KubernetesDrawer: ({ open, @@ -587,6 +633,12 @@ export class OidcKubernetesAuthProvider implements KubernetesAuthProvider { providerName: string; } +// @public (undocumented) +export class OpenshiftClusterLinksFormatter { + // (undocumented) + formatClusterLink(options: ClusterLinksFormatterOptions): Promise; +} + // @public export const PendingPodContent: ({ pod, @@ -716,6 +768,12 @@ export type PodsTablesProps = { children?: React_2.ReactNode; }; +// @public (undocumented) +export class RancherClusterLinksFormatter implements ClusterLinksFormatter { + // (undocumented) + formatClusterLink(options: ClusterLinksFormatterOptions): Promise; +} + // @public (undocumented) export const READY_COLUMNS: PodColumns; @@ -763,6 +821,12 @@ export const ServicesAccordions: ({}: ServicesAccordionsProps) => React_2.JSX.El // @public (undocumented) export type ServicesAccordionsProps = {}; +// @public (undocumented) +export class StandardClusterLinksFormatter implements ClusterLinksFormatter { + // (undocumented) + formatClusterLink(options: ClusterLinksFormatterOptions): Promise; +} + // @public export const useCustomResources: ( entity: Entity, diff --git a/plugins/kubernetes-react/config.d.ts b/plugins/kubernetes-react/config.d.ts new file mode 100644 index 0000000000..2d2b790d05 --- /dev/null +++ b/plugins/kubernetes-react/config.d.ts @@ -0,0 +1,31 @@ +import { PodExecTerminal } from './src/components/PodExecTerminal/PodExecTerminal'; +/* + * 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 interface Config { + kubernetes?: { + /** + * Pod Exec Terminal config + */ + podExecTerminal?: { + /** + * Enable `PodExecTerminal` UI feature + * @visibility frontend + */ + enabled?: boolean; + }; + }; +} diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 0b707da172..0d722402ba 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-react", "description": "Web library for the kubernetes-react plugin", - "version": "0.1.2-next.0", + "version": "0.3.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -14,6 +14,7 @@ "role": "web-library" }, "sideEffects": false, + "configSchema": "config.d.ts", "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", @@ -60,6 +61,7 @@ "msw": "^1.3.1" }, "files": [ - "dist" + "dist", + "config.d.ts" ] } diff --git a/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts b/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts index fd999ab335..0535e404f6 100644 --- a/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts +++ b/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts @@ -448,6 +448,9 @@ describe('KubernetesBackendClient', () => { const response = await backendClient.proxy(request); await expect(response.json()).resolves.toStrictEqual(nsResponse); + expect(kubernetesAuthProvidersApi.getCredentials).toHaveBeenCalledWith( + 'oidc.okta', + ); }); it('hits the /proxy API with serviceAccount as auth provider', async () => { @@ -495,6 +498,42 @@ describe('KubernetesBackendClient', () => { const response = await backendClient.proxy(request); await expect(response.json()).resolves.toStrictEqual(nsResponse); + expect(kubernetesAuthProvidersApi.getCredentials).toHaveBeenCalledWith( + 'serviceAccount', + ); + }); + + it('ignores oidcTokenProvider for non-oidc auth provider', async () => { + worker.use( + rest.get( + 'http://localhost:1234/api/kubernetes/clusters', + (_, res, ctx) => + res( + ctx.json({ + items: [ + { + name: 'cluster-a', + authProvider: 'not oidc', + oidcTokenProvider: 'should be ignored', + }, + ], + }), + ), + ), + rest.get( + 'http://localhost:1234/api/kubernetes/proxy/api/v1/namespaces', + (_, res, ctx) => res(ctx.json([])), + ), + ); + + await backendClient.proxy({ + clusterName: 'cluster-a', + path: '/api/v1/namespaces', + }); + + expect(kubernetesAuthProvidersApi.getCredentials).toHaveBeenCalledWith( + 'not oidc', + ); }); it('hits /proxy api when signed in as a guest', async () => { diff --git a/plugins/kubernetes-react/src/api/KubernetesBackendClient.ts b/plugins/kubernetes-react/src/api/KubernetesBackendClient.ts index a769eb088d..461b1d1ffe 100644 --- a/plugins/kubernetes-react/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes-react/src/api/KubernetesBackendClient.ts @@ -92,8 +92,13 @@ export class KubernetesBackendClient implements KubernetesApi { private async getCredentials( authProvider: string, + oidcTokenProvider?: string, ): Promise<{ token?: string }> { - return await this.kubernetesAuthProvidersApi.getCredentials(authProvider); + return await this.kubernetesAuthProvidersApi.getCredentials( + authProvider === 'oidc' + ? `${authProvider}.${oidcTokenProvider}` + : authProvider, + ); } async getObjectsByEntity( @@ -145,7 +150,10 @@ export class KubernetesBackendClient implements KubernetesApi { const { authProvider, oidcTokenProvider } = await this.getCluster( options.clusterName, ); - const kubernetesCredentials = await this.getCredentials(authProvider); + const kubernetesCredentials = await this.getCredentials( + authProvider, + oidcTokenProvider, + ); const url = `${await this.discoveryApi.getBaseUrl('kubernetes')}/proxy${ options.path }`; diff --git a/plugins/kubernetes-react/src/api/KubernetesClusterLinkFormatter.test.ts b/plugins/kubernetes-react/src/api/KubernetesClusterLinkFormatter.test.ts new file mode 100644 index 0000000000..05b1709c75 --- /dev/null +++ b/plugins/kubernetes-react/src/api/KubernetesClusterLinkFormatter.test.ts @@ -0,0 +1,76 @@ +/* + * 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 { KubernetesClusterLinkFormatter } from './KubernetesClusterLinkFormatter'; + +const dashboardUrl = new URL('http://dashboard/url'); +describe('KubernetesClusterLinkFormatter', () => { + const formatter = new KubernetesClusterLinkFormatter({ + formatters: { + standard: { formatClusterLink: async _ => dashboardUrl }, + }, + defaultFormatterName: 'standard', + }); + describe('formatter.formatClusterLink', () => { + it('should not return an url when there is no dashboard url', async () => { + const url = await formatter.formatClusterLink({ + object: {}, + kind: 'foo', + }); + expect(url).toBeUndefined(); + }); + it('should return an url even when there is no object', async () => { + const url = await formatter.formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com', + object: undefined, + kind: 'foo', + }); + expect(url).toBe('https://k8s.foo.com'); + }); + it('should throw when the app is not recognized', async () => { + await expect( + formatter.formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com', + dashboardApp: 'unknownapp', + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).rejects.toThrow( + "Could not find Kubernetes dashboard app named 'unknownapp'", + ); + }); + + describe('default app', () => { + it('should use default app', async () => { + const url = await formatter.formatClusterLink({ + dashboardUrl: 'https://k8s.foo.com/', + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }); + expect(url).toBe(dashboardUrl.toString()); + }); + }); + }); +}); diff --git a/plugins/kubernetes-react/src/api/KubernetesClusterLinkFormatter.ts b/plugins/kubernetes-react/src/api/KubernetesClusterLinkFormatter.ts new file mode 100644 index 0000000000..45dd6e6fa1 --- /dev/null +++ b/plugins/kubernetes-react/src/api/KubernetesClusterLinkFormatter.ts @@ -0,0 +1,59 @@ +/* + * 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 { + KubernetesClusterLinkFormatterApi, + FormatClusterLinkOptions, +} from './types'; +import { ClusterLinksFormatter } from '../types'; + +/** @public */ +export class KubernetesClusterLinkFormatter + implements KubernetesClusterLinkFormatterApi +{ + private readonly formatters: Record; + private readonly defaultFormatterName: string; + + constructor(options: { + formatters: Record; + defaultFormatterName: string; + }) { + this.formatters = options.formatters; + this.defaultFormatterName = options.defaultFormatterName; + } + async formatClusterLink(options: FormatClusterLinkOptions) { + if (!options.dashboardUrl && !options.dashboardParameters) { + return undefined; + } + if (options.dashboardUrl && !options.object) { + return options.dashboardUrl; + } + const app = options.dashboardApp ?? this.defaultFormatterName; + const formatter = this.formatters[app]; + if (!formatter) { + throw new Error(`Could not find Kubernetes dashboard app named '${app}'`); + } + const url = await formatter.formatClusterLink({ + dashboardUrl: options.dashboardUrl + ? new URL(options.dashboardUrl) + : undefined, + dashboardParameters: options.dashboardParameters, + object: options.object, + kind: options.kind, + }); + return url.toString(); + } +} diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/aks.test.ts b/plugins/kubernetes-react/src/api/formatters/AksClusterLinksFormatter.test.ts similarity index 82% rename from plugins/kubernetes-react/src/utils/clusterLinks/formatters/aks.test.ts rename to plugins/kubernetes-react/src/api/formatters/AksClusterLinksFormatter.test.ts index 18db618686..b7bdc3d7e8 100644 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/aks.test.ts +++ b/plugins/kubernetes-react/src/api/formatters/AksClusterLinksFormatter.test.ts @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { aksFormatter } from './aks'; +import { AksClusterLinksFormatter } from './AksClusterLinksFormatter'; describe('clusterLinks - AKS formatter', () => { - it('should provide a dashboardParameters in the options', () => { - expect(() => - aksFormatter({ + const formatter = new AksClusterLinksFormatter(); + it('should provide a dashboardParameters in the options', async () => { + await expect(() => + formatter.formatClusterLink({ object: { metadata: { name: 'foobar', @@ -27,11 +28,11 @@ describe('clusterLinks - AKS formatter', () => { }, kind: 'Deployment', }), - ).toThrow('AKS dashboard requires a dashboardParameters option'); + ).rejects.toThrow('AKS dashboard requires a dashboardParameters option'); }); - it('should provide a subscriptionId in the dashboardParameters options', () => { - expect(() => - aksFormatter({ + it('should provide a subscriptionId in the dashboardParameters options', async () => { + await expect( + formatter.formatClusterLink({ dashboardParameters: { resourceGroup: 'rg-1', clusterName: 'cluster-1', @@ -44,13 +45,13 @@ describe('clusterLinks - AKS formatter', () => { }, kind: 'Deployment', }), - ).toThrow( + ).rejects.toThrow( 'AKS dashboard requires a "subscriptionId" of type string in the dashboardParameters option', ); }); - it('should provide a resourceGroup in the dashboardParameters options', () => { - expect(() => - aksFormatter({ + it('should provide a resourceGroup in the dashboardParameters options', async () => { + await expect( + formatter.formatClusterLink({ dashboardParameters: { subscriptionId: '1234-GUID-5678', clusterName: 'cluster-1', @@ -63,13 +64,13 @@ describe('clusterLinks - AKS formatter', () => { }, kind: 'Deployment', }), - ).toThrow( + ).rejects.toThrow( 'AKS dashboard requires a "resourceGroup" of type string in the dashboardParameters option', ); }); - it('should provide a clusterName in the dashboardParameters options', () => { - expect(() => - aksFormatter({ + it('should provide a clusterName in the dashboardParameters options', async () => { + await expect( + formatter.formatClusterLink({ dashboardParameters: { subscriptionId: '1234-GUID-5678', resourceGroup: 'us-east1-c', @@ -82,12 +83,12 @@ describe('clusterLinks - AKS formatter', () => { }, kind: 'Deployment', }), - ).toThrow( + ).rejects.toThrow( 'AKS dashboard requires a "clusterName" of type string in the dashboardParameters option', ); }); - it('should return an url on the cluster with object details', () => { - const url = aksFormatter({ + it('should return an url on the cluster with object details', async () => { + const url = await formatter.formatClusterLink({ dashboardParameters: { subscriptionId: '1234-GUID-5678', resourceGroup: 'rg-1', diff --git a/plugins/kubernetes-react/src/api/formatters/AksClusterLinksFormatter.ts b/plugins/kubernetes-react/src/api/formatters/AksClusterLinksFormatter.ts new file mode 100644 index 0000000000..1e7bb0ff9e --- /dev/null +++ b/plugins/kubernetes-react/src/api/formatters/AksClusterLinksFormatter.ts @@ -0,0 +1,59 @@ +/* + * 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 { + ClusterLinksFormatter, + ClusterLinksFormatterOptions, +} from '../../types'; + +const basePath = + 'https://portal.azure.com/#blade/Microsoft_Azure_ContainerService/AksK8ResourceMenuBlade/overview-Deployment/aksClusterId'; + +const requiredParams = ['subscriptionId', 'resourceGroup', 'clusterName']; + +/** @public */ +export class AksClusterLinksFormatter implements ClusterLinksFormatter { + async formatClusterLink(options: ClusterLinksFormatterOptions) { + if (!options.dashboardParameters) { + throw new Error('AKS dashboard requires a dashboardParameters option'); + } + const args = options.dashboardParameters; + for (const param of requiredParams) { + if (typeof args[param] !== 'string') { + throw new Error( + `AKS dashboard requires a "${param}" of type string in the dashboardParameters option`, + ); + } + } + + const path = `/subscriptions/${args.subscriptionId}/resourceGroups/${args.resourceGroup}/providers/Microsoft.ContainerService/managedClusters/${args.clusterName}`; + + const { name, namespace, uid } = options.object.metadata; + const { selector } = options.object.spec; + const params = { + kind: options.kind, + metadata: { name, namespace, uid }, + spec: { + selector, + }, + }; + + return new URL( + `${basePath}/${encodeURIComponent(path)}/resource/${encodeURIComponent( + JSON.stringify(params), + )}`, + ); + } +} diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/eks.test.ts b/plugins/kubernetes-react/src/api/formatters/EksClusterLinksFormatter.test.ts similarity index 75% rename from plugins/kubernetes-react/src/utils/clusterLinks/formatters/eks.test.ts rename to plugins/kubernetes-react/src/api/formatters/EksClusterLinksFormatter.test.ts index d0ae73753f..738da9fb94 100644 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/eks.test.ts +++ b/plugins/kubernetes-react/src/api/formatters/EksClusterLinksFormatter.test.ts @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { eksFormatter } from './eks'; + +import { EksClusterLinksFormatter } from './EksClusterLinksFormatter'; describe('clusterLinks - EKS formatter', () => { - it('should return an url on the workloads when there is a namespace only', () => { - expect(() => - eksFormatter({ + const formatter = new EksClusterLinksFormatter(); + it('should return an url on the workloads when there is a namespace only', async () => { + await expect( + formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com'), object: { metadata: { @@ -28,6 +30,8 @@ describe('clusterLinks - EKS formatter', () => { }, kind: 'Deployment', }), - ).toThrow('EKS formatter is not yet implemented. Please, contribute!'); + ).rejects.toThrow( + 'EKS formatter is not yet implemented. Please, contribute!', + ); }); }); diff --git a/plugins/kubernetes-react/src/api/formatters/EksClusterLinksFormatter.ts b/plugins/kubernetes-react/src/api/formatters/EksClusterLinksFormatter.ts new file mode 100644 index 0000000000..d65b619639 --- /dev/null +++ b/plugins/kubernetes-react/src/api/formatters/EksClusterLinksFormatter.ts @@ -0,0 +1,30 @@ +/* + * 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 { + ClusterLinksFormatter, + ClusterLinksFormatterOptions, +} from '../../types'; + +/** @public */ +export class EksClusterLinksFormatter implements ClusterLinksFormatter { + async formatClusterLink( + _options: ClusterLinksFormatterOptions, + ): Promise { + throw new Error( + 'EKS formatter is not yet implemented. Please, contribute!', + ); + } +} diff --git a/plugins/kubernetes-react/src/api/formatters/GkeClusterLinksFormatter.test.ts b/plugins/kubernetes-react/src/api/formatters/GkeClusterLinksFormatter.test.ts new file mode 100644 index 0000000000..9b9e707a6c --- /dev/null +++ b/plugins/kubernetes-react/src/api/formatters/GkeClusterLinksFormatter.test.ts @@ -0,0 +1,264 @@ +/* + * 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 { GkeClusterLinksFormatter } from './GkeClusterLinksFormatter'; + +describe('clusterLinks - GKE formatter', () => { + describe('without googleAuthApi provided', () => { + const formatter = new GkeClusterLinksFormatter(undefined); + + it('should provide a dashboardParameters in the options', async () => { + await expect( + formatter.formatClusterLink({ + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).rejects.toThrow('GKE dashboard requires a dashboardParameters option'); + }); + it('should provide a projectId in the dashboardParameters options', async () => { + await expect( + formatter.formatClusterLink({ + dashboardParameters: { + region: 'us-east1-c', + clusterName: 'cluster-1', + }, + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).rejects.toThrow( + 'GKE dashboard requires a "projectId" of type string in the dashboardParameters option', + ); + }); + it('should provide a region in the dashboardParameters options', async () => { + await expect( + formatter.formatClusterLink({ + dashboardParameters: { + projectId: 'foobar-333614', + clusterName: 'cluster-1', + }, + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).rejects.toThrow( + 'GKE dashboard requires a "region" of type string in the dashboardParameters option', + ); + }); + it('should provide a clusterName in the dashboardParameters options', async () => { + await expect(() => + formatter.formatClusterLink({ + dashboardParameters: { + projectId: 'foobar-333614', + region: 'us-east1-c', + }, + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).rejects.toThrow( + 'GKE dashboard requires a "clusterName" of type string in the dashboardParameters option', + ); + }); + it('should return an url on the cluster when there is a namespace only', async () => { + const url = await formatter.formatClusterLink({ + dashboardParameters: { + projectId: 'foobar-333614', + region: 'us-east1-c', + clusterName: 'cluster-1', + }, + object: { + metadata: { + namespace: 'bar', + }, + }, + kind: 'foo', + }); + expect(url.href).toBe( + 'https://console.cloud.google.com/kubernetes/clusters/details/us-east1-c/cluster-1/details?project=foobar-333614', + ); + }); + it('should return an url on the cluster when the kind is not recognized', async () => { + const url = await formatter.formatClusterLink({ + dashboardParameters: { + projectId: 'foobar-333614', + region: 'us-east1-c', + clusterName: 'cluster-1', + }, + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'UnknownKind', + }); + expect(url.href).toBe( + 'https://console.cloud.google.com/kubernetes/clusters/details/us-east1-c/cluster-1/details?project=foobar-333614', + ); + }); + it('should return an url on the deployment', async () => { + const url = await formatter.formatClusterLink({ + dashboardParameters: { + projectId: 'foobar-333614', + region: 'us-east1-c', + clusterName: 'cluster-1', + }, + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }); + expect(url.href).toBe( + 'https://console.cloud.google.com/kubernetes/deployment/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614', + ); + }); + + it('should return an url on the service', async () => { + const url = await formatter.formatClusterLink({ + dashboardParameters: { + projectId: 'foobar-333614', + region: 'us-east1-c', + clusterName: 'cluster-1', + }, + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Service', + }); + expect(url.href).toBe( + 'https://console.cloud.google.com/kubernetes/service/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614', + ); + }); + it('should return an url on the pod', async () => { + const url = await formatter.formatClusterLink({ + dashboardParameters: { + projectId: 'foobar-333614', + region: 'us-east1-c', + clusterName: 'cluster-1', + }, + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Pod', + }); + expect(url.href).toBe( + 'https://console.cloud.google.com/kubernetes/pod/us-east1-c/cluster-1/bar/foobar/details?project=foobar-333614', + ); + }); + it('should return an url on the ingress', async () => { + const url = await formatter.formatClusterLink({ + dashboardParameters: { + projectId: 'foobar-333614', + region: 'us-east1-c', + clusterName: 'cluster-1', + }, + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Ingress', + }); + expect(url.href).toBe( + 'https://console.cloud.google.com/kubernetes/ingress/us-east1-c/cluster-1/bar/foobar/details?project=foobar-333614', + ); + }); + it('should return an url on the deployment for a hpa', async () => { + const url = await formatter.formatClusterLink({ + dashboardParameters: { + projectId: 'foobar-333614', + region: 'us-east1-c', + clusterName: 'cluster-1', + }, + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'HorizontalPodAutoscaler', + }); + expect(url.href).toBe( + 'https://console.cloud.google.com/kubernetes/deployment/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614', + ); + }); + }); + describe('with googleAuthApi provided', () => { + const object = { + dashboardParameters: { + projectId: 'foobar-333614', + region: 'us-east1-c', + clusterName: 'cluster-1', + }, + object: { + metadata: { + namespace: 'bar', + }, + }, + kind: 'foo', + }; + it('should not fail, when undefined is returned as profile', async () => { + const formatter = new GkeClusterLinksFormatter({ + getProfile: async _ => undefined, + }); + const url = await formatter.formatClusterLink(object); + expect(url.searchParams.has('authuser')).toBeFalsy(); + }); + it('should not fail, when profile returns no email', async () => { + const formatter = new GkeClusterLinksFormatter({ + getProfile: async _ => ({ email: undefined }), + }); + const url = await formatter.formatClusterLink(object); + expect(url.searchParams.has('authuser')).toBeFalsy(); + }); + it('should add authuser with email when provided', async () => { + const email = 'email@example.com'; + const formatter = new GkeClusterLinksFormatter({ + getProfile: async _ => ({ email }), + }); + const url = await formatter.formatClusterLink(object); + expect(url.searchParams.get('authuser')).toBe(email); + }); + }); +}); diff --git a/plugins/kubernetes-react/src/api/formatters/GkeClusterLinksFormatter.ts b/plugins/kubernetes-react/src/api/formatters/GkeClusterLinksFormatter.ts new file mode 100644 index 0000000000..4bf3c28f45 --- /dev/null +++ b/plugins/kubernetes-react/src/api/formatters/GkeClusterLinksFormatter.ts @@ -0,0 +1,81 @@ +/* + * 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 { + ClusterLinksFormatter, + ClusterLinksFormatterOptions, +} from '../../types'; +import { ProfileInfoApi } from '@backstage/core-plugin-api'; + +const kindMappings: Record = { + deployment: 'deployment', + pod: 'pod', + ingress: 'ingress', + service: 'service', + horizontalpodautoscaler: 'deployment', +}; + +/** @public */ +export class GkeClusterLinksFormatter implements ClusterLinksFormatter { + constructor(private readonly googleAuthApi: ProfileInfoApi | undefined) {} + async formatClusterLink(options: ClusterLinksFormatterOptions): Promise { + if (!options.dashboardParameters) { + throw new Error('GKE dashboard requires a dashboardParameters option'); + } + const args = options.dashboardParameters; + if (typeof args.projectId !== 'string') { + throw new Error( + 'GKE dashboard requires a "projectId" of type string in the dashboardParameters option', + ); + } + if (typeof args.region !== 'string') { + throw new Error( + 'GKE dashboard requires a "region" of type string in the dashboardParameters option', + ); + } + if (typeof args.clusterName !== 'string') { + throw new Error( + 'GKE dashboard requires a "clusterName" of type string in the dashboardParameters option', + ); + } + const basePath = new URL('https://console.cloud.google.com/'); + const region = encodeURIComponent(args.region); + const clusterName = encodeURIComponent(args.clusterName); + const name = encodeURIComponent(options.object.metadata?.name ?? ''); + const namespace = encodeURIComponent( + options.object.metadata?.namespace ?? '', + ); + const validKind = kindMappings[options.kind.toLocaleLowerCase('en-US')]; + let path: string; + if (namespace && name && validKind) { + const kindsWithDetails = ['ingress', 'pod']; + const landingPage = kindsWithDetails.includes(validKind) + ? 'details' + : 'overview'; + path = `kubernetes/${validKind}/${region}/${clusterName}/${namespace}/${name}/${landingPage}`; + } else { + path = `kubernetes/clusters/details/${region}/${clusterName}/details`; + } + const result = new URL(path, basePath); + result.searchParams.set('project', args.projectId); + if (this.googleAuthApi) { + const profile = await this.googleAuthApi.getProfile({ optional: true }); + if (profile?.email) { + result.searchParams.set('authuser', profile.email); + } + } + return result; + } +} diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/openshift.test.ts b/plugins/kubernetes-react/src/api/formatters/OpenshiftClusterLinksFormatter.test.ts similarity index 75% rename from plugins/kubernetes-react/src/utils/clusterLinks/formatters/openshift.test.ts rename to plugins/kubernetes-react/src/api/formatters/OpenshiftClusterLinksFormatter.test.ts index 945553e340..1adb688757 100644 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/openshift.test.ts +++ b/plugins/kubernetes-react/src/api/formatters/OpenshiftClusterLinksFormatter.test.ts @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { openshiftFormatter } from './openshift'; + +import { OpenshiftClusterLinksFormatter } from './OpenshiftClusterLinksFormatter'; describe('clusterLinks - OpenShift formatter', () => { - it('should provide a dashboardUrl in the options', () => { - expect(() => - openshiftFormatter({ + const formatter = new OpenshiftClusterLinksFormatter(); + it('should provide a dashboardUrl in the options', async () => { + await expect(() => + formatter.formatClusterLink({ object: { metadata: { name: 'foobar', @@ -27,10 +29,10 @@ describe('clusterLinks - OpenShift formatter', () => { }, kind: 'Deployment', }), - ).toThrow('OpenShift dashboard requires a dashboardUrl option'); + ).rejects.toThrow('OpenShift dashboard requires a dashboardUrl option'); }); - it('should return an url on the workloads when there is a namespace only', () => { - const url = openshiftFormatter({ + it('should return an url on the workloads when there is a namespace only', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com'), object: { metadata: { @@ -41,8 +43,8 @@ describe('clusterLinks - OpenShift formatter', () => { }); expect(url.href).toBe('https://k8s.foo.com/k8s/cluster/projects/bar'); }); - it('should return an url on the workloads when the kind is not recognizeed', () => { - const url = openshiftFormatter({ + it('should return an url on the workloads when the kind is not recognizeed', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com'), object: { metadata: { @@ -54,8 +56,8 @@ describe('clusterLinks - OpenShift formatter', () => { }); expect(url.href).toBe('https://k8s.foo.com/k8s/cluster/projects/bar'); }); - it('should return an url on the deployment', () => { - const url = openshiftFormatter({ + it('should return an url on the deployment', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -67,8 +69,8 @@ describe('clusterLinks - OpenShift formatter', () => { }); expect(url.href).toBe('https://k8s.foo.com/k8s/ns/bar/deployments/foobar'); }); - it('should return an url on the deployment and keep the path prefix 1', () => { - const url = openshiftFormatter({ + it('should return an url on the deployment and keep the path prefix 1', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/some/prefix/'), object: { metadata: { @@ -82,8 +84,8 @@ describe('clusterLinks - OpenShift formatter', () => { 'https://k8s.foo.com/some/prefix/k8s/ns/bar/deployments/foobar', ); }); - it('should return an url on the deployment and keep the path prefix 2', () => { - const url = openshiftFormatter({ + it('should return an url on the deployment and keep the path prefix 2', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/some/prefix'), object: { metadata: { @@ -97,8 +99,8 @@ describe('clusterLinks - OpenShift formatter', () => { 'https://k8s.foo.com/some/prefix/k8s/ns/bar/deployments/foobar', ); }); - it('should return an url on the service', () => { - const url = openshiftFormatter({ + it('should return an url on the service', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -110,8 +112,8 @@ describe('clusterLinks - OpenShift formatter', () => { }); expect(url.href).toBe('https://k8s.foo.com/k8s/ns/bar/services/foobar'); }); - it('should return an url on the ingress', () => { - const url = openshiftFormatter({ + it('should return an url on the ingress', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -123,8 +125,8 @@ describe('clusterLinks - OpenShift formatter', () => { }); expect(url.href).toBe('https://k8s.foo.com/k8s/ns/bar/ingresses/foobar'); }); - it('should return an url on the deployment for a hpa', () => { - const url = openshiftFormatter({ + it('should return an url on the deployment for a hpa', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -138,8 +140,8 @@ describe('clusterLinks - OpenShift formatter', () => { 'https://k8s.foo.com/k8s/ns/bar/horizontalpodautoscalers/foobar', ); }); - it('should return an url on the PV', () => { - const url = openshiftFormatter({ + it('should return an url on the PV', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { diff --git a/plugins/kubernetes-react/src/api/formatters/OpenshiftClusterLinksFormatter.ts b/plugins/kubernetes-react/src/api/formatters/OpenshiftClusterLinksFormatter.ts new file mode 100644 index 0000000000..228ff3d1ac --- /dev/null +++ b/plugins/kubernetes-react/src/api/formatters/OpenshiftClusterLinksFormatter.ts @@ -0,0 +1,60 @@ +/* + * 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 { ClusterLinksFormatterOptions } from '../../types'; + +const kindMappings: Record = { + deployment: 'deployments', + ingress: 'ingresses', + service: 'services', + horizontalpodautoscaler: 'horizontalpodautoscalers', + persistentvolume: 'persistentvolumes', +}; + +/** @public */ +export class OpenshiftClusterLinksFormatter { + async formatClusterLink(options: ClusterLinksFormatterOptions): Promise { + if (!options.dashboardUrl) { + throw new Error('OpenShift dashboard requires a dashboardUrl option'); + } + const basePath = new URL(options.dashboardUrl.href); + const name = encodeURIComponent(options.object.metadata?.name ?? ''); + const namespace = encodeURIComponent( + options.object.metadata?.namespace ?? '', + ); + const validKind = kindMappings[options.kind.toLocaleLowerCase('en-US')]; + if (!basePath.pathname.endsWith('/')) { + // a dashboard url with a path should end with a slash otherwise + // the new combined URL will replace the last segment with the appended path! + // https://foobar.com/abc/def + k8s/cluster/projects/test --> https://foobar.com/abc/k8s/cluster/projects/test + // https://foobar.com/abc/def/ + k8s/cluster/projects/test --> https://foobar.com/abc/def/k8s/cluster/projects/test + basePath.pathname += '/'; + } + let path = ''; + if (namespace) { + if (name && validKind) { + path = `k8s/ns/${namespace}/${validKind}/${name}`; + } else { + path = `k8s/cluster/projects/${namespace}`; + } + } else if (validKind) { + path = `k8s/cluster/${validKind}`; + if (name) { + path += `/${name}`; + } + } + return new URL(path, basePath); + } +} diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/rancher.test.ts b/plugins/kubernetes-react/src/api/formatters/RancherClusterLinksFormatter.test.ts similarity index 74% rename from plugins/kubernetes-react/src/utils/clusterLinks/formatters/rancher.test.ts rename to plugins/kubernetes-react/src/api/formatters/RancherClusterLinksFormatter.test.ts index bb26da3ab0..228340eb16 100644 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/rancher.test.ts +++ b/plugins/kubernetes-react/src/api/formatters/RancherClusterLinksFormatter.test.ts @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { rancherFormatter } from './rancher'; + +import { RancherClusterLinksFormatter } from './RancherClusterLinksFormatter'; describe('clusterLinks - rancher formatter', () => { - it('should provide a dashboardUrl in the options', () => { - expect(() => - rancherFormatter({ + const formatter = new RancherClusterLinksFormatter(); + it('should provide a dashboardUrl in the options', async () => { + await expect(() => + formatter.formatClusterLink({ object: { metadata: { name: 'foobar', @@ -27,10 +29,10 @@ describe('clusterLinks - rancher formatter', () => { }, kind: 'Deployment', }), - ).toThrow('Rancher dashboard requires a dashboardUrl option'); + ).rejects.toThrow('Rancher dashboard requires a dashboardUrl option'); }); - it('should return a url on the workloads when there is a namespace only', () => { - const url = rancherFormatter({ + it('should return a url on the workloads when there is a namespace only', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com'), object: { metadata: { @@ -41,8 +43,8 @@ describe('clusterLinks - rancher formatter', () => { }); expect(url.href).toBe('https://k8s.foo.com/explorer/workload'); }); - it('should return a url on the workloads when the kind is not recognized', () => { - const url = rancherFormatter({ + it('should return a url on the workloads when the kind is not recognized', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com'), object: { metadata: { @@ -54,8 +56,8 @@ describe('clusterLinks - rancher formatter', () => { }); expect(url.href).toBe('https://k8s.foo.com/explorer/workload'); }); - it('should return a url on the deployment', () => { - const url = rancherFormatter({ + it('should return a url on the deployment', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -69,8 +71,8 @@ describe('clusterLinks - rancher formatter', () => { 'https://k8s.foo.com/explorer/apps.deployment/bar/foobar', ); }); - it('should return a url on the service', () => { - const url = rancherFormatter({ + it('should return a url on the service', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -82,8 +84,8 @@ describe('clusterLinks - rancher formatter', () => { }); expect(url.href).toBe('https://k8s.foo.com/explorer/service/bar/foobar'); }); - it('should return a url on the ingress', () => { - const url = rancherFormatter({ + it('should return a url on the ingress', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -97,8 +99,8 @@ describe('clusterLinks - rancher formatter', () => { 'https://k8s.foo.com/explorer/networking.k8s.io.ingress/bar/foobar', ); }); - it('should return a url on the deployment for a hpa', () => { - const url = rancherFormatter({ + it('should return a url on the deployment for a hpa', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -112,8 +114,8 @@ describe('clusterLinks - rancher formatter', () => { 'https://k8s.foo.com/explorer/autoscaling.horizontalpodautoscaler/bar/foobar', ); }); - it('should support subpaths in dashboardUrl', () => { - const url = rancherFormatter({ + it('should support subpaths in dashboardUrl', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/dashboard/c/c-28a4b/'), object: { metadata: { diff --git a/plugins/kubernetes-react/src/api/formatters/RancherClusterLinksFormatter.ts b/plugins/kubernetes-react/src/api/formatters/RancherClusterLinksFormatter.ts new file mode 100644 index 0000000000..e607d98972 --- /dev/null +++ b/plugins/kubernetes-react/src/api/formatters/RancherClusterLinksFormatter.ts @@ -0,0 +1,55 @@ +/* + * 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 { + ClusterLinksFormatter, + ClusterLinksFormatterOptions, +} from '../../types'; + +const kindMappings: Record = { + deployment: 'apps.deployment', + ingress: 'networking.k8s.io.ingress', + service: 'service', + horizontalpodautoscaler: 'autoscaling.horizontalpodautoscaler', +}; + +/** @public */ +export class RancherClusterLinksFormatter implements ClusterLinksFormatter { + async formatClusterLink(options: ClusterLinksFormatterOptions): Promise { + if (!options.dashboardUrl) { + throw new Error('Rancher dashboard requires a dashboardUrl option'); + } + const basePath = new URL(options.dashboardUrl.href); + const name = encodeURIComponent(options.object.metadata?.name ?? ''); + const namespace = encodeURIComponent( + options.object.metadata?.namespace ?? '', + ); + const validKind = kindMappings[options.kind.toLocaleLowerCase('en-US')]; + if (!basePath.pathname.endsWith('/')) { + // a dashboard url with a path should end with a slash otherwise + // the new combined URL will replace the last segment with the appended path! + // https://foobar.com/abc/def + explorer/service/test --> https://foobar.com/abc/explorer/service/test + // https://foobar.com/abc/def/ + explorer/service/test --> https://foobar.com/abc/def/explorer/service/test + basePath.pathname += '/'; + } + let path = ''; + if (validKind && name && namespace) { + path = `explorer/${validKind}/${namespace}/${name}`; + } else if (namespace) { + path = 'explorer/workload'; + } + return new URL(path, basePath); + } +} diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/standard.test.ts b/plugins/kubernetes-react/src/api/formatters/StandardClusterLinksFormatter.test.ts similarity index 76% rename from plugins/kubernetes-react/src/utils/clusterLinks/formatters/standard.test.ts rename to plugins/kubernetes-react/src/api/formatters/StandardClusterLinksFormatter.test.ts index 280de6bfde..fe1b2be4f5 100644 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/standard.test.ts +++ b/plugins/kubernetes-react/src/api/formatters/StandardClusterLinksFormatter.test.ts @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { standardFormatter } from './standard'; + +import { StandardClusterLinksFormatter } from './StandardClusterLinksFormatter'; function formatUrl(url: URL) { // Note that we can't rely on 'url.href' since it will put the search before the hash @@ -23,9 +24,10 @@ function formatUrl(url: URL) { } describe('clusterLinks - standard formatter', () => { - it('should provide a dashboardUrl in the options', () => { - expect(() => - standardFormatter({ + const formatter = new StandardClusterLinksFormatter(); + it('should provide a dashboardUrl in the options', async () => { + await expect(() => + formatter.formatClusterLink({ object: { metadata: { name: 'foobar', @@ -34,10 +36,10 @@ describe('clusterLinks - standard formatter', () => { }, kind: 'Deployment', }), - ).toThrow('standard dashboard requires a dashboardUrl option'); + ).rejects.toThrow('standard dashboard requires a dashboardUrl option'); }); - it('should return an url on the workloads when there is a namespace only', () => { - const url = standardFormatter({ + it('should return an url on the workloads when there is a namespace only', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com'), object: { metadata: { @@ -50,8 +52,8 @@ describe('clusterLinks - standard formatter', () => { 'https://k8s.foo.com/#/workloads?namespace=bar', ); }); - it('should return an url on the workloads when the kind is not recognizeed', () => { - const url = standardFormatter({ + it('should return an url on the workloads when the kind is not recognizeed', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com'), object: { metadata: { @@ -65,8 +67,8 @@ describe('clusterLinks - standard formatter', () => { 'https://k8s.foo.com/#/workloads?namespace=bar', ); }); - it('should return an url on the deployment', () => { - const url = standardFormatter({ + it('should return an url on the deployment', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -80,8 +82,8 @@ describe('clusterLinks - standard formatter', () => { 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', ); }); - it('should return an url on the pod', () => { - const url = standardFormatter({ + it('should return an url on the pod', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -95,8 +97,8 @@ describe('clusterLinks - standard formatter', () => { 'https://k8s.foo.com/#/pod/bar/foobar?namespace=bar', ); }); - it('should return an url on the deployment with a prefix 1', () => { - const url = standardFormatter({ + it('should return an url on the deployment with a prefix 1', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/some/prefix'), object: { metadata: { @@ -110,8 +112,8 @@ describe('clusterLinks - standard formatter', () => { 'https://k8s.foo.com/some/prefix/#/deployment/bar/foobar?namespace=bar', ); }); - it('should return an url on the deployment with a prefix 2', () => { - const url = standardFormatter({ + it('should return an url on the deployment with a prefix 2', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/some/prefix/'), object: { metadata: { @@ -125,8 +127,8 @@ describe('clusterLinks - standard formatter', () => { 'https://k8s.foo.com/some/prefix/#/deployment/bar/foobar?namespace=bar', ); }); - it('should return an url on the deployment properly url encoded', () => { - const url = standardFormatter({ + it('should return an url on the deployment properly url encoded', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -140,8 +142,8 @@ describe('clusterLinks - standard formatter', () => { 'https://k8s.foo.com/#/deployment/bar%20bar/foobar?namespace=bar%20bar', ); }); - it('should return an url on the service', () => { - const url = standardFormatter({ + it('should return an url on the service', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -155,8 +157,8 @@ describe('clusterLinks - standard formatter', () => { 'https://k8s.foo.com/#/service/bar/foobar?namespace=bar', ); }); - it('should return an url on the ingress', () => { - const url = standardFormatter({ + it('should return an url on the ingress', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { @@ -170,8 +172,8 @@ describe('clusterLinks - standard formatter', () => { 'https://k8s.foo.com/#/ingress/bar/foobar?namespace=bar', ); }); - it('should return an url on the deployment for a hpa', () => { - const url = standardFormatter({ + it('should return an url on the deployment for a hpa', async () => { + const url = await formatter.formatClusterLink({ dashboardUrl: new URL('https://k8s.foo.com/'), object: { metadata: { diff --git a/plugins/kubernetes-react/src/api/formatters/StandardClusterLinksFormatter.ts b/plugins/kubernetes-react/src/api/formatters/StandardClusterLinksFormatter.ts new file mode 100644 index 0000000000..76537c5a4e --- /dev/null +++ b/plugins/kubernetes-react/src/api/formatters/StandardClusterLinksFormatter.ts @@ -0,0 +1,56 @@ +/* + * 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 { + ClusterLinksFormatter, + ClusterLinksFormatterOptions, +} from '../../types'; + +const kindMappings: Record = { + deployment: 'deployment', + pod: 'pod', + ingress: 'ingress', + service: 'service', + horizontalpodautoscaler: 'deployment', + statefulset: 'statefulset', +}; + +/** @public */ +export class StandardClusterLinksFormatter implements ClusterLinksFormatter { + async formatClusterLink(options: ClusterLinksFormatterOptions): Promise { + if (!options.dashboardUrl) { + throw new Error('standard dashboard requires a dashboardUrl option'); + } + const result = new URL(options.dashboardUrl.href); + const name = encodeURIComponent(options.object.metadata?.name ?? ''); + const namespace = encodeURIComponent( + options.object.metadata?.namespace ?? '', + ); + const validKind = kindMappings[options.kind.toLocaleLowerCase('en-US')]; + if (!result.pathname.endsWith('/')) { + result.pathname += '/'; + } + if (validKind && name && namespace) { + result.hash = `/${validKind}/${namespace}/${name}`; + } else if (namespace) { + result.hash = '/workloads'; + } + if (namespace) { + // Note that Angular SPA requires a hash and the query parameter should be part of it + result.hash += `?namespace=${namespace}`; + } + return result; + } +} diff --git a/plugins/kubernetes-react/src/api/formatters/index.ts b/plugins/kubernetes-react/src/api/formatters/index.ts new file mode 100644 index 0000000000..c4bf52d181 --- /dev/null +++ b/plugins/kubernetes-react/src/api/formatters/index.ts @@ -0,0 +1,50 @@ +/* + * 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 { AksClusterLinksFormatter } from './AksClusterLinksFormatter'; +import { ClusterLinksFormatter } from '../../types'; +import { EksClusterLinksFormatter } from './EksClusterLinksFormatter'; +import { GkeClusterLinksFormatter } from './GkeClusterLinksFormatter'; +import { StandardClusterLinksFormatter } from './StandardClusterLinksFormatter'; +import { OpenshiftClusterLinksFormatter } from './OpenshiftClusterLinksFormatter'; +import { RancherClusterLinksFormatter } from './RancherClusterLinksFormatter'; +import { ProfileInfoApi } from '@backstage/core-plugin-api'; + +export { + StandardClusterLinksFormatter, + AksClusterLinksFormatter, + EksClusterLinksFormatter, + GkeClusterLinksFormatter, + OpenshiftClusterLinksFormatter, + RancherClusterLinksFormatter, +}; + +/** @public */ +export const DEFAULT_FORMATTER_NAME = 'standard'; + +/** @public */ +export function getDefaultFormatters(deps: { + googleAuthApi: ProfileInfoApi; +}): Record { + return { + standard: new StandardClusterLinksFormatter(), + aks: new AksClusterLinksFormatter(), + eks: new EksClusterLinksFormatter(), + gke: new GkeClusterLinksFormatter(deps.googleAuthApi), + openshift: new OpenshiftClusterLinksFormatter(), + rancher: new RancherClusterLinksFormatter(), + }; +} diff --git a/plugins/kubernetes-react/src/api/index.ts b/plugins/kubernetes-react/src/api/index.ts index ec8843937a..42fbcf1618 100644 --- a/plugins/kubernetes-react/src/api/index.ts +++ b/plugins/kubernetes-react/src/api/index.ts @@ -14,7 +14,18 @@ * limitations under the License. */ -export { kubernetesApiRef, kubernetesProxyApiRef } from './types'; -export type { KubernetesApi, KubernetesProxyApi } from './types'; +export { + kubernetesApiRef, + kubernetesProxyApiRef, + kubernetesClusterLinkFormatterApiRef, +} from './types'; +export type { + KubernetesApi, + KubernetesProxyApi, + FormatClusterLinkOptions, + KubernetesClusterLinkFormatterApi, +} from './types'; export { KubernetesBackendClient } from './KubernetesBackendClient'; +export { KubernetesClusterLinkFormatter } from './KubernetesClusterLinkFormatter'; export { KubernetesProxyClient } from './KubernetesProxyClient'; +export * from './formatters'; diff --git a/plugins/kubernetes-react/src/api/types.ts b/plugins/kubernetes-react/src/api/types.ts index 28cf2dde55..cc27bf9dd4 100644 --- a/plugins/kubernetes-react/src/api/types.ts +++ b/plugins/kubernetes-react/src/api/types.ts @@ -22,6 +22,7 @@ import { } from '@backstage/plugin-kubernetes-common'; import { createApiRef } from '@backstage/core-plugin-api'; import { Event } from 'kubernetes-models/v1'; +import { JsonObject } from '@backstage/types'; /** @public */ export const kubernetesApiRef = createApiRef({ @@ -33,6 +34,12 @@ export const kubernetesProxyApiRef = createApiRef({ id: 'plugin.kubernetes.proxy-service', }); +/** @public */ +export const kubernetesClusterLinkFormatterApiRef = + createApiRef({ + id: 'plugin.kubernetes.cluster-link-formatter-service', + }); + /** @public */ export interface KubernetesApi { getObjectsByEntity( @@ -82,3 +89,21 @@ export interface KubernetesProxyApi { namespace: string; }): Promise; } + +/** + * @public + */ +export type FormatClusterLinkOptions = { + dashboardUrl?: string; + dashboardApp?: string; + dashboardParameters?: JsonObject; + object: any; + kind: string; +}; + +/** @public */ +export interface KubernetesClusterLinkFormatterApi { + formatClusterLink( + options: FormatClusterLinkOptions, + ): Promise; +} diff --git a/plugins/kubernetes-react/src/components/CronJobsAccordions/CronJobsDrawer.test.tsx b/plugins/kubernetes-react/src/components/CronJobsAccordions/CronJobsDrawer.test.tsx index b16e83ee50..75506476ea 100644 --- a/plugins/kubernetes-react/src/components/CronJobsAccordions/CronJobsDrawer.test.tsx +++ b/plugins/kubernetes-react/src/components/CronJobsAccordions/CronJobsDrawer.test.tsx @@ -15,16 +15,20 @@ */ import React from 'react'; import * as oneCronJobsFixture from '../../__fixtures__/1-cronjobs.json'; -import { renderInTestApp } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { CronJobDrawer } from './CronJobsDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; describe('CronJobDrawer', () => { it('should render cronJob drawer', async () => { const { getByText, getAllByText } = await renderInTestApp( - , + + + , + , ); expect(getAllByText('dice-roller-cronjob')).toHaveLength(2); diff --git a/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentDrawer.test.tsx b/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentDrawer.test.tsx index f221a36cee..1e8515f81a 100644 --- a/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentDrawer.test.tsx +++ b/plugins/kubernetes-react/src/components/DeploymentsAccordions/DeploymentDrawer.test.tsx @@ -16,16 +16,24 @@ import React from 'react'; import * as deployments from '../../__fixtures__/2-deployments.json'; -import { renderInTestApp, textContentMatcher } from '@backstage/test-utils'; +import { + renderInTestApp, + TestApiProvider, + textContentMatcher, +} from '@backstage/test-utils'; import { DeploymentDrawer } from './DeploymentDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; describe('DeploymentDrawer', () => { it('should render deployment drawer', async () => { const { getByText, getAllByText } = await renderInTestApp( - , + + + , + , ); expect(getAllByText('dice-roller')).toHaveLength(2); @@ -53,13 +61,16 @@ describe('DeploymentDrawer', () => { it('should render deployment drawer without namespace', async () => { const deployment = (deployments as any).deployments[0]; const { queryByText } = await renderInTestApp( - , + + + , + , ); expect(queryByText('namespace: default')).not.toBeInTheDocument(); diff --git a/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.test.tsx b/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.test.tsx index beb0db1e2b..6599a2041f 100644 --- a/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.test.tsx +++ b/plugins/kubernetes-react/src/components/HorizontalPodAutoscalers/HorizontalPodAutoscalerDrawer.test.tsx @@ -17,15 +17,18 @@ import React from 'react'; import { screen } from '@testing-library/react'; import * as hpas from './__fixtures__/horizontalpodautoscalers.json'; -import { renderInTestApp } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { HorizontalPodAutoscalerDrawer } from './HorizontalPodAutoscalerDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; describe('HorizontalPodAutoscalersDrawer', () => { it('should render hpa drawer', async () => { await renderInTestApp( - -

    CHILD

    -
    , + + +

    CHILD

    +
    +
    , ); expect(screen.getByText('dice-roller')).toBeInTheDocument(); diff --git a/plugins/kubernetes-react/src/components/IngressesAccordions/IngressDrawer.test.tsx b/plugins/kubernetes-react/src/components/IngressesAccordions/IngressDrawer.test.tsx index 8619e362b7..cfacb24ee4 100644 --- a/plugins/kubernetes-react/src/components/IngressesAccordions/IngressDrawer.test.tsx +++ b/plugins/kubernetes-react/src/components/IngressesAccordions/IngressDrawer.test.tsx @@ -14,16 +14,23 @@ * limitations under the License. */ -import { renderInTestApp, textContentMatcher } from '@backstage/test-utils'; +import { + renderInTestApp, + TestApiProvider, + textContentMatcher, +} from '@backstage/test-utils'; import { screen } from '@testing-library/react'; import React from 'react'; import { IngressDrawer } from './IngressDrawer'; import * as ingresses from './__fixtures__/2-ingresses.json'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; describe('IngressDrawer', () => { it('should render ingress drawer', async () => { await renderInTestApp( - , + + + , ); expect(screen.getAllByText('awesome-service')).toHaveLength(4); diff --git a/plugins/kubernetes-react/src/components/JobsAccordions/JobsDrawer.test.tsx b/plugins/kubernetes-react/src/components/JobsAccordions/JobsDrawer.test.tsx index 48a4e6fe7e..23f49a2118 100644 --- a/plugins/kubernetes-react/src/components/JobsAccordions/JobsDrawer.test.tsx +++ b/plugins/kubernetes-react/src/components/JobsAccordions/JobsDrawer.test.tsx @@ -15,13 +15,16 @@ */ import React from 'react'; import * as oneCronJobsFixture from '../../__fixtures__/1-cronjobs.json'; -import { renderInTestApp } from '@backstage/test-utils'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { JobDrawer } from './JobsDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; describe('JobDrawer', () => { it('should render job drawer', async () => { const { getByText, getAllByText } = await renderInTestApp( - , + + , + , ); expect(getAllByText('dice-roller-cronjob-1637025000')).toHaveLength(2); diff --git a/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesStructuredMetadataTableDrawer.tsx b/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesStructuredMetadataTableDrawer.tsx index 65001c658e..0b6cfa26b4 100644 --- a/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesStructuredMetadataTableDrawer.tsx +++ b/plugins/kubernetes-react/src/components/KubernetesDrawer/KubernetesStructuredMetadataTableDrawer.tsx @@ -37,10 +37,11 @@ import { WarningPanel, } from '@backstage/core-components'; import { ClusterContext } from '../../hooks'; -import { formatClusterLink } from '../../utils/clusterLinks'; import { ClusterAttributes } from '@backstage/plugin-kubernetes-common'; -import { FormatClusterLinkOptions } from '../../utils/clusterLinks/formatClusterLink'; import { ManifestYaml } from './ManifestYaml'; +import { useApi } from '@backstage/core-plugin-api'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; +import useAsync from 'react-use/lib/useAsync'; const useDrawerStyles = makeStyles((theme: Theme) => createStyles({ @@ -147,20 +148,6 @@ function replaceNullsWithUndefined(someObj: any) { return JSON.parse(JSON.stringify(someObj, replacer)); } -function tryFormatClusterLink(options: FormatClusterLinkOptions) { - try { - return { - clusterLink: formatClusterLink(options), - errorMessage: '', - }; - } catch (err) { - return { - clusterLink: '', - errorMessage: err.message || err.toString(), - }; - } -} - const KubernetesStructuredMetadataTableDrawerContent = < T extends KubernetesDrawerable, >({ @@ -171,15 +158,20 @@ const KubernetesStructuredMetadataTableDrawerContent = < }: KubernetesStructuredMetadataTableDrawerContentProps) => { const [isYaml, setIsYaml] = useState(false); + const formatter = useApi(kubernetesClusterLinkFormatterApiRef); const classes = useDrawerContentStyles(); const cluster = useContext(ClusterContext); - const { clusterLink, errorMessage } = tryFormatClusterLink({ - dashboardUrl: cluster.dashboardUrl, - dashboardApp: cluster.dashboardApp, - dashboardParameters: cluster.dashboardParameters, - object, - kind, - }); + const { value: clusterLink, error } = useAsync( + async () => + formatter.formatClusterLink({ + dashboardUrl: cluster.dashboardUrl, + dashboardApp: cluster.dashboardApp, + dashboardParameters: cluster.dashboardParameters, + object, + kind, + }), + [cluster, object, kind, formatter], + ); return ( <> @@ -221,9 +213,12 @@ const KubernetesStructuredMetadataTableDrawerContent = <
    - {errorMessage && ( + {error && (
    - +
    )}
    diff --git a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.tsx b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.tsx index f8106bd011..f67d79abb8 100644 --- a/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.tsx +++ b/plugins/kubernetes-react/src/components/PodExecTerminal/PodExecTerminal.tsx @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import 'xterm'; +import 'xterm/css/xterm.css'; import { discoveryApiRef, useApi } from '@backstage/core-plugin-api'; +import { createStyles, makeStyles, Theme } from '@material-ui/core'; import React, { useEffect, useMemo, useState } from 'react'; import { Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; @@ -37,12 +38,23 @@ export interface PodExecTerminalProps { const hasSocketProtocol = (url: string | URL) => /wss?:\/\//.test(url.toString()); +const useStyles = makeStyles((theme: Theme) => + createStyles({ + podExecTerminal: { + width: '100%', + height: '100%', + '& .xterm-screen': { padding: theme.spacing(1) }, + }, + }), +); + /** * Executes a `/bin/sh` process in the given pod's container and opens a terminal connected to it * * @public */ export const PodExecTerminal = (props: PodExecTerminalProps) => { + const classes = useStyles(); const { containerName, podNamespace, podName } = props; const [baseUrl, setBaseUrl] = useState(window.location.host); @@ -122,10 +134,7 @@ export const PodExecTerminal = (props: PodExecTerminalProps) => {
    ); }; diff --git a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx index db70f92160..592a93e3ea 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx @@ -27,6 +27,7 @@ import { IContainer, IContainerStatus } from 'kubernetes-models/v1'; import { DateTime } from 'luxon'; import React from 'react'; +import { useIsPodExecTerminalEnabled } from '../../../hooks'; import { bytesToMiB, formatMillicores } from '../../../utils/resources'; import { PodExecTerminalDialog } from '../../PodExecTerminal/PodExecTerminalDialog'; import { ResourceUtilization } from '../../ResourceUtilization'; @@ -108,6 +109,8 @@ export const ContainerCard: React.FC = ({ containerStatus, containerMetrics, }: ContainerCardProps) => { + const isPodExecTerminalEnabled = useIsPodExecTerminalEnabled(); + // This should never be undefined if (containerSpec === undefined) { return error reading pod from cluster; @@ -228,12 +231,14 @@ export const ContainerCard: React.FC = ({ ...podScope, }} /> - + {isPodExecTerminalEnabled && ( + + )} ); diff --git a/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.test.tsx b/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.test.tsx index dda5e300eb..7eec2926c4 100644 --- a/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.test.tsx +++ b/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.test.tsx @@ -17,13 +17,20 @@ import React from 'react'; import { screen } from '@testing-library/react'; import * as services from './__fixtures__/2-services.json'; -import { textContentMatcher, renderInTestApp } from '@backstage/test-utils'; +import { + textContentMatcher, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; import { ServiceDrawer } from './ServiceDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; describe('ServiceDrawer', () => { it('should render deployment drawer', async () => { await renderInTestApp( - , + + , + , ); expect(screen.getAllByText('awesome-service-grpc')).toHaveLength(2); diff --git a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetDrawer.test.tsx b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetDrawer.test.tsx index c187c7d05d..d69ab15f1a 100644 --- a/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetDrawer.test.tsx +++ b/plugins/kubernetes-react/src/components/StatefulSetsAccordions/StatefulSetDrawer.test.tsx @@ -16,16 +16,24 @@ import React from 'react'; import * as statefulsets from '../../__fixtures__/2-statefulsets.json'; -import { renderInTestApp, textContentMatcher } from '@backstage/test-utils'; +import { + renderInTestApp, + TestApiProvider, + textContentMatcher, +} from '@backstage/test-utils'; import { StatefulSetDrawer } from './StatefulSetDrawer'; +import { kubernetesClusterLinkFormatterApiRef } from '../../api'; describe('StatefulSetDrawer', () => { it('should render statefulset drawer', async () => { const { getByText, getAllByText } = await renderInTestApp( - , + + + , + , ); expect(getAllByText('dice-roller')).toHaveLength(4); @@ -55,13 +63,16 @@ describe('StatefulSetDrawer', () => { it('should render statefulset drawer without namespace', async () => { const statefulset = (statefulsets as any).statefulsets[0]; const { queryByText } = await renderInTestApp( - , + + + , + , ); expect(queryByText('namespace: default')).not.toBeInTheDocument(); diff --git a/plugins/kubernetes-react/src/hooks/index.ts b/plugins/kubernetes-react/src/hooks/index.ts index 141bc3815b..2bc519d69a 100644 --- a/plugins/kubernetes-react/src/hooks/index.ts +++ b/plugins/kubernetes-react/src/hooks/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +export * from './useIsPodExecTerminalEnabled'; export * from './useIsPodExecTerminalSupported'; export * from './useKubernetesObjects'; export * from './useCustomResources'; diff --git a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalEnabled.test.tsx b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalEnabled.test.tsx new file mode 100644 index 0000000000..84e75ce529 --- /dev/null +++ b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalEnabled.test.tsx @@ -0,0 +1,64 @@ +/* + * 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 { ConfigReader } from '@backstage/core-app-api'; +import { configApiRef } from '@backstage/core-plugin-api'; +import { TestApiProvider } from '@backstage/test-utils'; +import { renderHook } from '@testing-library/react'; +import { PropsWithChildren } from 'react'; +import React from 'react'; + +import { useIsPodExecTerminalEnabled } from './useIsPodExecTerminalEnabled'; + +describe('useIsPodExecTerminalEnabled', () => { + let isPodExecTerminalEnabled: boolean | undefined; + + const apiWrapper = ({ children }: PropsWithChildren) => ( + + {children} + + ); + + it.each([ + { + condition: 'missing config', + returnValue: undefined, + }, + { condition: 'disabled', returnValue: false }, + { + condition: 'enabled', + returnValue: true, + }, + ])('Should return $returnValue if $condition', async ({ returnValue }) => { + isPodExecTerminalEnabled = returnValue; + + const { result } = renderHook(() => useIsPodExecTerminalEnabled(), { + wrapper: apiWrapper, + }); + + expect(result.current).toEqual(returnValue); + }); +}); diff --git a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalEnabled.ts b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalEnabled.ts new file mode 100644 index 0000000000..7598e1c570 --- /dev/null +++ b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalEnabled.ts @@ -0,0 +1,27 @@ +/* + * 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 { configApiRef, useApi } from '@backstage/core-plugin-api'; + +/** + * Check if conditions for a pod exec call through the proxy endpoint are met + * + * @internal + */ +export const useIsPodExecTerminalEnabled = (): boolean | undefined => { + const configApi = useApi(configApiRef); + + return configApi.getOptionalBoolean('kubernetes.podExecTerminal.enabled'); +}; diff --git a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.test.ts b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.test.ts index 93bfb6dd4d..c4df347c6f 100644 --- a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.test.ts +++ b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.test.ts @@ -20,7 +20,7 @@ import { useIsPodExecTerminalSupported } from './useIsPodExecTerminalSupported'; jest.mock('@backstage/core-plugin-api'); -describe('useIsClusterShellEnabled', () => { +describe('useIsPodExecTerminalSupported', () => { let clusters: { authProvider: string }[] = []; beforeEach(() => { diff --git a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.ts b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.ts index e881951059..2483bb9412 100644 --- a/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.ts +++ b/plugins/kubernetes-react/src/hooks/useIsPodExecTerminalSupported.ts @@ -15,6 +15,7 @@ */ import { useApi } from '@backstage/core-plugin-api'; import useAsync, { AsyncState } from 'react-use/lib/useAsync'; + import { kubernetesApiRef } from '../api/types'; /** diff --git a/plugins/kubernetes-react/src/index.ts b/plugins/kubernetes-react/src/index.ts index 4fb8614091..326b363b05 100644 --- a/plugins/kubernetes-react/src/index.ts +++ b/plugins/kubernetes-react/src/index.ts @@ -27,5 +27,4 @@ export * from './hooks'; export * from './api'; export * from './kubernetes-auth-provider'; export * from './components'; -export * from './utils'; export * from './types'; diff --git a/plugins/kubernetes-react/src/setupTests.ts b/plugins/kubernetes-react/src/setupTests.ts index 865308e634..609118cd07 100644 --- a/plugins/kubernetes-react/src/setupTests.ts +++ b/plugins/kubernetes-react/src/setupTests.ts @@ -13,4 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import '@testing-library/jest-dom'; +import { Settings } from 'luxon'; + +Settings.defaultLocale = 'en'; diff --git a/plugins/kubernetes-react/src/types/types.ts b/plugins/kubernetes-react/src/types/types.ts index ad8df040ac..5db2e281f4 100644 --- a/plugins/kubernetes-react/src/types/types.ts +++ b/plugins/kubernetes-react/src/types/types.ts @@ -29,6 +29,6 @@ export interface ClusterLinksFormatterOptions { /** * @public */ -export type ClusterLinksFormatter = ( - options: ClusterLinksFormatterOptions, -) => URL; +export interface ClusterLinksFormatter { + formatClusterLink(options: ClusterLinksFormatterOptions): Promise; +} diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatClusterLink.test.ts b/plugins/kubernetes-react/src/utils/clusterLinks/formatClusterLink.test.ts deleted file mode 100644 index 254b9fc51b..0000000000 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatClusterLink.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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 { formatClusterLink } from './formatClusterLink'; - -describe('clusterLinks', () => { - describe('formatClusterLink', () => { - it('should not return an url when there is no dashboard url', () => { - const url = formatClusterLink({ object: {}, kind: 'foo' }); - expect(url).toBeUndefined(); - }); - it('should return an url even when there is no object', () => { - const url = formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com', - object: undefined, - kind: 'foo', - }); - expect(url).toBe('https://k8s.foo.com'); - }); - it('should throw when the app is not recognized', () => { - expect(() => - formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com', - dashboardApp: 'unknownapp', - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Deployment', - }), - ).toThrow("Could not find Kubernetes dashboard app named 'unknownapp'"); - }); - - describe('default app', () => { - it('should return an url on the deployment', () => { - const url = formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com/', - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Deployment', - }); - expect(url).toBe( - 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', - ); - }); - it('should return an url on the service', () => { - const url = formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com/', - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Service', - }); - expect(url).toBe( - 'https://k8s.foo.com/#/service/bar/foobar?namespace=bar', - ); - }); - }); - - describe('standard app', () => { - it('should return an url on the deployment', () => { - const url = formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com/', - dashboardApp: 'standard', - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Deployment', - }); - expect(url).toBe( - 'https://k8s.foo.com/#/deployment/bar/foobar?namespace=bar', - ); - }); - it('should return an url on the service', () => { - const url = formatClusterLink({ - dashboardUrl: 'https://k8s.foo.com/', - dashboardApp: 'standard', - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Service', - }); - expect(url).toBe( - 'https://k8s.foo.com/#/service/bar/foobar?namespace=bar', - ); - }); - }); - describe('GKE app', () => { - it('should return an url on the deployment', () => { - const url = formatClusterLink({ - dashboardApp: 'gke', - dashboardParameters: { - projectId: 'foobar-333614', - region: 'us-east1-c', - clusterName: 'cluster-1', - }, - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Deployment', - }); - expect(url).toBe( - 'https://console.cloud.google.com/kubernetes/deployment/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614', - ); - }); - it('should return an url on the service', () => { - const url = formatClusterLink({ - dashboardApp: 'gke', - dashboardParameters: { - projectId: 'foobar-333614', - region: 'us-east1-c', - clusterName: 'cluster-1', - }, - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Service', - }); - expect(url).toBe( - 'https://console.cloud.google.com/kubernetes/service/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614', - ); - }); - }); - }); -}); diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatClusterLink.ts b/plugins/kubernetes-react/src/utils/clusterLinks/formatClusterLink.ts deleted file mode 100644 index 8f7e43a121..0000000000 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatClusterLink.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 type { JsonObject } from '@backstage/types'; -import { defaultFormatterName, clusterLinksFormatters } from './formatters'; - -/** - * @public - */ -export type FormatClusterLinkOptions = { - dashboardUrl?: string; - dashboardApp?: string; - dashboardParameters?: JsonObject; - object: any; - kind: string; -}; - -/** - * @public - */ -export function formatClusterLink(options: FormatClusterLinkOptions) { - if (!options.dashboardUrl && !options.dashboardParameters) { - return undefined; - } - if (options.dashboardUrl && !options.object) { - return options.dashboardUrl; - } - const app = options.dashboardApp || defaultFormatterName; - const formatter = clusterLinksFormatters[app]; - if (!formatter) { - throw new Error(`Could not find Kubernetes dashboard app named '${app}'`); - } - const url = formatter({ - dashboardUrl: options.dashboardUrl - ? new URL(options.dashboardUrl) - : undefined, - dashboardParameters: options.dashboardParameters, - object: options.object, - kind: options.kind, - }); - return url.toString(); -} diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/aks.ts b/plugins/kubernetes-react/src/utils/clusterLinks/formatters/aks.ts deleted file mode 100644 index 71fffb00e6..0000000000 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/aks.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 { ClusterLinksFormatterOptions } from '../../../types/types'; - -const basePath = - 'https://portal.azure.com/#blade/Microsoft_Azure_ContainerService/AksK8ResourceMenuBlade/overview-Deployment/aksClusterId'; - -const requiredParams = ['subscriptionId', 'resourceGroup', 'clusterName']; - -export function aksFormatter(options: ClusterLinksFormatterOptions): URL { - if (!options.dashboardParameters) { - throw new Error('AKS dashboard requires a dashboardParameters option'); - } - const args = options.dashboardParameters; - for (const param of requiredParams) { - if (typeof args[param] !== 'string') { - throw new Error( - `AKS dashboard requires a "${param}" of type string in the dashboardParameters option`, - ); - } - } - - const path = `/subscriptions/${args.subscriptionId}/resourceGroups/${args.resourceGroup}/providers/Microsoft.ContainerService/managedClusters/${args.clusterName}`; - - const { name, namespace, uid } = options.object.metadata; - const { selector } = options.object.spec; - const params = { - kind: options.kind, - metadata: { name, namespace, uid }, - spec: { - selector, - }, - }; - - return new URL( - `${basePath}/${encodeURIComponent(path)}/resource/${encodeURIComponent( - JSON.stringify(params), - )}`, - ); -} diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/gke.test.ts b/plugins/kubernetes-react/src/utils/clusterLinks/formatters/gke.test.ts deleted file mode 100644 index 7fd0814267..0000000000 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/gke.test.ts +++ /dev/null @@ -1,222 +0,0 @@ -/* - * 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 { gkeFormatter } from './gke'; - -describe('clusterLinks - GKE formatter', () => { - it('should provide a dashboardParameters in the options', () => { - expect(() => - gkeFormatter({ - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Deployment', - }), - ).toThrow('GKE dashboard requires a dashboardParameters option'); - }); - it('should provide a projectId in the dashboardParameters options', () => { - expect(() => - gkeFormatter({ - dashboardParameters: { - region: 'us-east1-c', - clusterName: 'cluster-1', - }, - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Deployment', - }), - ).toThrow( - 'GKE dashboard requires a "projectId" of type string in the dashboardParameters option', - ); - }); - it('should provide a region in the dashboardParameters options', () => { - expect(() => - gkeFormatter({ - dashboardParameters: { - projectId: 'foobar-333614', - clusterName: 'cluster-1', - }, - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Deployment', - }), - ).toThrow( - 'GKE dashboard requires a "region" of type string in the dashboardParameters option', - ); - }); - it('should provide a clusterName in the dashboardParameters options', () => { - expect(() => - gkeFormatter({ - dashboardParameters: { - projectId: 'foobar-333614', - region: 'us-east1-c', - }, - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Deployment', - }), - ).toThrow( - 'GKE dashboard requires a "clusterName" of type string in the dashboardParameters option', - ); - }); - it('should return an url on the cluster when there is a namespace only', () => { - const url = gkeFormatter({ - dashboardParameters: { - projectId: 'foobar-333614', - region: 'us-east1-c', - clusterName: 'cluster-1', - }, - object: { - metadata: { - namespace: 'bar', - }, - }, - kind: 'foo', - }); - expect(url.href).toBe( - 'https://console.cloud.google.com/kubernetes/clusters/details/us-east1-c/cluster-1/details?project=foobar-333614', - ); - }); - it('should return an url on the cluster when the kind is not recognizeed', () => { - const url = gkeFormatter({ - dashboardParameters: { - projectId: 'foobar-333614', - region: 'us-east1-c', - clusterName: 'cluster-1', - }, - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'UnknownKind', - }); - expect(url.href).toBe( - 'https://console.cloud.google.com/kubernetes/clusters/details/us-east1-c/cluster-1/details?project=foobar-333614', - ); - }); - it('should return an url on the deployment', () => { - const url = gkeFormatter({ - dashboardParameters: { - projectId: 'foobar-333614', - region: 'us-east1-c', - clusterName: 'cluster-1', - }, - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Deployment', - }); - expect(url.href).toBe( - 'https://console.cloud.google.com/kubernetes/deployment/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614', - ); - }); - - it('should return an url on the service', () => { - const url = gkeFormatter({ - dashboardParameters: { - projectId: 'foobar-333614', - region: 'us-east1-c', - clusterName: 'cluster-1', - }, - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Service', - }); - expect(url.href).toBe( - 'https://console.cloud.google.com/kubernetes/service/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614', - ); - }); - it('should return an url on the pod', () => { - const url = gkeFormatter({ - dashboardParameters: { - projectId: 'foobar-333614', - region: 'us-east1-c', - clusterName: 'cluster-1', - }, - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Pod', - }); - expect(url.href).toBe( - 'https://console.cloud.google.com/kubernetes/pod/us-east1-c/cluster-1/bar/foobar/details?project=foobar-333614', - ); - }); - it('should return an url on the ingress', () => { - const url = gkeFormatter({ - dashboardParameters: { - projectId: 'foobar-333614', - region: 'us-east1-c', - clusterName: 'cluster-1', - }, - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'Ingress', - }); - expect(url.href).toBe( - 'https://console.cloud.google.com/kubernetes/ingress/us-east1-c/cluster-1/bar/foobar/details?project=foobar-333614', - ); - }); - it('should return an url on the deployment for a hpa', () => { - const url = gkeFormatter({ - dashboardParameters: { - projectId: 'foobar-333614', - region: 'us-east1-c', - clusterName: 'cluster-1', - }, - object: { - metadata: { - name: 'foobar', - namespace: 'bar', - }, - }, - kind: 'HorizontalPodAutoscaler', - }); - expect(url.href).toBe( - 'https://console.cloud.google.com/kubernetes/deployment/us-east1-c/cluster-1/bar/foobar/overview?project=foobar-333614', - ); - }); -}); diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/gke.ts b/plugins/kubernetes-react/src/utils/clusterLinks/formatters/gke.ts deleted file mode 100644 index 957032e6e2..0000000000 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/gke.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 { ClusterLinksFormatterOptions } from '../../../types/types'; - -const kindMappings: Record = { - deployment: 'deployment', - pod: 'pod', - ingress: 'ingress', - service: 'service', - horizontalpodautoscaler: 'deployment', -}; - -export function gkeFormatter(options: ClusterLinksFormatterOptions): URL { - if (!options.dashboardParameters) { - throw new Error('GKE dashboard requires a dashboardParameters option'); - } - const args = options.dashboardParameters; - if (typeof args.projectId !== 'string') { - throw new Error( - 'GKE dashboard requires a "projectId" of type string in the dashboardParameters option', - ); - } - if (typeof args.region !== 'string') { - throw new Error( - 'GKE dashboard requires a "region" of type string in the dashboardParameters option', - ); - } - if (typeof args.clusterName !== 'string') { - throw new Error( - 'GKE dashboard requires a "clusterName" of type string in the dashboardParameters option', - ); - } - const basePath = new URL('https://console.cloud.google.com/'); - const region = encodeURIComponent(args.region); - const clusterName = encodeURIComponent(args.clusterName); - const name = encodeURIComponent(options.object.metadata?.name ?? ''); - const namespace = encodeURIComponent( - options.object.metadata?.namespace ?? '', - ); - const validKind = kindMappings[options.kind.toLocaleLowerCase('en-US')]; - let path = ''; - if (namespace && name && validKind) { - const kindsWithDetails = ['ingress', 'pod']; - const landingPage = kindsWithDetails.includes(validKind) - ? 'details' - : 'overview'; - path = `kubernetes/${validKind}/${region}/${clusterName}/${namespace}/${name}/${landingPage}`; - } else { - path = `kubernetes/clusters/details/${region}/${clusterName}/details`; - } - const result = new URL(path, basePath); - result.searchParams.set('project', args.projectId); - return result; -} diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/index.ts b/plugins/kubernetes-react/src/utils/clusterLinks/formatters/index.ts deleted file mode 100644 index b75e45cc7f..0000000000 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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 { ClusterLinksFormatter } from '../../../types/types'; -import { standardFormatter } from './standard'; -import { rancherFormatter } from './rancher'; -import { openshiftFormatter } from './openshift'; -import { aksFormatter } from './aks'; -import { eksFormatter } from './eks'; -import { gkeFormatter } from './gke'; - -/** - * @public - */ -export const clusterLinksFormatters: Record = { - standard: standardFormatter, - rancher: rancherFormatter, - openshift: openshiftFormatter, - aks: aksFormatter, - eks: eksFormatter, - gke: gkeFormatter, -}; -export const defaultFormatterName = 'standard'; diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/openshift.ts b/plugins/kubernetes-react/src/utils/clusterLinks/formatters/openshift.ts deleted file mode 100644 index c5b7313061..0000000000 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/openshift.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 { ClusterLinksFormatterOptions } from '../../../types/types'; - -const kindMappings: Record = { - deployment: 'deployments', - ingress: 'ingresses', - service: 'services', - horizontalpodautoscaler: 'horizontalpodautoscalers', - persistentvolume: 'persistentvolumes', -}; - -export function openshiftFormatter(options: ClusterLinksFormatterOptions): URL { - if (!options.dashboardUrl) { - throw new Error('OpenShift dashboard requires a dashboardUrl option'); - } - const basePath = new URL(options.dashboardUrl.href); - const name = encodeURIComponent(options.object.metadata?.name ?? ''); - const namespace = encodeURIComponent( - options.object.metadata?.namespace ?? '', - ); - const validKind = kindMappings[options.kind.toLocaleLowerCase('en-US')]; - if (!basePath.pathname.endsWith('/')) { - // a dashboard url with a path should end with a slash otherwise - // the new combined URL will replace the last segment with the appended path! - // https://foobar.com/abc/def + k8s/cluster/projects/test --> https://foobar.com/abc/k8s/cluster/projects/test - // https://foobar.com/abc/def/ + k8s/cluster/projects/test --> https://foobar.com/abc/def/k8s/cluster/projects/test - basePath.pathname += '/'; - } - let path = ''; - if (namespace) { - if (name && validKind) { - path = `k8s/ns/${namespace}/${validKind}/${name}`; - } else { - path = `k8s/cluster/projects/${namespace}`; - } - } else if (validKind) { - path = `k8s/cluster/${validKind}`; - if (name) { - path += `/${name}`; - } - } - return new URL(path, basePath); -} diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/rancher.ts b/plugins/kubernetes-react/src/utils/clusterLinks/formatters/rancher.ts deleted file mode 100644 index 6c72730181..0000000000 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/rancher.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 { ClusterLinksFormatterOptions } from '../../../types/types'; - -const kindMappings: Record = { - deployment: 'apps.deployment', - ingress: 'networking.k8s.io.ingress', - service: 'service', - horizontalpodautoscaler: 'autoscaling.horizontalpodautoscaler', -}; - -export function rancherFormatter(options: ClusterLinksFormatterOptions): URL { - if (!options.dashboardUrl) { - throw new Error('Rancher dashboard requires a dashboardUrl option'); - } - const basePath = new URL(options.dashboardUrl.href); - const name = encodeURIComponent(options.object.metadata?.name ?? ''); - const namespace = encodeURIComponent( - options.object.metadata?.namespace ?? '', - ); - const validKind = kindMappings[options.kind.toLocaleLowerCase('en-US')]; - if (!basePath.pathname.endsWith('/')) { - // a dashboard url with a path should end with a slash otherwise - // the new combined URL will replace the last segment with the appended path! - // https://foobar.com/abc/def + explorer/service/test --> https://foobar.com/abc/explorer/service/test - // https://foobar.com/abc/def/ + explorer/service/test --> https://foobar.com/abc/def/explorer/service/test - basePath.pathname += '/'; - } - let path = ''; - if (validKind && name && namespace) { - path = `explorer/${validKind}/${namespace}/${name}`; - } else if (namespace) { - path = 'explorer/workload'; - } - return new URL(path, basePath); -} diff --git a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/standard.ts b/plugins/kubernetes-react/src/utils/clusterLinks/formatters/standard.ts deleted file mode 100644 index e26adf98e6..0000000000 --- a/plugins/kubernetes-react/src/utils/clusterLinks/formatters/standard.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 { ClusterLinksFormatterOptions } from '../../../types/types'; - -const kindMappings: Record = { - deployment: 'deployment', - pod: 'pod', - ingress: 'ingress', - service: 'service', - horizontalpodautoscaler: 'deployment', - statefulset: 'statefulset', -}; - -export function standardFormatter(options: ClusterLinksFormatterOptions) { - if (!options.dashboardUrl) { - throw new Error('standard dashboard requires a dashboardUrl option'); - } - const result = new URL(options.dashboardUrl.href); - const name = encodeURIComponent(options.object.metadata?.name ?? ''); - const namespace = encodeURIComponent( - options.object.metadata?.namespace ?? '', - ); - const validKind = kindMappings[options.kind.toLocaleLowerCase('en-US')]; - if (!result.pathname.endsWith('/')) { - result.pathname += '/'; - } - if (validKind && name && namespace) { - result.hash = `/${validKind}/${namespace}/${name}`; - } else if (namespace) { - result.hash = '/workloads'; - } - if (namespace) { - // Note that Angular SPA requires a hash and the query parameter should be part of it - result.hash += `?namespace=${namespace}`; - } - return result; -} diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index f4a0c0dfd9..7cee4eda45 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,145 @@ # @backstage/plugin-kubernetes +## 0.11.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-kubernetes-react@0.3.0-next.0 + - @backstage/plugin-kubernetes-common@0.7.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.11.4 + +### Patch Changes + +- d5d2c67: Add `authuser` search parameter to GKE cluster link formatter in k8s plugin + + Thanks to this, people with multiple simultaneously logged-in accounts in their GCP console will automatically view objects with the same email as the one signed in to the Google auth provider in Backstage. + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-kubernetes-react@0.2.1 + - @backstage/plugin-kubernetes-common@0.7.3 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.11.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.11.4-next.1 + +### Patch Changes + +- d5d2c67: Add `authuser` search parameter to GKE cluster link formatter in k8s plugin + + Thanks to this, people with multiple simultaneously logged-in accounts in their GCP console will automatically view objects with the same email as the one signed in to the Google auth provider in Backstage. + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/plugin-kubernetes-react@0.2.1-next.1 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + +## 0.11.4-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-kubernetes-common@0.7.3-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-kubernetes-react@0.2.1-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.11.3 + +### Patch Changes + +- 899d71a: Change `formatClusterLink` to be an API and make it async for further customization possibilities. + + **BREAKING** + If you have a custom k8s page and used `formatClusterLink` directly, you need to migrate to new `kubernetesClusterLinkFormatterApiRef` + +- 706fc3a: Updated dependency `@kubernetes/client-node` to `0.20.0`. +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-kubernetes-react@0.2.0 + - @backstage/plugin-kubernetes-common@0.7.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.11.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/plugin-kubernetes-react@0.2.0-next.3 + +## 0.11.3-next.2 + +### Patch Changes + +- 899d71a: Change `formatClusterLink` to be an API and make it async for further customization possibilities. + + **BREAKING** + If you have a custom k8s page and used `formatClusterLink` directly, you need to migrate to new `kubernetesClusterLinkFormatterApiRef` + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-kubernetes-react@0.2.0-next.2 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + +## 0.11.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-kubernetes-common@0.7.2-next.1 + - @backstage/plugin-kubernetes-react@0.1.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + ## 0.11.3-next.0 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 8970a9d19e..39a7a938a2 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.11.3-next.0", + "version": "0.11.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,28 +35,21 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-kubernetes-common": "workspace:^", "@backstage/plugin-kubernetes-react": "workspace:^", - "@backstage/theme": "workspace:^", - "@backstage/types": "workspace:^", "@kubernetes-models/apimachinery": "^1.1.0", "@kubernetes-models/base": "^4.0.1", "@kubernetes/client-node": "0.20.0", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "cronstrue": "^2.2.0", "js-yaml": "^4.0.0", "kubernetes-models": "^4.1.0", "lodash": "^4.17.21", "luxon": "^3.0.0", - "react-use": "^17.2.4", "xterm": "^5.2.1", "xterm-addon-attach": "^0.9.0", "xterm-addon-fit": "^0.8.0" @@ -68,15 +61,11 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "jest-websocket-mock": "^2.4.1", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/kubernetes/src/plugin.ts b/plugins/kubernetes/src/plugin.ts index ac55dbbf23..afe2287633 100644 --- a/plugins/kubernetes/src/plugin.ts +++ b/plugins/kubernetes/src/plugin.ts @@ -20,6 +20,10 @@ import { kubernetesAuthProvidersApiRef, KubernetesAuthProviders, KubernetesProxyClient, + kubernetesClusterLinkFormatterApiRef, + getDefaultFormatters, + KubernetesClusterLinkFormatter, + DEFAULT_FORMATTER_NAME, } from '@backstage/plugin-kubernetes-react'; import { createApiFactory, @@ -97,6 +101,17 @@ export const kubernetesPlugin = createPlugin({ }); }, }), + createApiFactory({ + api: kubernetesClusterLinkFormatterApiRef, + deps: { googleAuthApi: googleAuthApiRef }, + factory: deps => { + const formatters = getDefaultFormatters(deps); + return new KubernetesClusterLinkFormatter({ + formatters, + defaultFormatterName: DEFAULT_FORMATTER_NAME, + }); + }, + }), ], routes: { entityContent: rootCatalogKubernetesRouteRef, diff --git a/plugins/lighthouse-backend/CHANGELOG.md b/plugins/lighthouse-backend/CHANGELOG.md index 4d13ee7bb9..ddfbe1a7c5 100644 --- a/plugins/lighthouse-backend/CHANGELOG.md +++ b/plugins/lighthouse-backend/CHANGELOG.md @@ -1,5 +1,143 @@ # @backstage/plugin-lighthouse-backend +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.0 + +### Minor Changes + +- 7f0dbfd: Fixed crashes faced with custom schedule configuration. The configuration schema has been update to leverage the TaskScheduleDefinition interface. It is highly recommended to move the `lighthouse.shedule` and `lighthouse.timeout` respectively to `lighthouse.schedule.frequency` and `lighthouse.schedule.timeout`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.0-next.2 + +### Minor Changes + +- 7f0dbfd: Fixed crashes faced with custom schedule configuration. The configuration schema has been update to leverage the TaskScheduleDefinition interface. It is highly recommended to move the `lighthouse.shedule` and `lighthouse.timeout` respectively to `lighthouse.schedule.frequency` and `lighthouse.schedule.timeout`. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-lighthouse-common@0.1.4 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/lighthouse-backend/README.md b/plugins/lighthouse-backend/README.md index 28845d3c73..687467f1d5 100644 --- a/plugins/lighthouse-backend/README.md +++ b/plugins/lighthouse-backend/README.md @@ -19,13 +19,19 @@ import { PluginEnvironment } from '../types'; import { CatalogClient } from '@backstage/catalog-client'; export default async function createPlugin(env: PluginEnvironment) { - const { logger, scheduler, config } = env; + const { logger, scheduler, config, tokenManager } = env; const catalogClient = new CatalogClient({ discoveryApi: env.discovery, }); - await createScheduler({ logger, scheduler, config, catalogClient }); + await createScheduler({ + logger, + scheduler, + config, + catalogClient, + tokenManager, + }); } ``` @@ -80,5 +86,8 @@ You can define how often and when the scheduler should run the audits: ```yaml lighthouse: schedule: - days: 1 + frequency: + hours: 12 # Default: 1 day + timeout: + minutes: 30 # Default: 10 minutes ``` diff --git a/plugins/lighthouse-backend/config.d.ts b/plugins/lighthouse-backend/config.d.ts new file mode 100644 index 0000000000..2d49f188b6 --- /dev/null +++ b/plugins/lighthouse-backend/config.d.ts @@ -0,0 +1,30 @@ +/* + * 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 { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { HumanDuration } from '@backstage/types'; + +export interface Config { + /** + * Configuration options for the Lighthouse backend plugin. + */ + lighthouse?: { + /** + * Schedule of the audit + */ + schedule?: HumanDuration | TaskScheduleDefinitionConfig; + }; +} diff --git a/plugins/lighthouse-backend/package.json b/plugins/lighthouse-backend/package.json index 84cff5b8eb..9006c24488 100644 --- a/plugins/lighthouse-backend/package.json +++ b/plugins/lighthouse-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-lighthouse-backend", "description": "Backend functionalities for lighthouse", - "version": "0.3.5-next.0", + "version": "0.4.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,8 +24,10 @@ "lighthouse" ], "files": [ - "dist" + "dist", + "config.d.ts" ], + "configSchema": "config.d.ts", "scripts": { "build": "backstage-cli package build", "lint": "backstage-cli package lint", diff --git a/plugins/lighthouse-backend/src/config.ts b/plugins/lighthouse-backend/src/config.ts index bea1c35661..736a4e569d 100644 --- a/plugins/lighthouse-backend/src/config.ts +++ b/plugins/lighthouse-backend/src/config.ts @@ -13,56 +13,66 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { LoggerService } from '@backstage/backend-plugin-api'; +import { + TaskScheduleDefinition, + readTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-tasks'; +import { Config, readDurationFromConfig } from '@backstage/config'; +import { HumanDuration } from '@backstage/types'; -import { Config } from '@backstage/config'; -import { HumanDuration as HumanDuration } from '@backstage/types'; - -export interface LighthouseAuditScheduleConfig { - schedule: HumanDuration; - timeout: HumanDuration; - auditDetail: HumanDuration; -} - -/** @public */ -export type LighthouseAuditSchedule = { - getSchedule: () => HumanDuration; - getTimeout: () => HumanDuration; +type Options = { + logger: LoggerService; }; /** @public */ -export class LighthouseAuditScheduleImpl implements LighthouseAuditSchedule { - static fromConfig(config: Config) { - const lighthouse = config.getOptionalConfig('lighthouse'); +export class LighthouseAuditScheduleImpl implements TaskScheduleDefinition { + /** + * Looks at the `lighthouse.schedule` section in the application configuration + * and returns a TaskScheduleDefinition. + * Defaults to `{ frequency: { days: 1 }, timeout: {}, initialDelay: { minutes; 15 } }` + * + * @returns a TaskScheduleDefinition + */ + static fromConfig(config: Config, options: Options): TaskScheduleDefinition { + const { logger } = options; - let schedule: HumanDuration = { days: 1 }; - let timeout: HumanDuration = {}; + let lighthouse: TaskScheduleDefinition = { + frequency: { days: 1 }, + timeout: { minutes: 10 }, + initialDelay: { minutes: 15 }, + }; - if (lighthouse) { - const scheduleConfig = lighthouse.getOptionalConfig('schedule'); - const timeoutConfig = lighthouse.getOptionalConfig('timeout'); + if (config.has('lighthouse.schedule.frequency')) { + lighthouse = readTaskScheduleDefinitionFromConfig( + config.getConfig('lighthouse.schedule'), + ); + } else if (config.has('lighthouse.schedule')) { + logger.warn( + `[Deprecation] Please migrate the schedule configuration to 'lighthouse.schedule.frequency'`, + ); - if (scheduleConfig) { - schedule = scheduleConfig as HumanDuration; - } - - if (timeoutConfig) { - timeout = timeoutConfig as HumanDuration; - } + lighthouse.frequency = readDurationFromConfig( + config.getConfig('lighthouse.schedule'), + ); } - return new LighthouseAuditScheduleImpl(schedule, timeout); + if (config.has('lighthouse.timeout')) { + logger.warn( + `[Deprecation] Please migrate the timeout configuration to 'lighthouse.schedule.timeout'`, + ); + + lighthouse.timeout = readDurationFromConfig( + config.getConfig('lighthouse.timeout'), + ); + } + + return lighthouse; } constructor( - private schedule: HumanDuration, - private timeout: HumanDuration, + public frequency: HumanDuration, + public timeout: HumanDuration, + public initialDelay: HumanDuration, ) {} - - getSchedule(): HumanDuration { - return this.schedule; - } - - getTimeout(): HumanDuration { - return this.timeout; - } } diff --git a/plugins/lighthouse-backend/src/service/createScheduler.ts b/plugins/lighthouse-backend/src/service/createScheduler.ts index 498e459a09..45a3e88149 100644 --- a/plugins/lighthouse-backend/src/service/createScheduler.ts +++ b/plugins/lighthouse-backend/src/service/createScheduler.ts @@ -39,7 +39,9 @@ export async function createScheduler( const { logger, scheduler, catalogClient, config, tokenManager } = options; const lighthouseApi = LighthouseRestApi.fromConfig(config); - const lighthouseAuditConfig = LighthouseAuditScheduleImpl.fromConfig(config); + const lighthouseAuditConfig = LighthouseAuditScheduleImpl.fromConfig(config, { + logger, + }); const formFactorToScreenEmulationMap = { // the default is mobile, so no need to override mobile: undefined, @@ -56,16 +58,16 @@ export async function createScheduler( logger.info( `Running with Scheduler Config ${JSON.stringify( - lighthouseAuditConfig.getSchedule(), - )} and timeout ${JSON.stringify(lighthouseAuditConfig.getTimeout())}`, + lighthouseAuditConfig.frequency, + )} and timeout ${JSON.stringify(lighthouseAuditConfig.timeout)}`, ); if (scheduler) { await scheduler.scheduleTask({ id: 'lighthouse_audit', - frequency: lighthouseAuditConfig.getSchedule(), - timeout: lighthouseAuditConfig.getTimeout(), - initialDelay: { minutes: 15 }, + frequency: lighthouseAuditConfig.frequency, + timeout: lighthouseAuditConfig.timeout, + initialDelay: lighthouseAuditConfig.initialDelay, fn: async () => { const filter: Record = { kind: 'Component', diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 0a43306d8b..e93b449c5e 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,112 @@ # @backstage/plugin-lighthouse +## 0.4.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.14 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.14-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.13 + +### Patch Changes + +- ffbf656: Updated README +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.13-next.2 + +### Patch Changes + +- ffbf656: Updated README +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-lighthouse-common@0.1.4 + +## 0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-lighthouse-common@0.1.4 + ## 0.4.13-next.0 ### Patch Changes diff --git a/plugins/lighthouse/README.md b/plugins/lighthouse/README.md index 1d2385e4c4..e834ad39c1 100644 --- a/plugins/lighthouse/README.md +++ b/plugins/lighthouse/README.md @@ -47,6 +47,10 @@ const routes = ( Then configure the `lighthouse-audit-service` URL in your [`app-config.yaml`](https://github.com/backstage/backstage/blob/master/app-config.yaml). ```yaml +backend: + csp: + frame-src: + - http://your-service-url lighthouse: baseUrl: http://your-service-url ``` diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index d9e1562293..104396d6d4 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-lighthouse", "description": "A Backstage plugin that integrates towards Lighthouse", - "version": "0.4.13-next.0", + "version": "0.4.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,12 +35,10 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-lighthouse-common": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", @@ -60,7 +58,6 @@ "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0" }, "files": [ diff --git a/plugins/linguist-backend/CHANGELOG.md b/plugins/linguist-backend/CHANGELOG.md index b6d4eeecba..dd2ca24981 100644 --- a/plugins/linguist-backend/CHANGELOG.md +++ b/plugins/linguist-backend/CHANGELOG.md @@ -1,5 +1,154 @@ # @backstage/plugin-linguist-backend +## 0.5.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.5.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.5.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + +## 0.5.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.5.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/backend-tasks@0.5.13 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.5.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.5.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-linguist-common@0.1.2 + ## 0.5.5-next.0 ### Patch Changes diff --git a/plugins/linguist-backend/package.json b/plugins/linguist-backend/package.json index 078e771acb..7acae2049c 100644 --- a/plugins/linguist-backend/package.json +++ b/plugins/linguist-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-linguist-backend", - "version": "0.5.5-next.0", + "version": "0.5.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -56,7 +56,6 @@ "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.8", "js-yaml": "^4.1.0", - "msw": "^1.0.0", "supertest": "^6.2.4" }, "files": [ diff --git a/plugins/linguist/.eslintrc.js b/plugins/linguist/.eslintrc.js index e2a53a6ad2..e487f765b2 100644 --- a/plugins/linguist/.eslintrc.js +++ b/plugins/linguist/.eslintrc.js @@ -1 +1,5 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + '@backstage/no-top-level-material-ui-4-imports': 'error', + }, +}); diff --git a/plugins/linguist/CHANGELOG.md b/plugins/linguist/CHANGELOG.md index 9eff98f884..866d1f94f6 100644 --- a/plugins/linguist/CHANGELOG.md +++ b/plugins/linguist/CHANGELOG.md @@ -1,5 +1,123 @@ # @backstage/plugin-linguist +## 0.1.15-next.0 + +### Patch Changes + +- 995d280: Updated imports from named to default imports to help with the Material UI v4 to v5 migration +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.1.14 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- 4f42918: Added alpha support for the New Frontend System (Declarative Integration) +- Updated dependencies + - @backstage/core-compat-api@0.1.1 + - @backstage/frontend-plugin-api@0.5.0 + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.1.14-next.2 + +### Patch Changes + +- 4f42918: Added alpha support for the New Frontend System (Declarative Integration) +- Updated dependencies + - @backstage/core-compat-api@0.1.1-next.2 + - @backstage/frontend-plugin-api@0.4.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.1.14-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.1.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.1.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-linguist-common@0.1.2 + +## 0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-linguist-common@0.1.2 + ## 0.1.13-next.0 ### Patch Changes diff --git a/plugins/linguist/api-report-alpha.md b/plugins/linguist/api-report-alpha.md new file mode 100644 index 0000000000..418439d331 --- /dev/null +++ b/plugins/linguist/api-report-alpha.md @@ -0,0 +1,13 @@ +## API Report File for "@backstage/plugin-linguist" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackstagePlugin } from '@backstage/frontend-plugin-api'; + +// @alpha (undocumented) +const _default: BackstagePlugin<{}, {}>; +export default _default; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/linguist/package.json b/plugins/linguist/package.json index f5c1fc6d16..6f20f2b007 100644 --- a/plugins/linguist/package.json +++ b/plugins/linguist/package.json @@ -1,13 +1,26 @@ { "name": "@backstage/plugin-linguist", - "version": "0.1.13-next.0", + "version": "0.1.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" + }, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } }, "backstage": { "role": "frontend-plugin" @@ -30,14 +43,14 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", + "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-linguist-common": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.9.13", - "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "luxon": "^2.0.2", @@ -51,14 +64,10 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/linguist/src/alpha.ts b/plugins/linguist/src/alpha.ts new file mode 100644 index 0000000000..e80f131817 --- /dev/null +++ b/plugins/linguist/src/alpha.ts @@ -0,0 +1,18 @@ +/* + * 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 * from './alpha/index'; +export { default } from './alpha/index'; diff --git a/plugins/linguist/src/alpha/index.ts b/plugins/linguist/src/alpha/index.ts new file mode 100644 index 0000000000..2f137f09ee --- /dev/null +++ b/plugins/linguist/src/alpha/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { default } from './plugin'; diff --git a/plugins/linguist/src/alpha/plugin.tsx b/plugins/linguist/src/alpha/plugin.tsx new file mode 100644 index 0000000000..36d635740f --- /dev/null +++ b/plugins/linguist/src/alpha/plugin.tsx @@ -0,0 +1,53 @@ +/* + * 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 { + createApiExtension, + createApiFactory, + createPlugin, + discoveryApiRef, + identityApiRef, +} from '@backstage/frontend-plugin-api'; + +import { LinguistClient, linguistApiRef } from '../api'; +import { compatWrapper } from '@backstage/core-compat-api'; +import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; + +/** @alpha */ +export const entityLinguistCard = createEntityCardExtension({ + name: 'languages', + loader: async () => + import('../components/LinguistCard').then(m => + compatWrapper(), + ), +}); + +/** @alpha */ +export const linguistApi = createApiExtension({ + factory: createApiFactory({ + api: linguistApiRef, + deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef }, + factory: ({ discoveryApi, identityApi }) => + new LinguistClient({ discoveryApi, identityApi }), + }), +}); + +/** @alpha */ +export default createPlugin({ + id: 'linguist', + extensions: [linguistApi, entityLinguistCard], +}); diff --git a/plugins/linguist/src/components/LinguistCard/LinguistCard.tsx b/plugins/linguist/src/components/LinguistCard/LinguistCard.tsx index 6014487d30..6a07a958c2 100644 --- a/plugins/linguist/src/components/LinguistCard/LinguistCard.tsx +++ b/plugins/linguist/src/components/LinguistCard/LinguistCard.tsx @@ -14,15 +14,12 @@ * limitations under the License. */ -import { - Box, - Chip, - Tooltip, - Typography, - makeStyles, - Grid, - useTheme, -} from '@material-ui/core'; +import Box from '@material-ui/core/Box'; +import Chip from '@material-ui/core/Chip'; +import Tooltip from '@material-ui/core/Tooltip'; +import Typography from '@material-ui/core/Typography'; +import Grid from '@material-ui/core/Grid'; +import { makeStyles, useTheme } from '@material-ui/core/styles'; import { InfoCard, Progress } from '@backstage/core-components'; import Alert from '@material-ui/lab/Alert'; import { DateTime } from 'luxon'; diff --git a/plugins/microsoft-calendar/CHANGELOG.md b/plugins/microsoft-calendar/CHANGELOG.md index 5197b46b04..86ea39fcff 100644 --- a/plugins/microsoft-calendar/CHANGELOG.md +++ b/plugins/microsoft-calendar/CHANGELOG.md @@ -1,5 +1,74 @@ # @backstage/plugin-microsoft-calendar +## 0.1.11 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/errors@1.2.3 + +## 0.1.11-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/errors@1.2.3 + +## 0.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.1.10-next.0 ### Patch Changes diff --git a/plugins/microsoft-calendar/package.json b/plugins/microsoft-calendar/package.json index 2f8e24ba7c..aec094c029 100644 --- a/plugins/microsoft-calendar/package.json +++ b/plugins/microsoft-calendar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-microsoft-calendar", - "version": "0.1.10-next.0", + "version": "0.1.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -51,7 +51,6 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@microsoft/microsoft-graph-types": "^2.25.0", @@ -71,14 +70,10 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/newrelic-dashboard/CHANGELOG.md b/plugins/newrelic-dashboard/CHANGELOG.md index d4fc089300..ac5d043efe 100644 --- a/plugins/newrelic-dashboard/CHANGELOG.md +++ b/plugins/newrelic-dashboard/CHANGELOG.md @@ -1,5 +1,102 @@ # @backstage/plugin-newrelic-dashboard +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## 0.3.4 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.3.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.3.4-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.3.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + ## 0.3.3-next.0 ### Patch Changes diff --git a/plugins/newrelic-dashboard/package.json b/plugins/newrelic-dashboard/package.json index 35360c23be..e5a63a6971 100644 --- a/plugins/newrelic-dashboard/package.json +++ b/plugins/newrelic-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-newrelic-dashboard", - "version": "0.3.3-next.0", + "version": "0.3.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,7 +36,6 @@ "@backstage/plugin-catalog-react": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "react-use": "^17.2.4" }, diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md index cb8ad604bc..8935f6c619 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,67 @@ # @backstage/plugin-newrelic +## 0.3.44 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 0.3.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## 0.3.44-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## 0.3.43 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## 0.3.43-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## 0.3.43-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.3.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + ## 0.3.43-next.0 ### Patch Changes diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 3f4ae91bbe..a67a2203d5 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-newrelic", "description": "A Backstage plugin that integrates towards New Relic", - "version": "0.3.43-next.0", + "version": "0.3.44", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,9 +36,7 @@ "dependencies": { "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "parse-link-header": "^2.0.0", @@ -50,17 +48,13 @@ "react-router-dom": "6.0.0-beta.0 || ^6.3.0" }, "devDependencies": { - "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "@types/parse-link-header": "^2.0.1", - "cross-fetch": "^4.0.0", "msw": "^1.2.3" }, "files": [ diff --git a/plugins/nomad-backend/CHANGELOG.md b/plugins/nomad-backend/CHANGELOG.md index edcee04e86..2c93b3a3d0 100644 --- a/plugins/nomad-backend/CHANGELOG.md +++ b/plugins/nomad-backend/CHANGELOG.md @@ -1,5 +1,95 @@ # @backstage/plugin-nomad-backend +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.11 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + +## 0.1.11-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + ## 0.1.10-next.0 ### Patch Changes diff --git a/plugins/nomad-backend/package.json b/plugins/nomad-backend/package.json index 074e35f1db..c44721a581 100644 --- a/plugins/nomad-backend/package.json +++ b/plugins/nomad-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-nomad-backend", - "version": "0.1.10-next.0", + "version": "0.1.12-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -42,7 +42,6 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.8", - "msw": "^1.0.0", "supertest": "^6.2.4" }, "files": [ diff --git a/plugins/nomad/CHANGELOG.md b/plugins/nomad/CHANGELOG.md index 6efac21380..e9d5afb1a1 100644 --- a/plugins/nomad/CHANGELOG.md +++ b/plugins/nomad/CHANGELOG.md @@ -1,5 +1,102 @@ # @backstage/plugin-nomad +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.1.10 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.1.10-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + +## 0.1.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/theme@0.5.0-next.0 + ## 0.1.9-next.0 ### Patch Changes diff --git a/plugins/nomad/package.json b/plugins/nomad/package.json index fc8cab5de7..17c345c761 100644 --- a/plugins/nomad/package.json +++ b/plugins/nomad/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-nomad", - "version": "0.1.9-next.0", + "version": "0.1.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,14 +30,11 @@ }, "dependencies": { "@backstage/catalog-model": "workspace:^", - "@backstage/config": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "^4.0.0-alpha.60", "@types/react": "^16.13.1 || ^17.0.0", "luxon": "^3.3.0", "react-use": "^17.2.4" @@ -49,14 +46,10 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "cross-fetch": "^4.0.0" }, "files": [ "dist" diff --git a/plugins/octopus-deploy/CHANGELOG.md b/plugins/octopus-deploy/CHANGELOG.md index 4855e1964e..ce7de86809 100644 --- a/plugins/octopus-deploy/CHANGELOG.md +++ b/plugins/octopus-deploy/CHANGELOG.md @@ -1,5 +1,100 @@ # @backstage/plugin-octopus-deploy +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.2.11 + +### Patch Changes + +- 7d96ba8: added install path and fixed import on plugin-octopus-deploy +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.2.11-next.2 + +### Patch Changes + +- 7d96ba8: added install path and fixed import on plugin-octopus-deploy +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.2.11-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## 0.2.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.2.10-next.0 ### Patch Changes diff --git a/plugins/octopus-deploy/README.md b/plugins/octopus-deploy/README.md index 9e37777091..3699b76678 100644 --- a/plugins/octopus-deploy/README.md +++ b/plugins/octopus-deploy/README.md @@ -8,6 +8,17 @@ Welcome to the octopus-deploy plugin! ## Getting started +### Installing + +To get started, first install the plugin with the following command: + +```bash +# From your Backstage root directory +yarn add --cwd packages/app @backstage/plugin-octopus-deploy +``` + +### Setup + This plugin (currently) uses the Backstage proxy to securely communicate with the Octopus Deploy API. To use it, you will need to generate an [API Key](https://octopus.com/docs/octopus-rest-api/how-to-create-an-api-key) within Octopus Deploy. @@ -35,7 +46,7 @@ octopusdeploy: ``` // In packages/app/src/components/catalog/EntityPage.tsx import { - isOctopusDeployAvailable + isOctopusDeployAvailable, EntityOctopusDeployContent } from '@backstage/plugin-octopus-deploy'; diff --git a/plugins/octopus-deploy/package.json b/plugins/octopus-deploy/package.json index 2e28dd8453..f37205e60b 100644 --- a/plugins/octopus-deploy/package.json +++ b/plugins/octopus-deploy/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-octopus-deploy", - "version": "0.2.10-next.0", + "version": "0.2.12-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,10 +33,7 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.9.13", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "react-use": "^17.2.4" }, @@ -47,14 +44,10 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist", diff --git a/plugins/opencost/CHANGELOG.md b/plugins/opencost/CHANGELOG.md index bc4fcbd562..16d73b63d2 100644 --- a/plugins/opencost/CHANGELOG.md +++ b/plugins/opencost/CHANGELOG.md @@ -1,5 +1,67 @@ # @backstage/plugin-opencost +## 0.2.4 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/core-plugin-api@1.8.1 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + +## 0.2.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.0 + ## 0.2.3-next.0 ### Patch Changes diff --git a/plugins/opencost/package.json b/plugins/opencost/package.json index fba53eb9da..1e1b4fb674 100644 --- a/plugins/opencost/package.json +++ b/plugins/opencost/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-opencost", - "version": "0.2.3-next.0", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,18 +25,15 @@ "dependencies": { "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/theme": "workspace:^", "@date-io/luxon": "1.x", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "^4.0.0-alpha.60", "@material-ui/pickers": "^3.3.10", "@material-ui/styles": "^4.11.5", "@types/react": "^16.13.1 || ^17.0.0", "axios": "^1.4.0", "date-fns": "^2.30.0", "lodash": "^4.17.21", - "react-use": "^17.2.4", "recharts": "^2.5.0" }, "peerDependencies": { @@ -45,14 +42,8 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", - "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@testing-library/jest-dom": "^6.0.0" }, "files": [ "dist", diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 80ca996f05..294d0f50d5 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,106 @@ # @backstage/plugin-org-react +## 0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.1.18 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + +## 0.1.18-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/catalog-model@1.4.3 + +## 0.1.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.1.17-next.0 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index 2893e283ee..f07229e557 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org-react", - "version": "0.1.17-next.0", + "version": "0.1.19-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,7 +37,6 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", @@ -52,13 +51,11 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", - "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/user-event": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 9157075dd9..1d2534ee4d 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,107 @@ # @backstage/plugin-org +## 0.6.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.6.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.20 + +## 0.6.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.6.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.6.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.6.18 + +### Patch Changes + +- 59c24b9: Fix issue where members inside of `` would be rendered as squished when the card itself was shrunk down. +- 3a65d9c: Support member list scrollable when parent has specified height +- 4785d05: Add permission check to catalog create and refresh button +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/plugin-catalog-common@1.0.19 + +## 0.6.18-next.3 + +### Patch Changes + +- 59c24b9: Fix issue where members inside of `` would be rendered as squished when the card itself was shrunk down. +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.6.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.6.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.6.18-next.0 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 15c64bd7b8..1050876a7b 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-org", "description": "A Backstage plugin that helps you create entity pages for your organization", - "version": "0.6.18-next.0", + "version": "0.6.20-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,8 +33,8 @@ "@backstage/catalog-model": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", @@ -56,14 +56,14 @@ "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/plugin-catalog": "workspace:^", + "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", "@backstage/test-utils": "workspace:^", "@backstage/types": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/user-event": "^14.0.0" }, "files": [ "dist" diff --git a/plugins/org/src/__testUtils__/catalogMocks.ts b/plugins/org/src/__testUtils__/catalogMocks.ts index b14856e345..a97caa9971 100644 --- a/plugins/org/src/__testUtils__/catalogMocks.ts +++ b/plugins/org/src/__testUtils__/catalogMocks.ts @@ -208,8 +208,7 @@ export const groupAUserOne: Entity = { profile: { displayName: 'Group A User One', email: 'group-a-user-one@testing.email', - picture: - 'https://avatars.dicebear.com/api/avataaars/breanna-davison@example.com.svg', + picture: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Buster', }, }, }; @@ -224,8 +223,7 @@ export const groupBUserOne: Entity = { profile: { displayName: 'Group B User One', email: 'group-b-user-one@testing.email', - picture: - 'https://avatars.dicebear.com/api/avataaars/breanna-davison@example.com.svg', + picture: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Buster', }, }, }; @@ -240,8 +238,7 @@ export const groupDUserOne: Entity = { profile: { displayName: 'Group D User One', email: 'group-d-user-one@testing.email', - picture: - 'https://avatars.dicebear.com/api/avataaars/breanna-davison@example.com.svg', + picture: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Buster', }, }, }; @@ -256,8 +253,7 @@ export const groupEUserOne: Entity = { profile: { displayName: 'Group E User One', email: 'group-e-user-one@testing.email', - picture: - 'https://avatars.dicebear.com/api/avataaars/breanna-davison@example.com.svg', + picture: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Buster', }, }, }; @@ -272,8 +268,7 @@ export const groupFUserOne: Entity = { profile: { displayName: 'Group F User One', email: 'group-f-user-one@testing.email', - picture: - 'https://avatars.dicebear.com/api/avataaars/breanna-davison@example.com.svg', + picture: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Buster', }, }, }; @@ -288,8 +283,7 @@ export const duplicatedUser: Entity = { profile: { displayName: 'Duplicated User', email: 'duplicated-user@testing.email', - picture: - 'https://avatars.dicebear.com/api/avataaars/breanna-davison@example.com.svg', + picture: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Buster', }, }, }; diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx index e10d6322e0..17f285c039 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx @@ -21,6 +21,8 @@ import { EntityProvider, entityRouteRef, } from '@backstage/plugin-catalog-react'; +import { permissionApiRef } from '@backstage/plugin-permission-react'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils'; import { Grid } from '@material-ui/core'; import React, { ComponentType, PropsWithChildren } from 'react'; @@ -48,7 +50,7 @@ const defaultEntity: GroupEntity = { displayName: 'Team A', email: 'team-a@example.com', picture: - 'https://avatars.dicebear.com/api/identicon/team-a@example.com.svg?background=%23fff&margin=25', + 'https://api.dicebear.com/7.x/identicon/svg?seed=Fluffy&backgroundType=solid,gradientLinear&backgroundColor=ffd5dc,b6e3f4', }, type: 'group', children: [], @@ -60,6 +62,10 @@ const catalogApi: Partial = { async refreshEntity() {}, }; +const permissionApi: typeof permissionApiRef.T = { + authorize: async () => ({ result: AuthorizeResult.ALLOW }), +}; + export const Default = () => ( @@ -76,7 +82,12 @@ export default { decorators: [ (Story: ComponentType>) => wrapInTestApp( - + , { @@ -111,7 +122,7 @@ const extraDetailsEntity: GroupEntity = { displayName: 'Team A', email: 'team-a@example.com', picture: - 'https://avatars.dicebear.com/api/identicon/team-a@example.com.svg?background=%23fff&margin=25', + 'https://api.dicebear.com/7.x/identicon/svg?seed=Fluffy&backgroundType=solid,gradientLinear&backgroundColor=ffd5dc,b6e3f4', }, type: 'group', children: [], @@ -141,7 +152,7 @@ const groupWithTitle: GroupEntity = { profile: { email: 'team-a@example.com', picture: - 'https://avatars.dicebear.com/api/identicon/team-a@example.com.svg?background=%23fff&margin=25', + 'https://api.dicebear.com/7.x/identicon/svg?seed=Fluffy&backgroundType=solid,gradientLinear&backgroundColor=ffd5dc,b6e3f4', }, type: 'group', children: [], diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index 76527362ff..33c6ee137a 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -54,6 +54,8 @@ import EditIcon from '@material-ui/icons/Edit'; import EmailIcon from '@material-ui/icons/Email'; import GroupIcon from '@material-ui/icons/Group'; import { LinksGroup } from '../../Meta'; +import { useEntityPermission } from '@backstage/plugin-catalog-react/alpha'; +import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common/alpha'; const CardTitle = (props: { title: string }) => ( @@ -70,6 +72,9 @@ export const GroupProfileCard = (props: { const catalogApi = useApi(catalogApiRef); const alertApi = useApi(alertApiRef); const { entity: group } = useEntity(); + const { allowed: canRefresh } = useEntityPermission( + catalogEntityRefreshPermission, + ); const refreshEntity = useCallback(async () => { await catalogApi.refreshEntity(stringifyEntityRef(group)); @@ -127,7 +132,7 @@ export const GroupProfileCard = (props: { variant={props.variant} action={ <> - {allowRefresh && ( + {allowRefresh && canRefresh && ( { const displayName = profile?.displayName ?? metaName; return ( - - + + + - - - - - - {profile?.email && ( - - - - )} - {description && ( - {description} - )} - + + + + {profile?.email && ( + + + + )} + {description && ( + {description} + )} - + ); }; -const useListStyles = makeStyles(() => ({ +const useListStyles = makeStyles(theme => ({ root: { height: '100%', }, cardContent: { overflow: 'auto', }, + memberList: { + display: 'grid', + gap: theme.spacing(1.5), + gridTemplateColumns: `repeat(auto-fit, minmax(auto, ${theme.spacing( + 34, + )}px))`, + }, })); /** @public */ @@ -219,6 +224,25 @@ export const MembersListCard = (props: { /> ); + let memberList: React.JSX.Element; + if (members && members.length > 0) { + memberList = ( + + {members.slice(pageSize * (page - 1), pageSize * page).map(member => ( + + ))} + + ); + } else { + memberList = ( + + + This group has no {memberDisplayTitle.toLocaleLowerCase()}. + + + ); + } + return ( ) : ( - - {members && members.length > 0 ? ( - members - .slice(pageSize * (page - 1), pageSize * page) - .map(member => ( - - )) - ) : ( - - - This group has no {memberDisplayTitle.toLocaleLowerCase()}. - - - )} - + memberList )} diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx index 390fff12a4..1f50dc33d8 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx @@ -44,7 +44,7 @@ const defaultEntity: GroupEntity = { displayName: 'Team A', email: 'team-a@example.com', picture: - 'https://avatars.dicebear.com/api/identicon/team-a@example.com.svg?background=%23fff&margin=25', + 'https://api.dicebear.com/7.x/identicon/svg?seed=Fluffy&backgroundType=solid,gradientLinear&backgroundColor=ffd5dc,b6e3f4', }, type: 'group', children: [], diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx index 02598691e0..6326a043bc 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx @@ -45,8 +45,6 @@ const defaultEntity: UserEntity = { profile: { displayName: 'Guest User', email: 'guest@example.com', - picture: - 'https://avatars.dicebear.com/api/avataaars/guest@example.com.svg?background=%23fff', }, memberOf: ['team-a'], }, @@ -125,8 +123,6 @@ const extraDetailsEntity: UserEntity = { profile: { displayName: 'Guest User', email: 'guest@example.com', - picture: - 'https://avatars.dicebear.com/api/avataaars/guest@example.com.svg?background=%23fff', }, memberOf: ['team-a'], }, diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index 40c6ff4e02..a97f2a9085 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,123 @@ # @backstage/plugin-pagerduty +## 0.7.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-home-react@0.1.8-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## 0.7.1 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-home-react@0.1.7 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home-react@0.1.7-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-home-react@0.1.7-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.7.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-home-react@0.1.7-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.7.0 + +### Minor Changes + +- 5fca16f: This package has been deprecated, consider using [@pagerduty/backstage-plugin](https://github.com/pagerduty/backstage-plugin) instead. + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-home-react@0.1.6 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.7.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-home-react@0.1.6-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-home-react@0.1.6-next.2 + +## 0.7.0-next.1 + +### Minor Changes + +- 5fca16fdf6: This package has been deprecated, consider using [@pagerduty/backstage-plugin](https://github.com/pagerduty/backstage-plugin) instead. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-home-react@0.1.6-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.6.9-next.0 ### Patch Changes diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md index bf748439c9..c1079004e3 100644 --- a/plugins/pagerduty/README.md +++ b/plugins/pagerduty/README.md @@ -1,217 +1,3 @@ # PagerDuty + Backstage Integration Benefits -- Display relevant PagerDuty information about an entity within Backstage, such as the escalation policy, if there are any active incidents, and recent changes -- Trigger an incident to the currently on-call responder(s) for a service - -## How it Works - -- The Backstage PagerDuty plugin allows PagerDuty information about a Backstage entity to be displayed within Backstage. This includes active incidents, recent change events, as well as the current on-call responders' names, email addresses, and links to their profiles in PagerDuty. -- Incidents can be manually triggered via the plugin with a user-provided description, which will in turn notify the current on-call responders (Alternatively, the plugin can be configured with an optional `readOnly` property to suppress the ability to trigger incidents from the plugin). - - _Note: This feature is only available when providing the `pagerduty.com/integration-key` annotation_ -- Change events will be displayed in a separate tab. If the change event payload has additional links the first link only will be rendered. - -## Requirements - -- Setup of the PagerDuty plugin for Backstage requires a PagerDuty Admin role in order to generate the necessary authorizations, such as the API token. If you do not have this role, please reach out to an Admin or Account Owner within your organization to request configuration of this plugin. - -## Feature Overview - -### View any open incidents - -![PagerDuty plugin showing no incidents and the on-call rotation](doc/pd1.png) - -### Email link, and view contact information for staff on call - -![PagerDuty plugin showing on-call rotation contact information](doc/pd2.png) - -### Trigger an incident for a service - -![PagerDuty plugin popup modal for creating an incident](doc/pd3.png) - -![PagerDuty plugin showing an active incident](doc/pd4.png) - -## Support - -If you need help with this plugin, please reach out on the [Backstage Discord server](https://discord.gg/backstage-687207715902193673). - -## Integration Walk-through - -### In PagerDuty - -#### Integrating With a PagerDuty Service - -1. From the **Configuration** menu, select **Services**. -2. There are two ways to add an integration to a service: - - **If you are adding your integration to an existing service**: Click the **name** of the service you want to add the integration to. Then, select the **Integrations** tab and click the **New Integration** button. - - **If you are creating a new service for your integration**: Please read the documentation in section [Configuring Services and Integrations](https://support.pagerduty.com/docs/services-and-integrations#section-configuring-services-and-integrations) and follow the steps outlined in the [Create a New Service](https://support.pagerduty.com/docs/services-and-integrations#section-create-a-new-service) section, selecting **Backstage** as the **Integration Type** in step 4. Continue with the **In Backstage** section (below) once you have finished these steps. -3. Enter an **Integration Name** in the format `monitoring-tool-service-name` (e.g. `Backstage-Shopping-Cart`) and select **Backstage** from the Integration Type menu. -4. Click the **Add Integration** button to save your new integration. You will be redirected to the Integrations tab for your service. -5. An **Integration Key** will be generated on this screen. Keep this key saved in a safe place, as it will be used when you configure the integration with **Backstage** in the next section. - ![](https://pdpartner.s3.amazonaws.com/ig-template-copy-integration-key.png) - -### In Backstage - -#### Install the plugin - -The file paths mentioned in the following steps are relative to your app's root directory — for example, the directory created by following the [Getting Started](https://backstage.io/docs/getting-started/) guide and creating your app with `npx @backstage/create-app`. - -First, install the PagerDuty plugin via a CLI: - -```bash -# From your Backstage root directory -yarn add --cwd packages/app @backstage/plugin-pagerduty -``` - -Next, add the plugin to `EntityPage.tsx` in `packages/app/src/components/catalog` by adding the following code snippets. - -Add the following imports to the top of the file: - -```ts -import { - isPluginApplicableToEntity as isPagerDutyAvailable, - EntityPagerDutyCard, -} from '@backstage/plugin-pagerduty'; -``` - -Find `const overviewContent` in `EntityPage.tsx`, and add the following snippet inside the outermost `Grid` defined there, just before the closing `` tag: - -```ts - - - - - - - -``` - -When you're done, the `overviewContent` definition should look something like this: - -```ts -const overviewContent = ( - - ... - - - - - - - - -); -``` - -#### Configure the plugin - -First, [annotate](https://backstage.io/docs/features/software-catalog/descriptor-format#annotations-optional) the appropriate entity with the PagerDuty integration key in its `.yaml` configuration file: - -```yaml -annotations: - pagerduty.com/integration-key: [INTEGRATION_KEY] -``` - -#### The homepage component - -You may also add the component to the homepage of Backstage. Edit the `HomePage.tsx` file, import `PagerDutyHomepageCard` and add it to the home page. e.g. - -```typescript jsx -... -export const homePage = ( - - ... - - - ... - - - - -); -``` - -Next, provide the [API token](https://support.pagerduty.com/docs/generating-api-keys#generating-a-general-access-rest-api-key) that the client will use to make requests to the [PagerDuty API](https://developer.pagerduty.com/docs/rest-api-v2/rest-api/). - -Add the proxy configuration in `app-config.yaml`: - -```yaml -proxy: - ... - '/pagerduty': - target: https://api.pagerduty.com - headers: - Authorization: Token token=${PAGERDUTY_TOKEN} -``` - -Then, start the backend, passing the PagerDuty API token as an environment variable: - -```bash -$ PAGERDUTY_TOKEN='' yarn start -``` - -This will proxy the request by adding an `Authorization` header with the provided token. - -#### Optional configuration - -##### Annotating with Service ID - -If you want to integrate a PagerDuty service with Backstage but don't want to use an integration key, you can also [annotate](https://backstage.io/docs/features/software-catalog/descriptor-format#annotations-optional) the appropriate entity with a PagerDuty Service ID instead - -```yaml -annotations: - pagerduty.com/service-id: [SERVICE_ID] -``` - -This service ID can be found by navigating to a Service within PagerDuty and pulling the ID value out of the URL. - -1. From the **Configuration** menu within PagerDuty, select **Services**. -2. Click the **name** of the service you want to represent for your Entity. - -Your browser URL should now be located at `https://pagerduty.com/service-directory/[SERVICE_ID]`. - -_Note: When annotating with `pagerduty.com/service-id`, the feature to Create Incidents is not currently supported_ - -##### Custom Events URL - -If you want to override the default URL used for events, you can add it to `app-config.yaml`: - -```yaml -pagerDuty: - eventsBaseUrl: 'https://events.pagerduty.com/v2' -``` - -To suppress the rendering of the actionable incident-creation button, the `PagerDutyCard` can also be instantiated in `readOnly` mode: - -```ts - -``` - -**WARNING**: In current implementation, the PagerDuty plugin requires the `/pagerduty` proxy endpoint be exposed by the Backstage backend as an unprotected endpoint, in effect enabling PagerDuty API access using the configured `PAGERDUTY_TOKEN` for any user or process with access to the `/pagerduty` Backstage backend endpoint. If you regard this as problematic, consider using the plugin in `readOnly` mode (``) using the following proxy configuration: - -```yaml -proxy: - '/pagerduty': - target: https://api.pagerduty.com - headers: - Authorization: Token token=${PAGERDUTY_TOKEN} - # prohibit the `/pagerduty` proxy endpoint from servicing non-GET requests - allowedMethods: ['GET'] -``` - -## How to Uninstall - -1. Remove any configuration added in Backstage yaml files, such as the proxy configuration in `app-config.yaml` and the integration key in an entity's annotations. -2. Remove the added code snippets from `EntityPage.tsx` -3. Remove the plugin package: - -```bash -# From your Backstage root directory -yarn remove --cwd packages/app @backstage/plugin-pagerduty -``` - -4. [Delete the integration](https://support.pagerduty.com/docs/services-and-integrations#delete-an-integration-from-a-service) from the service in PagerDuty +This package has been deprecated, consider using [@pagerduty/backstage-plugin](https://github.com/pagerduty/backstage-plugin) instead. diff --git a/plugins/pagerduty/api-report.md b/plugins/pagerduty/api-report.md index db73259dd5..a96cab1af1 100644 --- a/plugins/pagerduty/api-report.md +++ b/plugins/pagerduty/api-report.md @@ -16,7 +16,7 @@ import { JSX as JSX_2 } from 'react'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; -// @public (undocumented) +// @public @deprecated (undocumented) export const EntityPagerDutyCard: ( props: EntityPagerDutyCardProps, ) => JSX_2.Element; @@ -27,7 +27,7 @@ export type EntityPagerDutyCardProps = { disableChangeEvents?: boolean; }; -// @public (undocumented) +// @public @deprecated (undocumented) export const HomePagePagerDutyCard: ( props: CardExtensionProps, ) => JSX_2.Element; @@ -138,7 +138,7 @@ export type PagerDutyClientApiDependencies = { fetchApi: FetchApi; }; -// @public (undocumented) +// @public @deprecated (undocumented) export type PagerDutyEntity = { integrationKey?: string; serviceId?: string; @@ -176,7 +176,7 @@ export type PagerDutyOnCallsResponse = { oncalls: PagerDutyOnCall[]; }; -// @public (undocumented) +// @public @deprecated (undocumented) const pagerDutyPlugin: BackstagePlugin<{}, {}>; export { pagerDutyPlugin }; export { pagerDutyPlugin as plugin }; diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 726ff91a63..383a7191bb 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-pagerduty", - "description": "A Backstage plugin that integrates towards PagerDuty", - "version": "0.6.9-next.0", + "description": "This plugin has been deprecated, consider using [@pagerduty/backstage-plugin](https://github.com/pagerduty/backstage-plugin) instead.", + "version": "0.7.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -40,12 +40,10 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-home-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", - "classnames": "^2.2.6", "luxon": "^3.0.0", "react-use": "^17.2.4" }, @@ -61,9 +59,7 @@ "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist", diff --git a/plugins/pagerduty/src/plugin.ts b/plugins/pagerduty/src/plugin.ts index 59cc5cd8cc..4144eb5878 100644 --- a/plugins/pagerduty/src/plugin.ts +++ b/plugins/pagerduty/src/plugin.ts @@ -30,7 +30,10 @@ export const rootRouteRef = createRouteRef({ id: 'pagerduty', }); -/** @public */ +/** + * @public + * @deprecated This plugin will be removed in a future release. Please use \@pagerduty/backstage-plugin plugin instead (https://www.npmjs.com/package/\@pagerduty/backstage-plugin). + */ export const pagerDutyPlugin = createPlugin({ id: 'pagerduty', apis: [ @@ -47,7 +50,10 @@ export const pagerDutyPlugin = createPlugin({ ], }); -/** @public */ +/** + * @public + * @deprecated This plugin and it's cards will be removed in a future release. Please use \@pagerduty/backstage-plugin plugin instead (https://www.npmjs.com/package/\@pagerduty/backstage-plugin). + */ export const EntityPagerDutyCard = pagerDutyPlugin.provide( createComponentExtension({ name: 'EntityPagerDutyCard', @@ -60,7 +66,10 @@ export const EntityPagerDutyCard = pagerDutyPlugin.provide( }), ); -/** @public */ +/** + * @public + * @deprecated This plugin and it's cards will be removed in a future release. Please use \@pagerduty/backstage-plugin plugin instead (https://www.npmjs.com/package/\@pagerduty/backstage-plugin). + */ export const HomePagePagerDutyCard = pagerDutyPlugin.provide( createCardExtension({ name: 'HomePagePagerDutyCard', diff --git a/plugins/pagerduty/src/types.ts b/plugins/pagerduty/src/types.ts index 9f38b429ef..e026a6ffbe 100644 --- a/plugins/pagerduty/src/types.ts +++ b/plugins/pagerduty/src/types.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -/** @public */ +/** + * @public + * @deprecated This plugin and it's types will be removed in a future release. Please use \@pagerduty/backstage-plugin plugin instead (https://www.npmjs.com/package/\@pagerduty/backstage-plugin). + */ export type PagerDutyEntity = { integrationKey?: string; serviceId?: string; diff --git a/plugins/periskop-backend/CHANGELOG.md b/plugins/periskop-backend/CHANGELOG.md index 5884a31457..a524779ac9 100644 --- a/plugins/periskop-backend/CHANGELOG.md +++ b/plugins/periskop-backend/CHANGELOG.md @@ -1,5 +1,87 @@ # @backstage/plugin-periskop-backend +## 0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + +## 0.2.7 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + +## 0.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## 0.2.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.2.7-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + +## 0.2.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + +## 0.2.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + +## 0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + ## 0.2.6-next.0 ### Patch Changes diff --git a/plugins/periskop-backend/alpha-api-report.md b/plugins/periskop-backend/api-report-alpha.md similarity index 100% rename from plugins/periskop-backend/alpha-api-report.md rename to plugins/periskop-backend/api-report-alpha.md diff --git a/plugins/periskop-backend/package.json b/plugins/periskop-backend/package.json index e1de6e6751..c342cf6ad4 100644 --- a/plugins/periskop-backend/package.json +++ b/plugins/periskop-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop-backend", - "version": "0.2.6-next.0", + "version": "0.2.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -54,7 +54,6 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.8", - "msw": "^1.0.0", "supertest": "^6.1.6" }, "files": [ diff --git a/plugins/periskop/CHANGELOG.md b/plugins/periskop/CHANGELOG.md index 139ab1c5f7..2b9409f9fa 100644 --- a/plugins/periskop/CHANGELOG.md +++ b/plugins/periskop/CHANGELOG.md @@ -1,5 +1,106 @@ # @backstage/plugin-periskop +## 0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## 0.1.27 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.27-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.26-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.1.26-next.0 ### Patch Changes diff --git a/plugins/periskop/package.json b/plugins/periskop/package.json index 9099c884a7..f8f5f02fbd 100644 --- a/plugins/periskop/package.json +++ b/plugins/periskop/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop", - "version": "0.1.26-next.0", + "version": "0.1.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,6 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", - "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "luxon": "^3.0.0", "react-use": "^17.2.4" @@ -49,15 +45,11 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", - "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "@types/luxon": "^3.0.0", - "msw": "^1.0.0" + "@types/luxon": "^3.0.0" }, "files": [ "dist", diff --git a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md index 59488140e0..14855fcb35 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,102 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/plugin-permission-common@0.7.12 + +## 0.1.6 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/plugin-permission-common@0.7.11 + +## 0.1.6-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## 0.1.5 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + +## 0.1.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## 0.1.5-next.2 + +### Patch Changes + +- cc4228e: Switched module ID to use kebab-case. +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + ## 0.1.5-next.0 ### Patch Changes diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json index 259186d50b..e607bc84f3 100644 --- a/plugins/permission-backend-module-policy-allow-all/package.json +++ b/plugins/permission-backend-module-policy-allow-all/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-backend-module-allow-all-policy", "description": "Allow all policy backend module for the permission plugin.", - "version": "0.1.5-next.0", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,7 +23,6 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", diff --git a/plugins/permission-backend-module-policy-allow-all/src/module.ts b/plugins/permission-backend-module-policy-allow-all/src/module.ts index 47ab202a00..f7a38f293c 100644 --- a/plugins/permission-backend-module-policy-allow-all/src/module.ts +++ b/plugins/permission-backend-module-policy-allow-all/src/module.ts @@ -23,8 +23,8 @@ import { AllowAllPermissionPolicy } from './policy'; * @public */ export const permissionModuleAllowAllPolicy = createBackendModule({ - moduleId: 'allowAllPolicy', pluginId: 'permission', + moduleId: 'allow-all-policy', register(reg) { reg.registerInit({ deps: { policy: policyExtensionPoint }, diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index d40c6eff77..287d320d61 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,121 @@ # @backstage/plugin-permission-backend +## 0.5.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + +## 0.5.32 + +### Patch Changes + +- b1acd9b: Updated README +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.5.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## 0.5.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.11 + +## 0.5.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + +## 0.5.31 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.5.31-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + +## 0.5.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + +## 0.5.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + ## 0.5.31-next.0 ### Patch Changes diff --git a/plugins/permission-backend/README.md b/plugins/permission-backend/README.md index 59fe1730ba..b4f6124bf4 100644 --- a/plugins/permission-backend/README.md +++ b/plugins/permission-backend/README.md @@ -1,6 +1,3 @@ # @backstage/plugin-permission-backend -> NOTE: THIS PACKAGE IS EXPERIMENTAL, HERE BE DRAGONS - -Backend for Backstage authorization and permissions. For more information, see -the [authorization PRFC](https://github.com/backstage/backstage/pull/7761). +Backend for Backstage authorization and permissions. For more information, see the [permissions documentation on Backstage.io](https://backstage.io/docs/permissions/overview). diff --git a/plugins/permission-backend/alpha-api-report.md b/plugins/permission-backend/api-report-alpha.md similarity index 100% rename from plugins/permission-backend/alpha-api-report.md rename to plugins/permission-backend/api-report-alpha.md diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index c3e8c7a54c..eb1f04273f 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.31-next.0", + "version": "0.5.33-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -56,7 +56,7 @@ "node-fetch": "^2.6.7", "winston": "^3.2.1", "yn": "^4.0.0", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index 26fb7356f9..74bd6db59b 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-permission-common +## 0.7.12 + +### Patch Changes + +- b1acd9b: Updated README +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.7.11 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + ## 0.7.10 ### Patch Changes diff --git a/plugins/permission-common/README.md b/plugins/permission-common/README.md index 7ba998bed5..fdb72b4be5 100644 --- a/plugins/permission-common/README.md +++ b/plugins/permission-common/README.md @@ -1,7 +1,3 @@ # @backstage/plugin-permission-common -> NOTE: THIS PACKAGE IS EXPERIMENTAL, HERE BE DRAGONS - -Isomorphic types and client for Backstage permissions and authorization. For -more information, see the [authorization -PRFC](https://github.com/backstage/backstage/pull/7761). +Isomorphic types and client for Backstage permissions and authorization. For more information, see the [permissions documentation on Backstage.io](https://backstage.io/docs/permissions/overview). diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index 0cc2d043df..b0ca7d74bf 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-common", "description": "Isomorphic types and client for Backstage permissions and authorization", - "version": "0.7.10", + "version": "0.7.12", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { @@ -47,7 +47,7 @@ "@backstage/types": "workspace:^", "cross-fetch": "^4.0.0", "uuid": "^8.0.0", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index a9c27025a9..9a954e9aca 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,112 @@ # @backstage/plugin-permission-node +## 0.7.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + +## 0.7.20 + +### Patch Changes + +- b1acd9b: Updated README +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.7.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + +## 0.7.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.11 + +## 0.7.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + +## 0.7.19 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.7.19-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + +## 0.7.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + +## 0.7.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + ## 0.7.19-next.0 ### Patch Changes diff --git a/plugins/permission-node/README.md b/plugins/permission-node/README.md index 82dfe54f9c..39859d8f4c 100644 --- a/plugins/permission-node/README.md +++ b/plugins/permission-node/README.md @@ -1,7 +1,3 @@ # @backstage/plugin-permission-node -> NOTE: THIS PACKAGE IS EXPERIMENTAL, HERE BE DRAGONS - -Common permission and authorization utilities for backend plugins. For more -information, see the [authorization -PRFC](https://github.com/backstage/backstage/pull/7761). +Common permission and authorization utilities for backend plugins. For more information, see the [permissions documentation on Backstage.io](https://backstage.io/docs/permissions/overview). diff --git a/plugins/permission-node/alpha-api-report.md b/plugins/permission-node/api-report-alpha.md similarity index 100% rename from plugins/permission-node/alpha-api-report.md rename to plugins/permission-node/api-report-alpha.md diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 4191d16a67..168fbdaa81 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.7.19-next.0", + "version": "0.7.21-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -55,7 +55,7 @@ "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", - "zod": "^3.21.4", + "zod": "^3.22.4", "zod-to-json-schema": "^3.20.4" }, "devDependencies": { diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index c5edb4e8f4..a432970e3f 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-permission-react +## 0.4.19 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- b1acd9b: Updated README +- Updated dependencies + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/config@1.1.1 + +## 0.4.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/config@1.1.1 + - @backstage/plugin-permission-common@0.7.11 + +## 0.4.19-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-permission-common@0.7.11 + +## 0.4.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/config@1.1.1 + +## 0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-permission-common@0.7.10 + ## 0.4.18-next.0 ### Patch Changes diff --git a/plugins/permission-react/README.md b/plugins/permission-react/README.md index 72383f024d..de22b0dba0 100644 --- a/plugins/permission-react/README.md +++ b/plugins/permission-react/README.md @@ -1,5 +1,3 @@ -# permission +# @backstage/plugin-permission-react -**NOTE: THIS PACKAGE IS EXPERIMENTAL!** - -Components and hooks to help implement permissions in Backstage frontend plugins. For more information, see the [authorization PRFC](https://github.com/backstage/backstage/pull/7761). +Components and hooks to help implement permissions in Backstage frontend plugins. For more information, see the [permissions documentation on Backstage.io](https://backstage.io/docs/permissions/overview). diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index 26f2cde701..7949433aa9 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.4.18-next.0", + "version": "0.4.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,8 +36,6 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@types/react": "^16.13.1 || ^17.0.0", - "cross-fetch": "^4.0.0", - "react-use": "^17.2.4", "swr": "^2.0.0" }, "peerDependencies": { diff --git a/plugins/playlist-backend/CHANGELOG.md b/plugins/playlist-backend/CHANGELOG.md index 29b12ea597..a4428bc5fe 100644 --- a/plugins/playlist-backend/CHANGELOG.md +++ b/plugins/playlist-backend/CHANGELOG.md @@ -1,5 +1,146 @@ # @backstage/plugin-playlist-backend +## 0.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-playlist-common@0.1.14 + +## 0.3.13 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-playlist-common@0.1.14 + +## 0.3.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + +## 0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-playlist-common@0.1.13 + +## 0.3.13-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-playlist-common@0.1.13 + +## 0.3.12 + +### Patch Changes + +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-playlist-common@0.1.13 + +## 0.3.12-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-playlist-common@0.1.12 + +## 0.3.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-playlist-common@0.1.12 + +## 0.3.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-playlist-common@0.1.12 + ## 0.3.12-next.0 ### Patch Changes diff --git a/plugins/playlist-backend/package.json b/plugins/playlist-backend/package.json index 28fc5e12da..976ae79ce1 100644 --- a/plugins/playlist-backend/package.json +++ b/plugins/playlist-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-playlist-backend", - "version": "0.3.12-next.0", + "version": "0.3.14-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -46,13 +46,12 @@ "uuid": "^8.2.0", "winston": "^3.2.1", "yn": "^4.0.0", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/supertest": "^2.0.8", - "msw": "^1.0.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/playlist-common/CHANGELOG.md b/plugins/playlist-common/CHANGELOG.md index af77b53222..0b65ee551f 100644 --- a/plugins/playlist-common/CHANGELOG.md +++ b/plugins/playlist-common/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-playlist-common +## 0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + +## 0.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + ## 0.1.12 ### Patch Changes diff --git a/plugins/playlist-common/package.json b/plugins/playlist-common/package.json index f19883a74d..9ab95c4c8a 100644 --- a/plugins/playlist-common/package.json +++ b/plugins/playlist-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-playlist-common", "description": "Common functionalities for the playlist plugin", - "version": "0.1.12", + "version": "0.1.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/playlist/CHANGELOG.md b/plugins/playlist/CHANGELOG.md index 12d289d0bf..7c90e8f956 100644 --- a/plugins/playlist/CHANGELOG.md +++ b/plugins/playlist/CHANGELOG.md @@ -1,5 +1,147 @@ # @backstage/plugin-playlist +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/plugin-search-react@1.7.6-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-playlist-common@0.1.14 + +## 0.2.3 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-search-react@1.7.5 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-playlist-common@0.1.14 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/plugin-search-react@1.7.5-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/plugin-search-react@1.7.5-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-playlist-common@0.1.13 + +## 0.2.3-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/plugin-search-react@1.7.5-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-playlist-common@0.1.13 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-search-react@1.7.4 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-playlist-common@0.1.13 + +## 0.2.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist-common@0.1.12 + - @backstage/plugin-search-react@1.7.4-next.3 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/plugin-search-react@1.7.4-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-playlist-common@0.1.12 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/plugin-search-react@1.7.4-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-playlist-common@0.1.12 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/playlist/package.json b/plugins/playlist/package.json index c23be32837..9f9cbd6724 100644 --- a/plugins/playlist/package.json +++ b/plugins/playlist/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-playlist", - "version": "0.2.2-next.0", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -39,7 +39,6 @@ "@backstage/plugin-permission-react": "workspace:^", "@backstage/plugin-playlist-common": "workspace:^", "@backstage/plugin-search-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", @@ -64,7 +63,6 @@ "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "msw": "^1.0.0", "swr": "^2.0.0" }, diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 49518973c8..49c650ce3e 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,85 @@ # @backstage/plugin-proxy-backend +## 0.4.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + +## 0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/config@1.1.1 + +## 0.4.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## 0.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + +## 0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/config@1.1.1 + +## 0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/config@1.1.1 + +## 0.4.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/config@1.1.1 + +## 0.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/config@1.1.1 + +## 0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/config@1.1.1 + ## 0.4.6-next.0 ### Patch Changes diff --git a/plugins/proxy-backend/alpha-api-report.md b/plugins/proxy-backend/api-report-alpha.md similarity index 100% rename from plugins/proxy-backend/alpha-api-report.md rename to plugins/proxy-backend/api-report-alpha.md diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index a3bc61540f..eac4fa43e6 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-proxy-backend", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", - "version": "0.4.6-next.0", + "version": "0.4.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/puppetdb/CHANGELOG.md b/plugins/puppetdb/CHANGELOG.md index 0147ed7ac7..255ac0f314 100644 --- a/plugins/puppetdb/CHANGELOG.md +++ b/plugins/puppetdb/CHANGELOG.md @@ -1,5 +1,106 @@ # @backstage/plugin-puppetdb +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + +## 0.1.12 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.12-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + +## 0.1.11-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.1.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + ## 0.1.11-next.0 ### Patch Changes diff --git a/plugins/puppetdb/package.json b/plugins/puppetdb/package.json index fbddd1a2e4..005950f3a6 100644 --- a/plugins/puppetdb/package.json +++ b/plugins/puppetdb/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-puppetdb", "description": "Backstage plugin to visualize resource information and Puppet facts from PuppetDB.", - "version": "0.1.11-next.0", + "version": "0.1.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -40,10 +40,7 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "^4.0.0-alpha.57", "@types/react": "^16.13.1 || ^17.0.0", "react-use": "^17.2.4" }, @@ -59,7 +56,6 @@ "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", "msw": "^1.0.1" }, "files": [ diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index 26b858f2ae..c52f70327b 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,76 @@ # @backstage/plugin-rollbar-backend +## 0.1.55-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/config@1.1.1 + +## 0.1.54 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/config@1.1.1 + +## 0.1.54-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + +## 0.1.54-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/config@1.1.1 + +## 0.1.54-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + +## 0.1.53 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/config@1.1.1 + +## 0.1.53-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + +## 0.1.53-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + +## 0.1.53-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + ## 0.1.53-next.0 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 6512f32d88..de6526701b 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar-backend", "description": "A Backstage backend plugin that integrates towards Rollbar", - "version": "0.1.53-next.0", + "version": "0.1.55-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index 8d8f35757a..ced8ca89fa 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,98 @@ # @backstage/plugin-rollbar +## 0.4.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + +## 0.4.29 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/core-plugin-api@1.8.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + +## 0.4.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 0.4.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-model@1.4.3 + +## 0.4.29-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + +## 0.4.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-model@1.4.3 + +## 0.4.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.3 + +## 0.4.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + +## 0.4.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0-next.0 + ## 0.4.28-next.0 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 5c51bdab2a..a3898e64d3 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar", "description": "A Backstage plugin that integrates towards Rollbar", - "version": "0.4.28-next.0", + "version": "0.4.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -38,9 +38,7 @@ "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", - "@backstage/theme": "workspace:^", "@material-ui/core": "^4.12.2", - "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "lodash": "^4.17.21", @@ -54,15 +52,11 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^9.0.0", "@testing-library/jest-dom": "^6.0.0", - "@testing-library/react": "^14.0.0", - "@testing-library/user-event": "^14.0.0", - "cross-fetch": "^4.0.0", - "msw": "^1.0.0" + "@testing-library/react": "^14.0.0" }, "files": [ "dist", diff --git a/plugins/scaffolder-backend-module-azure/.eslintrc.js b/plugins/scaffolder-backend-module-azure/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/scaffolder-backend-module-azure/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md new file mode 100644 index 0000000000..50b2070b72 --- /dev/null +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -0,0 +1,87 @@ +# @backstage/plugin-scaffolder-backend-module-azure + +## 0.1.2-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.1 + +### Patch Changes + +- d076ee4: Updated dependency `azure-devops-node-api` to `^12.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## 0.1.1-next.0 + +### Patch Changes + +- d076ee4: Updated dependency `azure-devops-node-api` to `^12.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## 0.1.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.0-next.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 diff --git a/plugins/scaffolder-backend-module-azure/README.md b/plugins/scaffolder-backend-module-azure/README.md new file mode 100644 index 0000000000..505ef36db1 --- /dev/null +++ b/plugins/scaffolder-backend-module-azure/README.md @@ -0,0 +1,5 @@ +# @backstage/plugin-scaffolder-backend-module-azure + +The azure module for [@backstage/plugin-scaffolder-backend](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend). + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/scaffolder-backend-module-azure/api-report.md b/plugins/scaffolder-backend-module-azure/api-report.md new file mode 100644 index 0000000000..d48d00e0e0 --- /dev/null +++ b/plugins/scaffolder-backend-module-azure/api-report.md @@ -0,0 +1,33 @@ +## API Report File for "@backstage/plugin-scaffolder-backend-module-azure" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; + +// @public +const azureModule: () => BackendFeature; +export default azureModule; + +// @public +export function createPublishAzureAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction< + { + repoUrl: string; + description?: string | undefined; + defaultBranch?: string | undefined; + sourcePath?: string | undefined; + token?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + }, + JsonObject +>; +``` diff --git a/plugins/scaffolder-backend-module-azure/catalog-info.yaml b/plugins/scaffolder-backend-module-azure/catalog-info.yaml new file mode 100644 index 0000000000..16ae855eae --- /dev/null +++ b/plugins/scaffolder-backend-module-azure/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-backend-module-azure + title: '@backstage/plugin-scaffolder-backend-module-azure' + description: The azure module for @backstage/plugin-scaffolder-backend +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json new file mode 100644 index 0000000000..2613104d7a --- /dev/null +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -0,0 +1,50 @@ +{ + "name": "@backstage/plugin-scaffolder-backend-module-azure", + "description": "The azure module for @backstage/plugin-scaffolder-backend", + "version": "0.1.2-next.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "backstage": { + "role": "backend-plugin-module" + }, + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, + "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" + }, + "dependencies": { + "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/integration": "workspace:^", + "@backstage/plugin-scaffolder-node": "workspace:^", + "azure-devops-node-api": "^12.0.0", + "yaml": "^2.0.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.examples.test.ts b/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.test.ts similarity index 95% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.examples.test.ts rename to plugins/scaffolder-backend-module-azure/src/actions/azure.examples.test.ts index e4fd18ef59..27989650ce 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.examples.test.ts +++ b/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.test.ts @@ -21,7 +21,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { getVoidLogger } from '@backstage/backend-common'; import { WebApi } from 'azure-devops-node-api'; import { PassThrough } from 'stream'; -import { initRepoAndPush } from '../helpers'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; import { examples } from './azure.examples'; jest.mock('azure-devops-node-api', () => ({ @@ -29,8 +29,9 @@ jest.mock('azure-devops-node-api', () => ({ getPersonalAccessTokenHandler: jest.fn().mockReturnValue(() => {}), })); -jest.mock('../helpers', () => { +jest.mock('@backstage/plugin-scaffolder-node', () => { return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), initRepoAndPush: jest.fn().mockResolvedValue({ commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', }), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.examples.ts b/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.examples.ts rename to plugins/scaffolder-backend-module-azure/src/actions/azure.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts b/plugins/scaffolder-backend-module-azure/src/actions/azure.test.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts rename to plugins/scaffolder-backend-module-azure/src/actions/azure.test.ts index 9b7673ea29..4401e01007 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts +++ b/plugins/scaffolder-backend-module-azure/src/actions/azure.test.ts @@ -19,8 +19,9 @@ jest.mock('azure-devops-node-api', () => ({ getPersonalAccessTokenHandler: jest.fn().mockReturnValue(() => {}), })); -jest.mock('../helpers', () => { +jest.mock('@backstage/plugin-scaffolder-node', () => { return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), initRepoAndPush: jest.fn().mockResolvedValue({ commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', }), @@ -36,7 +37,7 @@ import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { WebApi } from 'azure-devops-node-api'; import { PassThrough } from 'stream'; -import { initRepoAndPush } from '../helpers'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; describe('publish:azure', () => { const config = new ConfigReader({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts b/plugins/scaffolder-backend-module-azure/src/actions/azure.ts similarity index 97% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts rename to plugins/scaffolder-backend-module-azure/src/actions/azure.ts index a7df154687..e90cfeade4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts +++ b/plugins/scaffolder-backend-module-azure/src/actions/azure.ts @@ -19,15 +19,18 @@ import { DefaultAzureDevOpsCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; -import { initRepoAndPush } from '../helpers'; +import { + initRepoAndPush, + getRepoSourceDirectory, + parseRepoUrl, + createTemplateAction, +} from '@backstage/plugin-scaffolder-node'; import { GitRepositoryCreateOptions } from 'azure-devops-node-api/interfaces/GitInterfaces'; import { getBearerHandler, getPersonalAccessTokenHandler, WebApi, } from 'azure-devops-node-api'; -import { getRepoSourceDirectory, parseRepoUrl } from './util'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { Config } from '@backstage/config'; import { examples } from './azure.examples'; diff --git a/plugins/scaffolder-react/src/legacy/index.ts b/plugins/scaffolder-backend-module-azure/src/actions/index.ts similarity index 95% rename from plugins/scaffolder-react/src/legacy/index.ts rename to plugins/scaffolder-backend-module-azure/src/actions/index.ts index c539a8ba60..76ea7c2f07 100644 --- a/plugins/scaffolder-react/src/legacy/index.ts +++ b/plugins/scaffolder-backend-module-azure/src/actions/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './extensions'; +export * from './azure'; diff --git a/plugins/scaffolder-backend-module-azure/src/index.ts b/plugins/scaffolder-backend-module-azure/src/index.ts new file mode 100644 index 0000000000..b5650d8723 --- /dev/null +++ b/plugins/scaffolder-backend-module-azure/src/index.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * A module for the scaffolder backend that lets you interact with azure + * + * @packageDocumentation + */ + +export * from './actions'; +export { azureModule as default } from './module'; diff --git a/plugins/scaffolder-backend-module-azure/src/module.ts b/plugins/scaffolder-backend-module-azure/src/module.ts new file mode 100644 index 0000000000..40ca3f0978 --- /dev/null +++ b/plugins/scaffolder-backend-module-azure/src/module.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2024 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 { + createBackendModule, + coreServices, +} from '@backstage/backend-plugin-api'; +import { ScmIntegrations } from '@backstage/integration'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { createPublishAzureAction } from './actions'; + +/** + * @public + * The Azure Module for the Scaffolder Backend + */ +export const azureModule = createBackendModule({ + moduleId: 'azure', + pluginId: 'scaffolder', + register({ registerInit }) { + registerInit({ + deps: { + scaffolderActions: scaffolderActionsExtensionPoint, + config: coreServices.rootConfig, + }, + async init({ scaffolderActions, config }) { + const integrations = ScmIntegrations.fromConfig(config); + scaffolderActions.addActions( + createPublishAzureAction({ + integrations, + config, + }), + ); + }, + }); + }, +}); diff --git a/plugins/scaffolder-backend-module-bitbucket/.eslintrc.js b/plugins/scaffolder-backend-module-bitbucket/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md new file mode 100644 index 0000000000..9a0344eed8 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -0,0 +1,87 @@ +# @backstage/plugin-scaffolder-backend-module-bitbucket + +## 0.1.2-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- fc98bb6: Enhanced the pull request action to allow for adding new content to the PR as described in this issue #21762 +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.1 + +### Patch Changes + +- a694f71: The Scaffolder builtin actions now contains an action for running pipelines from Bitbucket Cloud Rest API +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## 0.1.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.0-next.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 diff --git a/plugins/scaffolder-backend-module-bitbucket/README.md b/plugins/scaffolder-backend-module-bitbucket/README.md new file mode 100644 index 0000000000..90b8680e80 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/README.md @@ -0,0 +1,5 @@ +# @backstage/plugin-scaffolder-backend-module-bitbucket + +The bitbucket module for [@backstage/plugin-scaffolder-backend](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend). + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/scaffolder-backend-module-bitbucket/api-report.md b/plugins/scaffolder-backend-module-bitbucket/api-report.md new file mode 100644 index 0000000000..7540b0c113 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/api-report.md @@ -0,0 +1,100 @@ +## API Report File for "@backstage/plugin-scaffolder-backend-module-bitbucket" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; + +// @public +const bitbucketModule: () => BackendFeature; +export default bitbucketModule; + +// @public +export const createBitbucketPipelinesRunAction: (options: { + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + workspace: string; + repo_slug: string; + body?: object | undefined; + token?: string | undefined; + }, + JsonObject +>; + +// @public @deprecated +export function createPublishBitbucketAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction< + { + repoUrl: string; + description?: string | undefined; + defaultBranch?: string | undefined; + repoVisibility?: 'private' | 'public' | undefined; + sourcePath?: string | undefined; + enableLFS?: boolean | undefined; + token?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + }, + JsonObject +>; + +// @public +export function createPublishBitbucketCloudAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction< + { + repoUrl: string; + description?: string | undefined; + defaultBranch?: string | undefined; + repoVisibility?: 'private' | 'public' | undefined; + sourcePath?: string | undefined; + token?: string | undefined; + }, + JsonObject +>; + +// @public +export function createPublishBitbucketServerAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction< + { + repoUrl: string; + description?: string | undefined; + defaultBranch?: string | undefined; + repoVisibility?: 'private' | 'public' | undefined; + sourcePath?: string | undefined; + enableLFS?: boolean | undefined; + token?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + }, + JsonObject +>; + +// @public +export function createPublishBitbucketServerPullRequestAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction< + { + repoUrl: string; + title: string; + description?: string | undefined; + targetBranch?: string | undefined; + sourceBranch: string; + token?: string | undefined; + }, + JsonObject +>; +``` diff --git a/plugins/scaffolder-backend-module-bitbucket/catalog-info.yaml b/plugins/scaffolder-backend-module-bitbucket/catalog-info.yaml new file mode 100644 index 0000000000..b0a94d1c13 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-backend-module-bitbucket + title: '@backstage/plugin-scaffolder-backend-module-bitbucket' + description: The bitbucket module for @backstage/plugin-scaffolder-backend +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json new file mode 100644 index 0000000000..6f02655692 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -0,0 +1,53 @@ +{ + "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", + "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", + "version": "0.1.2-next.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "backstage": { + "role": "backend-plugin-module" + }, + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, + "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" + }, + "dependencies": { + "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/integration": "workspace:^", + "@backstage/plugin-scaffolder-node": "workspace:^", + "fs-extra": "10.1.0", + "node-fetch": "^2.6.7", + "yaml": "^2.0.0" + }, + "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "msw": "^1.0.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.test.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.examples.test.ts rename to plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.test.ts index a1564d0498..73325ab5bf 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.examples.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.test.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -jest.mock('../helpers', () => { +jest.mock('@backstage/plugin-scaffolder-node', () => { return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), initRepoAndPush: jest.fn().mockResolvedValue({ commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', }), @@ -33,7 +34,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { initRepoAndPush } from '../helpers'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; import yaml from 'yaml'; import { sep } from 'path'; import { examples } from './bitbucket.examples'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.examples.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.examples.ts rename to plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.test.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts rename to plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.test.ts index 5d46a28a60..d6e068c039 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.test.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -jest.mock('../helpers', () => { +jest.mock('@backstage/plugin-scaffolder-node', () => { return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), initRepoAndPush: jest.fn().mockResolvedValue({ commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', }), @@ -24,7 +25,6 @@ jest.mock('../helpers', () => { }), }; }); - import { createPublishBitbucketAction } from './bitbucket'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -33,7 +33,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { initRepoAndPush } from '../helpers'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; describe('publish:bitbucket', () => { const config = new ConfigReader({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts rename to plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts index a4d664e0e7..76fe1de5df 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts @@ -19,10 +19,13 @@ import { BitbucketIntegrationConfig, ScmIntegrationRegistry, } from '@backstage/integration'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + createTemplateAction, + initRepoAndPush, + getRepoSourceDirectory, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import fetch, { Response, RequestInit } from 'node-fetch'; -import { initRepoAndPush } from '../helpers'; -import { getRepoSourceDirectory, parseRepoUrl } from './util'; import { Config } from '@backstage/config'; import { examples } from './bitbucket.examples'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketCloud.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloud.test.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketCloud.test.ts rename to plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloud.test.ts index 3159bfc562..7045d58e01 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketCloud.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloud.test.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -jest.mock('../helpers', () => { +jest.mock('@backstage/plugin-scaffolder-node', () => { return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), initRepoAndPush: jest.fn().mockResolvedValue({ commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', }), @@ -33,7 +34,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { initRepoAndPush } from '../helpers'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; describe('publish:bitbucketCloud', () => { const config = new ConfigReader({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketCloud.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloud.ts similarity index 90% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketCloud.ts rename to plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloud.ts index 00529d5856..e9d815f5e8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketCloud.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloud.ts @@ -16,11 +16,16 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + createTemplateAction, + initRepoAndPush, + getRepoSourceDirectory, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import fetch, { Response, RequestInit } from 'node-fetch'; -import { initRepoAndPush } from '../helpers'; -import { getRepoSourceDirectory, parseRepoUrl } from './util'; + import { Config } from '@backstage/config'; +import { getAuthorizationHeader } from './helpers'; const createRepository = async (opts: { workspace: string; @@ -89,29 +94,6 @@ const createRepository = async (opts: { return { remoteUrl, repoContentsUrl }; }; -const getAuthorizationHeader = (config: { - username?: string; - appPassword?: string; - token?: string; -}) => { - if (config.username && config.appPassword) { - const buffer = Buffer.from( - `${config.username}:${config.appPassword}`, - 'utf8', - ); - - return `Basic ${buffer.toString('base64')}`; - } - - if (config.token) { - return `Bearer ${config.token}`; - } - - throw new Error( - `Authorization has not been provided for Bitbucket Cloud. Please add either username + appPassword to the Integrations config or a user login auth token`, - ); -}; - /** * Creates a new action that initializes a git repository of the content in the workspace * and publishes it to Bitbucket Cloud. diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.test.ts new file mode 100644 index 0000000000..90ae2c08b9 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.test.ts @@ -0,0 +1,274 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { PassThrough } from 'stream'; +import { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun'; +import yaml from 'yaml'; +import { examples } from './bitbucketCloudPipelinesRun.examples'; +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; + +describe('bitbucket:pipelines:run', () => { + const config = new ConfigReader({ + integrations: { + bitbucketCloud: [ + { + username: 'u', + appPassword: 'p', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createBitbucketPipelinesRunAction({ integrations }); + const mockContext = { + input: {}, + workspacePath: 'wsp', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + const responseJson = { + repository: { + links: { + html: { href: 'https://bitbucket.org/test-workspace/test-repo-slug' }, + }, + }, + build_number: 1, + }; + const worker = setupServer(); + setupRequestMockHandlers(worker); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should trigger a pipeline for a branch', async () => { + expect.assertions(2); + worker.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + expect(req.body).toEqual({ + target: { + ref_type: 'branch', + type: 'pipeline_ref_target', + ref_name: 'master', + }, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + const ctx = Object.assign({}, mockContext, { + input: yaml.parse(examples[0].example).steps[0].input, + }); + await action.handler(ctx); + }); + + it('should trigger a pipeline for a commit on a branch', async () => { + expect.assertions(2); + worker.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + expect(req.body).toEqual({ + target: { + commit: { + type: 'commit', + hash: 'ce5b7431602f7cbba007062eeb55225c6e18e956', + }, + ref_type: 'branch', + type: 'pipeline_ref_target', + ref_name: 'master', + }, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + const ctx = Object.assign({}, mockContext, { + input: yaml.parse(examples[1].example).steps[0].input, + }); + await action.handler(ctx); + }); + + it('should trigger a specific pipeline definition for a commit', async () => { + expect.assertions(2); + worker.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + expect(req.body).toEqual({ + target: { + commit: { + type: 'commit', + hash: 'a3c4e02c9a3755eccdc3764e6ea13facdf30f923', + }, + selector: { + type: 'custom', + pattern: 'Deploy to production', + }, + type: 'pipeline_commit_target', + }, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + const ctx = Object.assign({}, mockContext, { + input: yaml.parse(examples[2].example).steps[0].input, + }); + await action.handler(ctx); + }); + + it('should trigger a specific pipeline definition for a commit on a branch or tag', async () => { + expect.assertions(2); + worker.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + expect(req.body).toEqual({ + target: { + commit: { + type: 'commit', + hash: 'a3c4e02c9a3755eccdc3764e6ea13facdf30f923', + }, + selector: { + type: 'custom', + pattern: 'Deploy to production', + }, + type: 'pipeline_ref_target', + ref_name: 'master', + ref_type: 'branch', + }, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + const ctx = Object.assign({}, mockContext, { + input: yaml.parse(examples[3].example).steps[0].input, + }); + await action.handler(ctx); + }); + + it('should trigger a custom pipeline with variables', async () => { + expect.assertions(2); + worker.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + expect(req.body).toEqual({ + target: { + type: 'pipeline_ref_target', + ref_name: 'master', + ref_type: 'branch', + selector: { + type: 'custom', + pattern: 'Deploy to production', + }, + }, + variables: [ + { key: 'var1key', value: 'var1value', secured: true }, + { + key: 'var2key', + value: 'var2value', + }, + ], + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + const ctx = Object.assign({}, mockContext, { + input: yaml.parse(examples[4].example).steps[0].input, + }); + await action.handler(ctx); + }); + + it('should trigger a pull request pipeline', async () => { + expect.assertions(2); + worker.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + expect(req.body).toEqual({ + target: { + type: 'pipeline_pullrequest_target', + source: 'pull-request-branch', + destination: 'master', + destination_commit: { + hash: '9f848b7', + }, + commit: { + hash: '1a372fc', + }, + pull_request: { + id: '3', + }, + selector: { + type: 'pull-requests', + pattern: '**', + }, + }, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + const ctx = Object.assign({}, mockContext, { + input: yaml.parse(examples[5].example).steps[0].input, + }); + await action.handler(ctx); + }); +}); diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.ts new file mode 100644 index 0000000000..d6255526af --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.ts @@ -0,0 +1,202 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Trigger a pipeline for a branch', + example: yaml.stringify({ + steps: [ + { + action: 'bitbucket:pipelines:run', + id: 'run-bitbucket-pipeline', + name: 'Run an example bitbucket pipeline', + input: { + workspace: 'test-workspace', + repo_slug: 'test-repo-slug', + body: { + target: { + ref_type: 'branch', + type: 'pipeline_ref_target', + ref_name: 'master', + }, + }, + }, + }, + ], + }), + }, + { + description: 'Trigger a pipeline for a commit on a branch', + example: yaml.stringify({ + steps: [ + { + action: 'bitbucket:pipelines:run', + id: 'run-bitbucket-pipeline', + name: 'Run an example bitbucket pipeline', + input: { + workspace: 'test-workspace', + repo_slug: 'test-repo-slug', + body: { + target: { + commit: { + type: 'commit', + hash: 'ce5b7431602f7cbba007062eeb55225c6e18e956', + }, + ref_type: 'branch', + type: 'pipeline_ref_target', + ref_name: 'master', + }, + }, + }, + }, + ], + }), + }, + { + description: 'Trigger a specific pipeline definition for a commit', + example: yaml.stringify({ + steps: [ + { + action: 'bitbucket:pipelines:run', + id: 'run-bitbucket-pipeline', + name: 'Run an example bitbucket pipeline', + input: { + workspace: 'test-workspace', + repo_slug: 'test-repo-slug', + body: { + target: { + commit: { + type: 'commit', + hash: 'a3c4e02c9a3755eccdc3764e6ea13facdf30f923', + }, + selector: { + type: 'custom', + pattern: 'Deploy to production', + }, + type: 'pipeline_commit_target', + }, + }, + }, + }, + ], + }), + }, + { + description: + 'Trigger a specific pipeline definition for a commit on a branch or tag', + example: yaml.stringify({ + steps: [ + { + action: 'bitbucket:pipelines:run', + id: 'run-bitbucket-pipeline', + name: 'Run an example bitbucket pipeline', + input: { + workspace: 'test-workspace', + repo_slug: 'test-repo-slug', + body: { + target: { + commit: { + type: 'commit', + hash: 'a3c4e02c9a3755eccdc3764e6ea13facdf30f923', + }, + selector: { + type: 'custom', + pattern: 'Deploy to production', + }, + type: 'pipeline_ref_target', + ref_name: 'master', + ref_type: 'branch', + }, + }, + }, + }, + ], + }), + }, + { + description: 'Trigger a custom pipeline with variables', + example: yaml.stringify({ + steps: [ + { + action: 'bitbucket:pipelines:run', + id: 'run-bitbucket-pipeline', + name: 'Run an example bitbucket pipeline', + input: { + workspace: 'test-workspace', + repo_slug: 'test-repo-slug', + body: { + target: { + type: 'pipeline_ref_target', + ref_name: 'master', + ref_type: 'branch', + selector: { + type: 'custom', + pattern: 'Deploy to production', + }, + }, + variables: [ + { key: 'var1key', value: 'var1value', secured: true }, + { + key: 'var2key', + value: 'var2value', + }, + ], + }, + }, + }, + ], + }), + }, + { + description: 'Trigger a pull request pipeline', + example: yaml.stringify({ + steps: [ + { + action: 'bitbucket:pipelines:run', + id: 'run-bitbucket-pipeline', + name: 'Run an example bitbucket pipeline', + input: { + workspace: 'test-workspace', + repo_slug: 'test-repo-slug', + body: { + target: { + type: 'pipeline_pullrequest_target', + source: 'pull-request-branch', + destination: 'master', + destination_commit: { + hash: '9f848b7', + }, + commit: { + hash: '1a372fc', + }, + pull_request: { + id: '3', + }, + selector: { + type: 'pull-requests', + pattern: '**', + }, + }, + }, + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.test.ts new file mode 100644 index 0000000000..dc76a79898 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.test.ts @@ -0,0 +1,156 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { PassThrough } from 'stream'; +import { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun'; +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; + +describe('bitbucket:pipelines:run', () => { + const config = new ConfigReader({ + integrations: { + bitbucketCloud: [ + { + username: 'u', + appPassword: 'p', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createBitbucketPipelinesRunAction({ integrations }); + const mockContext = { + input: {}, + workspacePath: 'wsp', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + const workspace = 'test-workspace'; + const repo_slug = 'test-repo-slug'; + const responseJson = { + repository: { + links: { + html: { href: 'https://bitbucket.org/test-workspace/test-repo-slug' }, + }, + }, + build_number: 1, + }; + const worker = setupServer(); + setupRequestMockHandlers(worker); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should throw if there is no integration credentials or token provided', async () => { + const configNoCreds = new ConfigReader({ + integrations: { + bitbucketCloud: [], + }, + }); + + const integrationsNoCreds = ScmIntegrations.fromConfig(configNoCreds); + const actionNoCreds = createBitbucketPipelinesRunAction({ + integrations: integrationsNoCreds, + }); + const testContext = Object.assign({}, mockContext, { + input: { workspace, repo_slug }, + }); + await expect(actionNoCreds.handler(testContext)).rejects.toThrow( + /Authorization has not been provided for Bitbucket Cloud/, + ); + }); + + it('should call the bitbucket api for running a pipeline with integration credentials', async () => { + expect.assertions(1); + worker.use( + rest.post( + `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo_slug}/pipelines`, + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + + const testContext = Object.assign({}, mockContext, { + input: { workspace, repo_slug }, + }); + await action.handler(testContext); + }); + + it('should call the bitbucket api for running a pipeline with input token', async () => { + expect.assertions(1); + worker.use( + rest.post( + `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo_slug}/pipelines`, + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer abc'); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + + const testContext = Object.assign({}, mockContext, { + input: { workspace, repo_slug, token: 'abc' }, + }); + await action.handler(testContext); + }); + + it('should call outputs with the correct data', async () => { + worker.use( + rest.post( + `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo_slug}/pipelines`, + (_, res, ctx) => { + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + + const testContext = Object.assign({}, mockContext, { + input: { workspace, repo_slug, token: 'abc' }, + }); + await action.handler(testContext); + expect(testContext.output).toHaveBeenCalledWith('buildNumber', 1); + expect(testContext.output).toHaveBeenCalledWith( + 'repoUrl', + 'https://bitbucket.org/test-workspace/test-repo-slug', + ); + expect(testContext.output).toHaveBeenCalledWith( + 'pipelinesUrl', + 'https://bitbucket.org/test-workspace/test-repo-slug/pipelines', + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.ts new file mode 100644 index 0000000000..c44fdcc824 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.ts @@ -0,0 +1,117 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { examples } from './bitbucketCloudPipelinesRun.examples'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import fetch, { Response } from 'node-fetch'; +import * as inputProps from './inputProperties'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { getAuthorizationHeader } from './helpers'; + +const id = 'bitbucket:pipelines:run'; +/** + * Creates a new action that triggers a run of a bitbucket pipeline + * + * @public + */ +export const createBitbucketPipelinesRunAction = (options: { + integrations: ScmIntegrationRegistry; +}) => { + const { integrations } = options; + return createTemplateAction<{ + workspace: string; + repo_slug: string; + body?: object; + token?: string; + }>({ + id, + description: 'Run a bitbucket cloud pipeline', + examples, + schema: { + input: { + type: 'object', + required: ['workspace', 'repo_slug'], + properties: { + workspace: inputProps.workspace, + repo_slug: inputProps.repo_slug, + body: inputProps.pipelinesRunBody, + token: inputProps.token, + }, + }, + output: { + type: 'object', + properties: { + buildNumber: { + title: 'Build number', + type: 'number', + }, + repoUrl: { + title: 'A URL to the pipeline repositry', + type: 'string', + }, + repoContentsUrl: { + title: 'A URL to the pipeline', + type: 'string', + }, + }, + }, + }, + supportsDryRun: false, + async handler(ctx) { + const { workspace, repo_slug, body, token } = ctx.input; + const host = 'bitbucket.org'; + const integrationConfig = integrations.bitbucketCloud.byHost(host); + + const authorization = getAuthorizationHeader( + token ? { token } : integrationConfig!.config, + ); + let response: Response; + try { + response = await fetch( + `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo_slug}/pipelines`, + { + method: 'POST', + headers: { + Authorization: authorization, + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body) ?? {}, + }, + ); + } catch (e) { + throw new Error(`Unable to run pipeline, ${e}`); + } + + if (response.status !== 201) { + throw new Error( + `Unable to run pipeline, ${response.status} ${ + response.statusText + }, ${await response.text()}`, + ); + } + + const responseObject = await response.json(); + + ctx.output('buildNumber', responseObject.build_number); + ctx.output('repoUrl', responseObject.repository.links.html.href); + ctx.output( + 'pipelinesUrl', + `${responseObject.repository.links.html.href}/pipelines`, + ); + }, + }); +}; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketServer.test.ts similarity index 99% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.test.ts rename to plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketServer.test.ts index 67a29210fc..ab7b52996f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketServer.test.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -jest.mock('../helpers', () => { +jest.mock('@backstage/plugin-scaffolder-node', () => { return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), initRepoAndPush: jest.fn().mockResolvedValue({ commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', }), @@ -33,7 +34,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { initRepoAndPush } from '../helpers'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; describe('publish:bitbucketServer', () => { const config = new ConfigReader({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketServer.ts similarity index 97% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.ts rename to plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketServer.ts index 66b7a46f77..31125deab2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketServer.ts @@ -19,10 +19,14 @@ import { getBitbucketServerRequestOptions, ScmIntegrationRegistry, } from '@backstage/integration'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + createTemplateAction, + initRepoAndPush, + getRepoSourceDirectory, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import fetch, { Response, RequestInit } from 'node-fetch'; -import { initRepoAndPush } from '../helpers'; -import { getRepoSourceDirectory, parseRepoUrl } from './util'; + import { Config } from '@backstage/config'; const createRepository = async (opts: { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServerPullRequest.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketServerPullRequest.test.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServerPullRequest.test.ts rename to plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketServerPullRequest.test.ts index af3999097f..3af47853c3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServerPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketServerPullRequest.test.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -jest.mock('../helpers', () => { +jest.mock('@backstage/plugin-scaffolder-node', () => { return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), initRepoAndPush: jest.fn().mockResolvedValue({ commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', }), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServerPullRequest.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketServerPullRequest.ts similarity index 68% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServerPullRequest.ts rename to plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketServerPullRequest.ts index 79711b9d69..fc123f0b80 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServerPullRequest.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketServerPullRequest.ts @@ -19,10 +19,18 @@ import { getBitbucketServerRequestOptions, ScmIntegrationRegistry, } from '@backstage/integration'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + createTemplateAction, + getRepoSourceDirectory, + commitAndPushBranch, + addFiles, + createBranch as createGitBranch, + cloneRepo, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import fetch, { RequestInit, Response } from 'node-fetch'; -import { parseRepoUrl } from './util'; import { Config } from '@backstage/config'; +import fs from 'fs-extra'; const createPullRequest = async (opts: { project: string; @@ -150,7 +158,51 @@ const findBranches = async (opts: { return undefined; }; +const createBranch = async (opts: { + project: string; + repo: string; + branchName: string; + authorization: string; + apiBaseUrl: string; + startPoint: string; +}) => { + const { project, repo, branchName, authorization, apiBaseUrl, startPoint } = + opts; + let response: Response; + const options: RequestInit = { + method: 'POST', + body: JSON.stringify({ + name: branchName, + startPoint, + }), + headers: { + Authorization: authorization, + 'Content-Type': 'application/json', + }, + }; + + try { + response = await fetch( + `${apiBaseUrl}/projects/${encodeURIComponent( + project, + )}/repos/${encodeURIComponent(repo)}/branches`, + options, + ); + } catch (e) { + throw new Error(`Unable to create branch, ${e}`); + } + + if (response.status !== 200) { + throw new Error( + `Unable to create branch, ${response.status} ${ + response.statusText + }, ${await response.text()}`, + ); + } + + return await response.json(); +}; /** * Creates a BitbucketServer Pull Request action. * @public @@ -159,7 +211,7 @@ export function createPublishBitbucketServerPullRequestAction(options: { integrations: ScmIntegrationRegistry; config: Config; }) { - const { integrations } = options; + const { integrations, config } = options; return createTemplateAction<{ repoUrl: string; @@ -266,7 +318,7 @@ export function createPublishBitbucketServerPullRequestAction(options: { apiBaseUrl, }); - const fromRef = await findBranches({ + let fromRef = await findBranches({ project, repo, branchName: sourceBranch, @@ -274,6 +326,83 @@ export function createPublishBitbucketServerPullRequestAction(options: { apiBaseUrl, }); + if (!fromRef) { + // create branch + ctx.logger.info( + `source branch not found -> creating branch named: ${sourceBranch} lastCommit: ${toRef.latestCommit}`, + ); + const latestCommit = toRef.latestCommit; + + fromRef = await createBranch({ + project, + repo, + branchName: sourceBranch, + authorization, + apiBaseUrl, + startPoint: latestCommit, + }); + + const remoteUrl = `https://${host}/scm/${project}/${repo}.git`; + + const auth = authConfig.token + ? { + token: token!, + } + : { + username: authConfig.username!, + password: authConfig.password!, + }; + + const gitAuthorInfo = { + name: config.getOptionalString('scaffolder.defaultAuthor.name'), + email: config.getOptionalString('scaffolder.defaultAuthor.email'), + }; + + const tempDir = await ctx.createTemporaryDirectory(); + const sourceDir = getRepoSourceDirectory(ctx.workspacePath, undefined); + await cloneRepo({ + url: remoteUrl, + dir: tempDir, + auth, + logger: ctx.logger, + ref: sourceBranch, + }); + + await createGitBranch({ + dir: tempDir, + auth, + logger: ctx.logger, + ref: sourceBranch, + }); + + // copy files + fs.cpSync(sourceDir, tempDir, { + recursive: true, + filter: path => { + return !(path.indexOf('.git') > -1); + }, + }); + + await addFiles({ + dir: tempDir, + auth, + logger: ctx.logger, + filepath: '.', + }); + + await commitAndPushBranch({ + dir: tempDir, + auth, + logger: ctx.logger, + commitMessage: + description ?? + config.getOptionalString('scaffolder.defaultCommitMessage') ?? + '', + gitAuthorInfo, + branch: sourceBranch, + }); + } + const pullRequestUrl = await createPullRequest({ project, repo, diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/helpers.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/helpers.ts new file mode 100644 index 0000000000..efa6fd64f4 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/helpers.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const getAuthorizationHeader = (config: { + username?: string; + appPassword?: string; + token?: string; +}) => { + if (config.username && config.appPassword) { + const buffer = Buffer.from( + `${config.username}:${config.appPassword}`, + 'utf8', + ); + + return `Basic ${buffer.toString('base64')}`; + } + + if (config.token) { + return `Bearer ${config.token}`; + } + + throw new Error( + `Authorization has not been provided for Bitbucket Cloud. Please add either username + appPassword to the Integrations config or a user login auth token`, + ); +}; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/index.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/index.ts new file mode 100644 index 0000000000..9b009255fd --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/index.ts @@ -0,0 +1,20 @@ +/* + * 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 * from './bitbucket'; +export * from './bitbucketCloud'; +export * from './bitbucketServer'; +export * from './bitbucketServerPullRequest'; +export { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun'; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/inputProperties.ts new file mode 100644 index 0000000000..d37426e982 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/inputProperties.ts @@ -0,0 +1,155 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const workspace = { + title: 'Workspace', + description: `The workspace name`, + type: 'string', +}; + +const repo_slug = { + title: 'Repository name', + description: 'The repository name', + type: 'string', +}; + +const ref_type = { + title: 'ref_type', + type: 'string', +}; + +const type = { + title: 'type', + type: 'string', +}; + +const ref_name = { + title: 'ref_name', + type: 'string', +}; +const source = { + title: 'source', + type: 'string', +}; +const destination = { + title: 'destination', + type: 'string', +}; +const hash = { + title: 'hash', + type: 'string', +}; + +const pattern = { + title: 'pattern', + type: 'string', +}; + +const id = { + title: 'id', + type: 'string', +}; + +const key = { + title: 'key', + type: 'string', +}; +const value = { + title: 'value', + type: 'string', +}; +const secured = { + title: 'secured', + type: 'boolean', +}; + +const token = { + title: 'Authentication Token', + type: 'string', + description: 'The token to use for authorization to BitBucket Cloud', +}; + +const destination_commit = { + title: 'destination_commit', + type: 'object', + properties: { + hash, + }, +}; + +const commit = { + title: 'commit', + type: 'object', + properties: { + type, + hash, + }, +}; + +const selector = { + title: 'selector', + type: 'object', + properties: { + type, + pattern, + }, +}; + +const pull_request = { + title: 'pull_request', + type: 'object', + properties: { + id, + }, +}; + +const pipelinesRunBody = { + title: 'Request Body', + description: + 'Request body properties: see Bitbucket Cloud Rest API documentation for more details', + type: 'object', + properties: { + target: { + title: 'target', + type: 'object', + properties: { + ref_type, + type, + ref_name, + source, + destination, + destination_commit, + commit, + selector, + pull_request, + }, + }, + variables: { + title: 'variables', + type: 'array', + items: { + type: 'object', + properties: { + key, + value, + secured, + }, + }, + }, + }, +}; + +export { workspace, repo_slug, pipelinesRunBody, token }; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/index.ts b/plugins/scaffolder-backend-module-bitbucket/src/index.ts new file mode 100644 index 0000000000..26f186b6dc --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/index.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * A module for the scaffolder backend that lets you interact with bitbucket + * + * @packageDocumentation + */ + +export * from './actions'; +export { bitbucketModule as default } from './module'; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/module.ts b/plugins/scaffolder-backend-module-bitbucket/src/module.ts new file mode 100644 index 0000000000..19554270a7 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/module.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2024 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 { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { + createBitbucketPipelinesRunAction, + createPublishBitbucketCloudAction, + createPublishBitbucketServerAction, + createPublishBitbucketServerPullRequestAction, +} from './actions'; +import { ScmIntegrations } from '@backstage/integration'; + +/** + * @public + * The Bitbucket Module for the Scaffolder Backend + */ +export const bitbucketModule = createBackendModule({ + moduleId: 'bitbucket', + pluginId: 'scaffolder', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + config: coreServices.rootConfig, + }, + async init({ scaffolder, config }) { + const integrations = ScmIntegrations.fromConfig(config); + + scaffolder.addActions( + createPublishBitbucketCloudAction({ integrations, config }), + createPublishBitbucketServerAction({ integrations, config }), + createPublishBitbucketServerPullRequestAction({ + integrations, + config, + }), + createBitbucketPipelinesRunAction({ integrations }), + ); + }, + }); + }, +}); diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index 96500ac2a5..1b943777b0 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,111 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.2.11-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.2.10 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## 0.2.10-next.1 + +### Patch Changes + +- 7acbb5a: Removed `mock-fs` dev dependency. +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## 0.2.10-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + ## 0.2.9-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/api-report.md b/plugins/scaffolder-backend-module-confluence-to-markdown/api-report.md index 70428f118c..e5701bfadb 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/api-report.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/api-report.md @@ -3,12 +3,17 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { UrlReader } from '@backstage/backend-common'; +// @public +const confluenceToMarkdownModule: () => BackendFeature; +export default confluenceToMarkdownModule; + // @public (undocumented) export const createConfluenceToMarkdownAction: (options: { reader: UrlReader; diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 0f441ab713..cf23ba1ba4 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,14 +1,12 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", - "version": "0.2.9-next.0", + "version": "0.2.11-next.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" + "access": "public" }, "backstage": { "role": "backend-plugin-module" @@ -19,6 +17,17 @@ "url": "https://github.com/backstage/backstage", "directory": "plugins/scaffolder-backend-module-confluence-to-markdown" }, + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", @@ -31,11 +40,11 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", - "@backstage/types": "workspace:^", "fs-extra": "10.1.0", "git-url-parse": "^13.1.0", "node-fetch": "^2.6.7", @@ -45,7 +54,6 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "mock-fs": "^5.2.0", "msw": "^1.0.0" }, "files": [ diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts index e5aeadbf00..f3bf1fef35 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.examples.test.ts @@ -19,28 +19,16 @@ import { getVoidLogger } from '@backstage/backend-common'; import { UrlReader } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; -import mockFs from 'mock-fs'; -import os from 'os'; -import { readFile, writeFile, createWriteStream } from 'fs-extra'; +import { + createMockDirectory, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { examples } from './confluenceToMarkdown.examples'; import yaml from 'yaml'; import { ActionContext } from '@backstage/plugin-scaffolder-node'; -jest.mock('fs-extra', () => ({ - mkdirSync: jest.fn(), - readFile: jest.fn().mockResolvedValue('File contents'), - writeFile: jest.fn().mockImplementation(() => { - return Promise.resolve(); - }), - outputFile: jest.fn(), - openSync: jest.fn(), - createWriteStream: jest.fn().mockReturnValue(new PassThrough()), - ensureDir: jest.fn(), -})); - describe('confluence:transform:markdown examples', () => { const baseUrl = `https://confluence.example.com`; const worker = setupServer(); @@ -72,7 +60,8 @@ describe('confluence:transform:markdown examples', () => { const logger = getVoidLogger(); jest.spyOn(logger, 'info'); - const mockTmpDir = os.tmpdir(); + const mockDir = createMockDirectory(); + const workspacePath = mockDir.resolve('workspace'); beforeEach(() => { reader = { @@ -84,17 +73,18 @@ describe('confluence:transform:markdown examples', () => { }; mockContext = { input: yaml.parse(examples[0].example).steps[0].input, - workspacePath: '/tmp', + workspacePath, logger, logStream: new PassThrough(), output: jest.fn(), - createTemporaryDirectory: jest.fn().mockResolvedValue(mockTmpDir), + createTemporaryDirectory: jest.fn(), }; - mockFs({ [`${mockTmpDir}/src/docs`]: {} }); + + mockDir.setContent({ 'workspace/mkdocs.yml': 'File contents' }); }); + afterEach(() => { jest.clearAllMocks(); - mockFs.restore(); }); it('should call confluence to markdown action successfully with results array', async () => { @@ -155,8 +145,10 @@ describe('confluence:transform:markdown examples', () => { `Fetching the mkdocs.yml catalog from https://github.com/organization-name/repo-name/blob/main/mkdocs.yml`, ); expect(logger.info).toHaveBeenCalledTimes(5); - expect(createWriteStream).toHaveBeenCalledTimes(1); - expect(readFile).toHaveBeenCalledTimes(1); - expect(writeFile).toHaveBeenCalledTimes(1); + + expect(mockDir.content({ path: 'workspace/docs' })).toEqual({ + img: { 'testing.pdf': Buffer.from('hello') }, + 'Page-Title.md': 'hello world', + }); }); }); diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts index 4e4f3091f1..5ce8bb2697 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.test.ts @@ -19,26 +19,14 @@ import { getVoidLogger } from '@backstage/backend-common'; import { UrlReader } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; -import mockFs from 'mock-fs'; -import os from 'os'; +import { + createMockDirectory, + setupRequestMockHandlers, +} from '@backstage/backend-test-utils'; import type { ActionContext } from '@backstage/plugin-scaffolder-node'; -import { readFile, writeFile, createWriteStream } from 'fs-extra'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -jest.mock('fs-extra', () => ({ - mkdirSync: jest.fn(), - readFile: jest.fn().mockResolvedValue('File contents'), - writeFile: jest.fn().mockImplementation(() => { - return Promise.resolve(); - }), - outputFile: jest.fn(), - openSync: jest.fn(), - createWriteStream: jest.fn().mockReturnValue(new PassThrough()), - ensureDir: jest.fn(), -})); - describe('confluence:transform:markdown', () => { const baseUrl = `https://nodomain.confluence.com`; const worker = setupServer(); @@ -70,7 +58,8 @@ describe('confluence:transform:markdown', () => { const logger = getVoidLogger(); jest.spyOn(logger, 'info'); - const mockTmpDir = os.tmpdir(); + const mockDir = createMockDirectory(); + const workspacePath = mockDir.resolve('workspace'); beforeEach(() => { reader = { @@ -85,19 +74,21 @@ describe('confluence:transform:markdown', () => { confluenceUrls: [ 'https://nodomain.confluence.com/display/testing/mkdocs', ], - repoUrl: 'https://notreal.github.com/space/backstage/mkdocs.yml', + repoUrl: + 'https://notreal.github.com/space/backstage/blob/main/mkdocs.yml', }, - workspacePath: '/tmp', + workspacePath, logger, logStream: new PassThrough(), output: jest.fn(), - createTemporaryDirectory: jest.fn().mockResolvedValue(mockTmpDir), + createTemporaryDirectory: jest.fn(), }; - mockFs({ [`${mockTmpDir}/src/docs`]: {} }); + + mockDir.setContent({ 'workspace/mkdocs.yml': 'File contents' }); }); + afterEach(() => { jest.clearAllMocks(); - mockFs.restore(); }); it('should call confluence to markdown action successfully with results array', async () => { @@ -155,12 +146,14 @@ describe('confluence:transform:markdown', () => { await action.handler(mockContext); expect(logger.info).toHaveBeenCalledWith( - `Fetching the mkdocs.yml catalog from https://notreal.github.com/space/backstage/mkdocs.yml`, + `Fetching the mkdocs.yml catalog from https://notreal.github.com/space/backstage/blob/main/mkdocs.yml`, ); expect(logger.info).toHaveBeenCalledTimes(5); - expect(createWriteStream).toHaveBeenCalledTimes(1); - expect(readFile).toHaveBeenCalledTimes(1); - expect(writeFile).toHaveBeenCalledTimes(1); + + expect(mockDir.content({ path: 'workspace/docs' })).toEqual({ + img: { 'testing.pdf': Buffer.from('hello') }, + 'mkdocs.md': 'hello world', + }); }); it('should call confluence to markdown action successfully with empty results array', async () => { @@ -202,13 +195,13 @@ describe('confluence:transform:markdown', () => { await action.handler(mockContext); expect(logger.info).toHaveBeenCalledWith( - `Fetching the mkdocs.yml catalog from https://notreal.github.com/space/backstage/mkdocs.yml`, + `Fetching the mkdocs.yml catalog from https://notreal.github.com/space/backstage/blob/main/mkdocs.yml`, ); expect(logger.info).toHaveBeenCalledTimes(5); - expect(createWriteStream).not.toHaveBeenCalled(); - expect(readFile).toHaveBeenCalledTimes(1); - expect(writeFile).toHaveBeenCalledTimes(1); + expect(mockDir.content({ path: 'workspace/docs' })).toEqual({ + 'mkdocs.md': 'hello world', + }); }); it('should fail on the first fetch call with response.ok set to false', async () => { diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/index.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/index.ts index 80d2e35cb3..04dd3d3221 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/index.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/index.ts @@ -21,3 +21,4 @@ */ export * from './actions'; +export { confluenceToMarkdownModule as default } from './module'; diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/module.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/module.ts new file mode 100644 index 0000000000..402f56c951 --- /dev/null +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/module.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2024 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 { + createBackendModule, + coreServices, +} from '@backstage/backend-plugin-api'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { createConfluenceToMarkdownAction } from './actions'; +import { ScmIntegrations } from '@backstage/integration'; + +/** + * @public + * The Confluence to Markdown Module for the Scaffolder Backend + */ +export const confluenceToMarkdownModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'confluence-to-markdown', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + config: coreServices.rootConfig, + reader: coreServices.urlReader, + }, + async init({ scaffolder, config, reader }) { + const integrations = ScmIntegrations.fromConfig(config); + scaffolder.addActions( + createConfluenceToMarkdownAction({ + config, + integrations, + reader, + }), + ); + }, + }); + }, +}); diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index d7cf6eef65..021b21be3e 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,113 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.34-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## 0.2.33 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## 0.2.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## 0.2.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.33-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## 0.2.32 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.2.32-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + +## 0.2.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## 0.2.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + ## 0.2.32-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/api-report.md b/plugins/scaffolder-backend-module-cookiecutter/api-report.md index 927d4b4c55..ed2b044189 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/api-report.md +++ b/plugins/scaffolder-backend-module-cookiecutter/api-report.md @@ -5,12 +5,17 @@ ```ts /// +import { BackendFeature } from '@backstage/backend-plugin-api'; import { ContainerRunner } from '@backstage/backend-common'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { UrlReader } from '@backstage/backend-common'; +// @public +const cookiecutterModule: () => BackendFeature; +export default cookiecutterModule; + // @public export function createFetchCookiecutterAction(options: { reader: UrlReader; diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index a9d7675198..32536047d1 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,14 +1,12 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", - "version": "0.2.32-next.0", + "version": "0.2.34-next.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" + "access": "public" }, "backstage": { "role": "backend-plugin-module" @@ -19,6 +17,17 @@ "url": "https://github.com/backstage/backstage", "directory": "plugins/scaffolder-backend-module-cookiecutter" }, + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", @@ -30,6 +39,7 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", @@ -44,8 +54,7 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/command-exists": "^1.2.0", - "@types/fs-extra": "^9.0.1", - "msw": "^1.0.0" + "@types/fs-extra": "^9.0.1" }, "files": [ "dist" diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/index.ts b/plugins/scaffolder-backend-module-cookiecutter/src/index.ts index 4f0ba9a407..c6df8295d1 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/index.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/index.ts @@ -21,3 +21,4 @@ */ export * from './actions'; +export { cookiecutterModule as default } from './module'; diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/module.ts b/plugins/scaffolder-backend-module-cookiecutter/src/module.ts new file mode 100644 index 0000000000..df1b201518 --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/src/module.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2024 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 { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { createFetchCookiecutterAction } from './actions'; +import { ScmIntegrations } from '@backstage/integration'; + +/** + * @public + * The Cookiecutter Module for the Scaffolder Backend + */ +export const cookiecutterModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'cookiecutter', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + config: coreServices.rootConfig, + reader: coreServices.urlReader, + }, + async init({ scaffolder, config, reader }) { + const integrations = ScmIntegrations.fromConfig(config); + scaffolder.addActions( + createFetchCookiecutterAction({ reader, integrations }), + ); + }, + }); + }, +}); diff --git a/plugins/scaffolder-backend-module-gerrit/.eslintrc.js b/plugins/scaffolder-backend-module-gerrit/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/scaffolder-backend-module-gerrit/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md new file mode 100644 index 0000000000..0b0d09dbbd --- /dev/null +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -0,0 +1,79 @@ +# @backstage/plugin-scaffolder-backend-module-gerrit + +## 0.1.2-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## 0.1.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- d86cd98: Add dry run support for the `publish:gerrit` action. +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.0-next.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 diff --git a/plugins/scaffolder-backend-module-gerrit/README.md b/plugins/scaffolder-backend-module-gerrit/README.md new file mode 100644 index 0000000000..f1492f5b35 --- /dev/null +++ b/plugins/scaffolder-backend-module-gerrit/README.md @@ -0,0 +1,5 @@ +# @backstage/plugin-scaffolder-backend-module-gerrit + +The gerrit module for [@backstage/plugin-scaffolder-backend](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend). + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/scaffolder-backend-module-gerrit/api-report.md b/plugins/scaffolder-backend-module-gerrit/api-report.md new file mode 100644 index 0000000000..e61de671a8 --- /dev/null +++ b/plugins/scaffolder-backend-module-gerrit/api-report.md @@ -0,0 +1,48 @@ +## API Report File for "@backstage/plugin-scaffolder-backend-module-gerrit" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; + +// @public +export function createPublishGerritAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction< + { + repoUrl: string; + description: string; + defaultBranch?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + sourcePath?: string | undefined; + }, + JsonObject +>; + +// @public +export function createPublishGerritReviewAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction< + { + repoUrl: string; + branch?: string | undefined; + sourcePath?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + }, + JsonObject +>; + +// @public +const gerritModule: () => BackendFeature; +export default gerritModule; +``` diff --git a/plugins/scaffolder-backend-module-gerrit/catalog-info.yaml b/plugins/scaffolder-backend-module-gerrit/catalog-info.yaml new file mode 100644 index 0000000000..3eeb8a9018 --- /dev/null +++ b/plugins/scaffolder-backend-module-gerrit/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-backend-module-gerrit + title: '@backstage/plugin-scaffolder-backend-module-gerrit' + description: The gerrit module for @backstage/plugin-scaffolder-backend +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json new file mode 100644 index 0000000000..a1d3110bff --- /dev/null +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -0,0 +1,52 @@ +{ + "name": "@backstage/plugin-scaffolder-backend-module-gerrit", + "description": "The gerrit module for @backstage/plugin-scaffolder-backend", + "version": "0.1.2-next.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "backstage": { + "role": "backend-plugin-module" + }, + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, + "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" + }, + "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/integration": "workspace:^", + "@backstage/plugin-scaffolder-node": "workspace:^", + "node-fetch": "^2.6.7", + "yaml": "^2.0.0" + }, + "devDependencies": { + "@backstage/backend-common": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "msw": "^1.0.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.examples.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.examples.ts rename to plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.test.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.test.ts similarity index 92% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.test.ts rename to plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.test.ts index 73dcc47225..d05c8727c4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.test.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.test.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -jest.mock('../helpers', () => { +jest.mock('@backstage/plugin-scaffolder-node', () => { return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), initRepoAndPush: jest.fn().mockResolvedValue({ commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', }), @@ -34,7 +35,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { initRepoAndPush } from '../helpers'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; describe('publish:gerrit', () => { const config = new ConfigReader({ @@ -240,7 +241,22 @@ describe('publish:gerrit', () => { 'https://gerrithost.org/repo/+/refs/heads/master', ); }); + it('should not create new projects on dryRun', async () => { + await action.handler({ + ...mockContext, + isDryRun: true, + input: { + ...mockContext.input, + repoUrl: 'gerrithost.org?workspace=workspace&repo=repo', + sourcePath: 'repository/', + }, + }); + expect(mockContext.output).toHaveBeenCalledWith( + 'commitHash', + 'abcd-dry-run-1234', + ); + }); afterEach(() => { jest.resetAllMocks(); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts similarity index 87% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.ts rename to plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts index 67ed868c4c..6f729227e6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts @@ -22,10 +22,13 @@ import { getGerritRequestOptions, ScmIntegrationRegistry, } from '@backstage/integration'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { getRepoSourceDirectory, parseRepoUrl } from './util'; +import { + createTemplateAction, + initRepoAndPush, + getRepoSourceDirectory, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import fetch, { Response, RequestInit } from 'node-fetch'; -import { initRepoAndPush } from '../helpers'; import { examples } from './gerrit.examples'; const createGerritProject = async ( @@ -97,6 +100,7 @@ export function createPublishGerritAction(options: { sourcePath?: string; }>({ id: 'publish:gerrit', + supportsDryRun: true, description: 'Initializes a git repository of the content in the workspace, and publishes it to Gerrit.', examples, @@ -187,6 +191,31 @@ export function createPublishGerritAction(options: { ); } + const repoContentsUrl = `${integrationConfig.config.gitilesBaseUrl}/${repo}/+/refs/heads/${defaultBranch}`; + const remoteUrl = `${integrationConfig.config.cloneUrl}/a/${repo}`; + const gitName = gitAuthorName + ? gitAuthorName + : config.getOptionalString('scaffolder.defaultAuthor.name'); + const gitEmail = gitAuthorEmail + ? gitAuthorEmail + : config.getOptionalString('scaffolder.defaultAuthor.email'); + const commitMessage = generateCommitMessage(config, gitCommitMessage); + + if (ctx.isDryRun) { + ctx.logger.info( + `Dry run arguments: ${{ + gitName, + gitEmail, + commitMessage, + ...ctx.input, + }}`, + ); + ctx.output('remoteUrl', remoteUrl); + ctx.output('commitHash', 'abcd-dry-run-1234'); + ctx.output('repoContentsUrl', repoContentsUrl); + return; + } + await createGerritProject(integrationConfig.config, { description, owner: owner, @@ -198,15 +227,10 @@ export function createPublishGerritAction(options: { password: integrationConfig.config.password!, }; const gitAuthorInfo = { - name: gitAuthorName - ? gitAuthorName - : config.getOptionalString('scaffolder.defaultAuthor.name'), - email: gitAuthorEmail - ? gitAuthorEmail - : config.getOptionalString('scaffolder.defaultAuthor.email'), + name: gitName, + email: gitEmail, }; - const remoteUrl = `${integrationConfig.config.cloneUrl}/a/${repo}`; const commitResult = await initRepoAndPush({ dir: getRepoSourceDirectory(ctx.workspacePath, sourcePath), remoteUrl, @@ -217,7 +241,6 @@ export function createPublishGerritAction(options: { gitAuthorInfo, }); - const repoContentsUrl = `${integrationConfig.config.gitilesBaseUrl}/${repo}/+/refs/heads/${defaultBranch}`; ctx.output('remoteUrl', remoteUrl); ctx.output('commitHash', commitResult?.commitHash); ctx.output('repoContentsUrl', repoContentsUrl); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerritReview.test.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.test.ts similarity index 93% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerritReview.test.ts rename to plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.test.ts index 55d7bde5a7..eeae3871ee 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerritReview.test.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.test.ts @@ -14,14 +14,19 @@ * limitations under the License. */ -jest.mock('../helpers'); +jest.mock('@backstage/plugin-scaffolder-node', () => { + return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), + commitAndPushRepo: jest.fn(), + }; +}); import { createPublishGerritReviewAction } from './gerritReview'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { commitAndPushRepo } from '../helpers'; +import { commitAndPushRepo } from '@backstage/plugin-scaffolder-node'; describe('publish:gerrit:review', () => { const config = new ConfigReader({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerritReview.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts similarity index 96% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerritReview.ts rename to plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts index 73e74f5cbc..bd8d0321ab 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerritReview.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts @@ -18,9 +18,12 @@ import crypto from 'crypto'; import { InputError } from '@backstage/errors'; import { Config } from '@backstage/config'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { getRepoSourceDirectory, parseRepoUrl } from './util'; -import { commitAndPushRepo } from '../helpers'; +import { + createTemplateAction, + commitAndPushRepo, + getRepoSourceDirectory, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; const generateGerritChangeId = (): string => { const changeId = crypto.randomBytes(20).toString('hex'); diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/index.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/index.ts new file mode 100644 index 0000000000..1918679320 --- /dev/null +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/index.ts @@ -0,0 +1,17 @@ +/* + * 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 * from './gerrit'; +export * from './gerritReview'; diff --git a/plugins/scaffolder-backend-module-gerrit/src/index.ts b/plugins/scaffolder-backend-module-gerrit/src/index.ts new file mode 100644 index 0000000000..a3c54a4ec7 --- /dev/null +++ b/plugins/scaffolder-backend-module-gerrit/src/index.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * A module for the scaffolder backend that lets you interact with gerrit + * + * @packageDocumentation + */ + +export * from './actions'; +export { gerritModule as default } from './module'; diff --git a/plugins/scaffolder-backend-module-gerrit/src/module.ts b/plugins/scaffolder-backend-module-gerrit/src/module.ts new file mode 100644 index 0000000000..32607431f3 --- /dev/null +++ b/plugins/scaffolder-backend-module-gerrit/src/module.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2024 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 { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { + createPublishGerritAction, + createPublishGerritReviewAction, +} from './actions'; +import { ScmIntegrations } from '@backstage/integration'; + +/** + * @public + * The Gerrit Module for the Scaffolder Backend + */ +export const gerritModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'geritt', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + config: coreServices.rootConfig, + }, + async init({ scaffolder, config }) { + const integrations = ScmIntegrations.fromConfig(config); + scaffolder.addActions( + createPublishGerritAction({ integrations, config }), + createPublishGerritReviewAction({ integrations, config }), + ); + }, + }); + }, +}); diff --git a/plugins/scaffolder-backend-module-github/.eslintrc.js b/plugins/scaffolder-backend-module-github/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md new file mode 100644 index 0000000000..88f68d392e --- /dev/null +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -0,0 +1,90 @@ +# @backstage/plugin-scaffolder-backend-module-github + +## 0.1.2-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.1 + +### Patch Changes + +- 5470300: Ensure `teamReviewers` list is unique before calling API +- bf92ae3: Updated dependency `octokit` to `^3.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## 0.1.1-next.0 + +### Patch Changes + +- bf92ae3: Updated dependency `octokit` to `^3.0.0`. +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## 0.1.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- cb6a65e: The `scaffolder.defaultCommitMessage` config value is now being used if provided and uses "initial commit" when it is not provided. +- 28949ea: Add a new action for creating github-autolink references for a repository: `github:autolinks:create` +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.0-next.0 + +### Minor Changes + +- 219d7f0: Create new scaffolder module for external integrations + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 diff --git a/plugins/scaffolder-backend-module-github/README.md b/plugins/scaffolder-backend-module-github/README.md new file mode 100644 index 0000000000..ba4b3a8787 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/README.md @@ -0,0 +1,5 @@ +# @backstage/plugin-scaffolder-backend-module-github + +The github module for [@backstage/plugin-scaffolder-backend](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend). + +_This plugin was created through the Backstage CLI_ diff --git a/plugins/scaffolder-backend-module-github/api-report.md b/plugins/scaffolder-backend-module-github/api-report.md new file mode 100644 index 0000000000..d6a759dfa6 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/api-report.md @@ -0,0 +1,385 @@ +## API Report File for "@backstage/plugin-scaffolder-backend-module-github" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { createPullRequest } from 'octokit-plugin-create-pull-request'; +import { GithubCredentialsProvider } from '@backstage/integration'; +import { JsonObject } from '@backstage/types'; +import { Octokit } from 'octokit'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { ScmIntegrations } from '@backstage/integration'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; + +// @public +export function createGithubActionsDispatchAction(options: { + integrations: ScmIntegrations; + githubCredentialsProvider?: GithubCredentialsProvider; +}): TemplateAction< + { + repoUrl: string; + workflowId: string; + branchOrTagName: string; + workflowInputs?: + | { + [key: string]: string; + } + | undefined; + token?: string | undefined; + }, + JsonObject +>; + +// @public +export function createGithubAutolinksAction(options: { + integrations: ScmIntegrations; + githubCredentialsProvider?: GithubCredentialsProvider; +}): TemplateAction< + { + repoUrl: string; + keyPrefix: string; + urlTemplate: string; + isAlphanumeric?: boolean | undefined; + token?: string | undefined; + }, + JsonObject +>; + +// @public +export function createGithubDeployKeyAction(options: { + integrations: ScmIntegrationRegistry; +}): TemplateAction< + { + repoUrl: string; + publicKey: string; + privateKey: string; + deployKeyName: string; + privateKeySecretName?: string | undefined; + token?: string | undefined; + }, + JsonObject +>; + +// @public +export function createGithubEnvironmentAction(options: { + integrations: ScmIntegrationRegistry; +}): TemplateAction< + { + repoUrl: string; + name: string; + deploymentBranchPolicy?: + | { + protected_branches: boolean; + custom_branch_policies: boolean; + } + | undefined; + customBranchPolicyNames?: string[] | undefined; + environmentVariables?: + | { + [key: string]: string; + } + | undefined; + secrets?: + | { + [key: string]: string; + } + | undefined; + token?: string | undefined; + }, + JsonObject +>; + +// @public +export function createGithubIssuesLabelAction(options: { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; +}): TemplateAction< + { + repoUrl: string; + number: number; + labels: string[]; + token?: string | undefined; + }, + JsonObject +>; + +// @public +export interface CreateGithubPullRequestActionOptions { + clientFactory?: (input: { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; + host: string; + owner: string; + repo: string; + token?: string; + }) => Promise< + Octokit & { + createPullRequest(options: createPullRequest.Options): Promise<{ + data: { + html_url: string; + number: number; + base: { + ref: string; + }; + }; + } | null>; + } + >; + githubCredentialsProvider?: GithubCredentialsProvider; + integrations: ScmIntegrationRegistry; +} + +// @public +export function createGithubRepoCreateAction(options: { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; +}): TemplateAction< + { + repoUrl: string; + description?: string | undefined; + homepage?: string | undefined; + access?: string | undefined; + deleteBranchOnMerge?: boolean | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + allowRebaseMerge?: boolean | undefined; + allowSquashMerge?: boolean | undefined; + squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE' | undefined; + squashMergeCommitMessage?: + | 'PR_BODY' + | 'COMMIT_MESSAGES' + | 'BLANK' + | undefined; + allowMergeCommit?: boolean | undefined; + allowAutoMerge?: boolean | undefined; + requireCodeOwnerReviews?: boolean | undefined; + bypassPullRequestAllowances?: + | { + users?: string[] | undefined; + teams?: string[] | undefined; + apps?: string[] | undefined; + } + | undefined; + requiredApprovingReviewCount?: number | undefined; + restrictions?: + | { + users: string[]; + teams: string[]; + apps?: string[] | undefined; + } + | undefined; + requiredStatusCheckContexts?: string[] | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; + requiredConversationResolution?: boolean | undefined; + repoVisibility?: 'internal' | 'private' | 'public' | undefined; + collaborators?: + | ( + | { + user: string; + access: string; + } + | { + team: string; + access: string; + } + | { + username: string; + access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; + } + )[] + | undefined; + hasProjects?: boolean | undefined; + hasWiki?: boolean | undefined; + hasIssues?: boolean | undefined; + token?: string | undefined; + topics?: string[] | undefined; + repoVariables?: + | { + [key: string]: string; + } + | undefined; + secrets?: + | { + [key: string]: string; + } + | undefined; + requireCommitSigning?: boolean | undefined; + }, + JsonObject +>; + +// @public +export function createGithubRepoPushAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; + githubCredentialsProvider?: GithubCredentialsProvider; +}): TemplateAction< + { + repoUrl: string; + description?: string | undefined; + defaultBranch?: string | undefined; + protectDefaultBranch?: boolean | undefined; + protectEnforceAdmins?: boolean | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + requireCodeOwnerReviews?: boolean | undefined; + dismissStaleReviews?: boolean | undefined; + bypassPullRequestAllowances?: + | { + users?: string[]; + teams?: string[]; + apps?: string[]; + } + | undefined; + requiredApprovingReviewCount?: number | undefined; + restrictions?: + | { + users: string[]; + teams: string[]; + apps?: string[]; + } + | undefined; + requiredStatusCheckContexts?: string[] | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; + requiredConversationResolution?: boolean | undefined; + sourcePath?: string | undefined; + token?: string | undefined; + requiredCommitSigning?: boolean | undefined; + }, + JsonObject +>; + +// @public +export function createGithubWebhookAction(options: { + integrations: ScmIntegrationRegistry; + defaultWebhookSecret?: string; + githubCredentialsProvider?: GithubCredentialsProvider; +}): TemplateAction< + { + repoUrl: string; + webhookUrl: string; + webhookSecret?: string | undefined; + events?: string[] | undefined; + active?: boolean | undefined; + contentType?: 'form' | 'json' | undefined; + insecureSsl?: boolean | undefined; + token?: string | undefined; + }, + JsonObject +>; + +// @public +export function createPublishGithubAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; + githubCredentialsProvider?: GithubCredentialsProvider; +}): TemplateAction< + { + repoUrl: string; + description?: string | undefined; + homepage?: string | undefined; + access?: string | undefined; + defaultBranch?: string | undefined; + protectDefaultBranch?: boolean | undefined; + protectEnforceAdmins?: boolean | undefined; + deleteBranchOnMerge?: boolean | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + allowRebaseMerge?: boolean | undefined; + allowSquashMerge?: boolean | undefined; + squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE' | undefined; + squashMergeCommitMessage?: + | 'PR_BODY' + | 'COMMIT_MESSAGES' + | 'BLANK' + | undefined; + allowMergeCommit?: boolean | undefined; + allowAutoMerge?: boolean | undefined; + sourcePath?: string | undefined; + bypassPullRequestAllowances?: + | { + users?: string[]; + teams?: string[]; + apps?: string[]; + } + | undefined; + requiredApprovingReviewCount?: number | undefined; + restrictions?: + | { + users: string[]; + teams: string[]; + apps?: string[]; + } + | undefined; + requireCodeOwnerReviews?: boolean | undefined; + dismissStaleReviews?: boolean | undefined; + requiredStatusCheckContexts?: string[] | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; + requiredConversationResolution?: boolean | undefined; + repoVisibility?: 'internal' | 'private' | 'public' | undefined; + collaborators?: + | ( + | { + user: string; + access: string; + } + | { + team: string; + access: string; + } + | { + username: string; + access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; + } + )[] + | undefined; + hasProjects?: boolean | undefined; + hasWiki?: boolean | undefined; + hasIssues?: boolean | undefined; + token?: string | undefined; + topics?: string[] | undefined; + repoVariables?: + | { + [key: string]: string; + } + | undefined; + secrets?: + | { + [key: string]: string; + } + | undefined; + requiredCommitSigning?: boolean | undefined; + }, + JsonObject +>; + +// @public +export const createPublishGithubPullRequestAction: ( + options: CreateGithubPullRequestActionOptions, +) => TemplateAction< + { + title: string; + branchName: string; + targetBranchName?: string | undefined; + description: string; + repoUrl: string; + draft?: boolean | undefined; + targetPath?: string | undefined; + sourcePath?: string | undefined; + token?: string | undefined; + reviewers?: string[] | undefined; + teamReviewers?: string[] | undefined; + commitMessage?: string | undefined; + update?: boolean | undefined; + }, + JsonObject +>; + +// @public +const githubModule: () => BackendFeature; +export default githubModule; +``` diff --git a/plugins/scaffolder-backend-module-github/catalog-info.yaml b/plugins/scaffolder-backend-module-github/catalog-info.yaml new file mode 100644 index 0000000000..5e70704940 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-backend-module-github + title: '@backstage/plugin-scaffolder-backend-module-github' + description: The github module for @backstage/plugin-scaffolder-backend +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json new file mode 100644 index 0000000000..8e0272c876 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/package.json @@ -0,0 +1,59 @@ +{ + "name": "@backstage/plugin-scaffolder-backend-module-github", + "description": "The github module for @backstage/plugin-scaffolder-backend", + "version": "0.1.2-next.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public" + }, + "backstage": { + "role": "backend-plugin-module" + }, + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, + "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" + }, + "dependencies": { + "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/integration": "workspace:^", + "@backstage/plugin-scaffolder-node": "workspace:^", + "@octokit/webhooks": "^10.0.0", + "libsodium-wrappers": "^0.7.11", + "octokit": "^3.0.0", + "octokit-plugin-create-pull-request": "^3.10.0", + "winston": "^3.2.1", + "yaml": "^2.0.0" + }, + "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@types/libsodium-wrappers": "^0.7.10", + "fs-extra": "10.1.0", + "jest-when": "^3.1.0", + "jsonschema": "^1.2.6" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts b/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts similarity index 58% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts rename to plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts index 5ac92128ca..84d8e507b3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts @@ -14,121 +14,10 @@ * limitations under the License. */ -import { Git } from '@backstage/backend-common'; -import { Config } from '@backstage/config'; import { assertError } from '@backstage/errors'; import { Octokit } from 'octokit'; import { Logger } from 'winston'; -export async function initRepoAndPush({ - dir, - remoteUrl, - auth, - logger, - defaultBranch = 'master', - commitMessage = 'Initial commit', - gitAuthorInfo, -}: { - dir: string; - remoteUrl: string; - // For use cases where token has to be used with Basic Auth - // it has to be provided as password together with a username - // which may be a fixed value defined by the provider. - auth: { username: string; password: string } | { token: string }; - logger: Logger; - defaultBranch?: string; - commitMessage?: string; - gitAuthorInfo?: { name?: string; email?: string }; -}): Promise<{ commitHash: string }> { - const git = Git.fromAuth({ - ...auth, - logger, - }); - - await git.init({ - dir, - defaultBranch, - }); - - await git.add({ dir, filepath: '.' }); - - // use provided info if possible, otherwise use fallbacks - const authorInfo = { - name: gitAuthorInfo?.name ?? 'Scaffolder', - email: gitAuthorInfo?.email ?? 'scaffolder@backstage.io', - }; - - const commitHash = await git.commit({ - dir, - message: commitMessage, - author: authorInfo, - committer: authorInfo, - }); - await git.addRemote({ - dir, - url: remoteUrl, - remote: 'origin', - }); - - await git.push({ - dir, - remote: 'origin', - }); - - return { commitHash }; -} - -export async function commitAndPushRepo({ - dir, - auth, - logger, - commitMessage, - gitAuthorInfo, - branch = 'master', - remoteRef, -}: { - dir: string; - // For use cases where token has to be used with Basic Auth - // it has to be provided as password together with a username - // which may be a fixed value defined by the provider. - auth: { username: string; password: string } | { token: string }; - logger: Logger; - commitMessage: string; - gitAuthorInfo?: { name?: string; email?: string }; - branch?: string; - remoteRef?: string; -}): Promise<{ commitHash: string }> { - const git = Git.fromAuth({ - ...auth, - logger, - }); - - await git.fetch({ dir }); - await git.checkout({ dir, ref: branch }); - await git.add({ dir, filepath: '.' }); - - // use provided info if possible, otherwise use fallbacks - const authorInfo = { - name: gitAuthorInfo?.name ?? 'Scaffolder', - email: gitAuthorInfo?.email ?? 'scaffolder@backstage.io', - }; - - const commitHash = await git.commit({ - dir, - message: commitMessage, - author: authorInfo, - committer: authorInfo, - }); - - await git.push({ - dir, - remote: 'origin', - remoteRef: remoteRef ?? `refs/heads/${branch}`, - }); - - return { commitHash }; -} - type BranchProtectionOptions = { client: Octokit; owner: string; @@ -239,15 +128,6 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ } }; -export function getGitCommitMessage( - gitCommitMessage: string | undefined, - config: Config, -): string | undefined { - return gitCommitMessage - ? gitCommitMessage - : config.getOptionalString('scaffolder.defaultCommitMessage'); -} - export function entityRefToName(name: string): string { return name.replace(/^.*[:/]/g, ''); } diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/gitHubEnvironment.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/gitHubEnvironment.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/gitHubEnvironment.examples.ts rename to plugins/scaffolder-backend-module-github/src/actions/gitHubEnvironment.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts similarity index 85% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.examples.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts index 4d87ec97c4..7ea5e025ce 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.examples.test.ts @@ -13,9 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -jest.mock('../helpers'); +jest.mock('./gitHelpers', () => { + return { + ...jest.requireActual('./gitHelpers'), + entityRefToName: jest.fn(), + }; +}); -import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +jest.mock('@backstage/plugin-scaffolder-node', () => { + return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), + initRepoAndPush: jest.fn().mockResolvedValue({ + commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', + }), + commitAndPushRepo: jest.fn().mockResolvedValue({ + commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', + }), + }; +}); + +import { + TemplateAction, + initRepoAndPush, +} from '@backstage/plugin-scaffolder-node'; import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { @@ -24,10 +44,10 @@ import { ScmIntegrations, } from '@backstage/integration'; import { PassThrough } from 'stream'; -import { entityRefToName, initRepoAndPush } from '../helpers'; import { createPublishGithubAction } from './github'; import { examples } from './github.examples'; import yaml from 'yaml'; +import { entityRefToName } from './gitHelpers'; const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; @@ -76,7 +96,7 @@ describe('publish:github', () => { }); const { entityRefToName: realFamiliarizeEntityName } = - jest.requireActual('../helpers'); + jest.requireActual('./helpers'); const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; let action: TemplateAction; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/github.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.examples.ts rename to plugins/scaffolder-backend-module-github/src/actions/github.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/github.test.ts index e15d085cbf..a2a8e11e15 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts @@ -13,7 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -jest.mock('../helpers'); +jest.mock('./gitHelpers', () => { + return { + ...jest.requireActual('./gitHelpers'), + enableBranchProtectionOnDefaultRepoBranch: jest.fn(), + entityRefToName: jest.fn(), + }; +}); + +jest.mock('@backstage/plugin-scaffolder-node', () => { + return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), + initRepoAndPush: jest.fn().mockResolvedValue({ + commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', + }), + commitAndPushRepo: jest.fn().mockResolvedValue({ + commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', + }), + }; +}); import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { getVoidLogger } from '@backstage/backend-common'; @@ -25,12 +43,12 @@ import { } from '@backstage/integration'; import { when } from 'jest-when'; import { PassThrough } from 'stream'; +import { createPublishGithubAction } from './github'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; import { enableBranchProtectionOnDefaultRepoBranch, entityRefToName, - initRepoAndPush, -} from '../helpers'; -import { createPublishGithubAction } from './github'; +} from './gitHelpers'; const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; @@ -79,7 +97,7 @@ describe('publish:github', () => { }); const { entityRefToName: realFamiliarizeEntityName } = - jest.requireActual('../helpers'); + jest.requireActual('./gitHelpers'); const integrations = ScmIntegrations.fromConfig(config); let githubCredentialsProvider: GithubCredentialsProvider; let action: TemplateAction; @@ -546,7 +564,7 @@ describe('publish:github', () => { defaultBranch: 'master', auth: { username: 'x-access-token', password: 'tokenlols' }, logger: mockContext.logger, - commitMessage: 'initial commit', + commitMessage: 'Test commit message', gitAuthorInfo: { email: undefined, name: undefined }, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend-module-github/src/actions/github.ts similarity index 96% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts rename to plugins/scaffolder-backend-module-github/src/actions/github.ts index e5b6071863..91d7a34aea 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.ts @@ -21,15 +21,17 @@ import { ScmIntegrationRegistry, } from '@backstage/integration'; import { Octokit } from 'octokit'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import { createGithubRepoWithCollaboratorsAndTopics, getOctokitOptions, initRepoPushAndProtect, -} from '../github/helpers'; -import * as inputProps from '../github/inputProperties'; -import * as outputProps from '../github/outputProperties'; -import { parseRepoUrl } from './util'; +} from './helpers'; +import * as inputProps from './inputProperties'; +import * as outputProps from './outputProperties'; import { examples } from './github.examples'; /** @@ -185,7 +187,7 @@ export function createPublishGithubAction(options: { protectDefaultBranch = true, protectEnforceAdmins = true, deleteBranchOnMerge = false, - gitCommitMessage = 'initial commit', + gitCommitMessage, gitAuthorName, gitAuthorEmail, allowMergeCommit = true, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.examples.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.examples.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts similarity index 96% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts index 6623ee1f27..0d24b74927 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubActionsDispatch.ts @@ -19,9 +19,11 @@ import { GithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import { Octokit } from 'octokit'; -import { parseRepoUrl } from '../publish/util'; import { getOctokitOptions } from './helpers'; import { examples } from './githubActionsDispatch.examples'; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.test.ts new file mode 100644 index 0000000000..612f68dea2 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.test.ts @@ -0,0 +1,90 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { PassThrough } from 'stream'; +import { createGithubAutolinksAction } from './githubAutolinks'; +import { examples } from './githubAutolinks.examples'; +import yaml from 'yaml'; + +const mockOctokit = { + rest: { + repos: { + createAutolink: jest.fn(), + }, + }, +}; +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + +describe('github:autolinks:create', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'tokenlols' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + let githubCredentialsProvider: GithubCredentialsProvider; + let action: TemplateAction; + + it('should call the githubApis for creating autolink reference', async () => { + const input = yaml.parse(examples[0].example).steps[0].input; + githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubAutolinksAction({ + integrations, + githubCredentialsProvider, + }); + + mockOctokit.rest.repos.createAutolink.mockResolvedValue({ + data: { + id: '1', + }, + }); + await action.handler({ + input, + workspacePath: 'lol', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }); + + expect(mockOctokit.rest.repos.createAutolink).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + key_prefix: 'TICKET-', + url_template: 'https://example.com/TICKET?query=', + is_alphanumeric: false, + }); + }); +}); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.ts new file mode 100644 index 0000000000..5acaeeb6e2 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.examples.ts @@ -0,0 +1,38 @@ +/* + * 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 { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'GitHub alphanumric autolink reference', + example: yaml.stringify({ + steps: [ + { + action: 'github:autolinks:create', + name: 'Create an autolink reference', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + keyPrefix: 'TICKET-', + urlTemplate: 'https://example.com/TICKET?query=', + isAlphanumeric: false, + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.test.ts new file mode 100644 index 0000000000..95c0226104 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { PassThrough } from 'stream'; +import { createGithubAutolinksAction } from './githubAutolinks'; + +const mockOctokit = { + rest: { + repos: { + createAutolink: jest.fn(), + }, + }, +}; +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + +describe('github:autolinks:create', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'tokenlols' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + let githubCredentialsProvider: GithubCredentialsProvider; + let action: TemplateAction; + const mockContext = { + workspacePath: 'lol', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + it('should call the githubApis for creating alphanumeric autolink reference', async () => { + githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubAutolinksAction({ + integrations, + githubCredentialsProvider, + }); + + mockOctokit.rest.repos.createAutolink.mockResolvedValue({ + data: { + id: '1', + }, + }); + await action.handler({ + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + keyPrefix: 'TICKET-', + urlTemplate: 'https://example.com/TICKET?query=', + }, + ...mockContext, + }); + + expect(mockOctokit.rest.repos.createAutolink).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + key_prefix: 'TICKET-', + url_template: 'https://example.com/TICKET?query=', + }); + }); + + it('should call the githubApis for creating numeric autolink reference', async () => { + githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubAutolinksAction({ + integrations, + githubCredentialsProvider, + }); + + mockOctokit.rest.repos.createAutolink.mockResolvedValue({ + data: { + id: '1', + }, + }); + await action.handler({ + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + keyPrefix: 'TICKET-', + urlTemplate: 'https://example.com/TICKET?query=', + isAlphanumeric: false, + }, + ...mockContext, + }); + + expect(mockOctokit.rest.repos.createAutolink).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + key_prefix: 'TICKET-', + url_template: 'https://example.com/TICKET?query=', + is_alphanumeric: false, + }); + }); +}); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts new file mode 100644 index 0000000000..4b0d0e1e1f --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubAutolinks.ts @@ -0,0 +1,118 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/errors'; +import { + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; +import { Octokit } from 'octokit'; +import { examples } from './githubAutolinks.examples'; +import { getOctokitOptions } from './helpers'; + +/** + * Create an autolink reference for a repository + * @public + */ +export function createGithubAutolinksAction(options: { + integrations: ScmIntegrations; + githubCredentialsProvider?: GithubCredentialsProvider; +}) { + const { integrations, githubCredentialsProvider } = options; + + return createTemplateAction<{ + repoUrl: string; + keyPrefix: string; + urlTemplate: string; + isAlphanumeric?: boolean; + token?: string; + }>({ + id: 'github:autolinks:create', + description: 'Create an autolink reference for a repository', + examples, + schema: { + input: { + type: 'object', + required: ['repoUrl', 'keyPrefix', 'urlTemplate'], + properties: { + repoUrl: { + title: 'Repository Location', + description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the new repository name and 'owner' is an organization or username`, + type: 'string', + }, + keyPrefix: { + title: 'Key Prefix', + description: + 'This prefix appended by certain characters will generate a link any time it is found in an issue, pull request, or commit.', + type: 'string', + }, + urlTemplate: { + title: 'URL Template', + description: + 'The URL must contain for the reference number. matches different characters depending on the value of isAlphanumeric.', + type: 'string', + }, + isAlphanumeric: { + title: 'Alphanumeric', + description: + 'Whether this autolink reference matches alphanumeric characters. If true, the parameter of the url_template matches alphanumeric characters A-Z (case insensitive), 0-9, and -. If false, this autolink reference only matches numeric characters. Default: true', + type: 'boolean', + }, + token: { + title: 'Authentication Token', + type: 'string', + description: 'The token to use for authorization to GitHub', + }, + }, + }, + }, + async handler(ctx) { + const { repoUrl, keyPrefix, urlTemplate, isAlphanumeric, token } = + ctx.input; + + ctx.logger.info(`Creating autolink reference for repo ${repoUrl}`); + + const { owner, repo } = parseRepoUrl(repoUrl, integrations); + + if (!owner) { + throw new InputError('Invalid repository owner provided in repoUrl'); + } + + const client = new Octokit( + await getOctokitOptions({ + integrations, + repoUrl, + credentialsProvider: githubCredentialsProvider, + token, + }), + ); + + await client.rest.repos.createAutolink({ + owner, + repo, + key_prefix: keyPrefix, + url_template: urlTemplate, + is_alphanumeric: isAlphanumeric, + }); + + ctx.logger.info(`Autolink reference created successfully`); + }, + }); +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubDeployKey.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.examples.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubDeployKey.examples.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.examples.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubDeployKey.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubDeployKey.examples.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubDeployKey.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubDeployKey.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubDeployKey.ts b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.ts similarity index 97% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubDeployKey.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.ts index 1db9746bb4..ba55b6a1fb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubDeployKey.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubDeployKey.ts @@ -15,9 +15,11 @@ */ import { InputError } from '@backstage/errors'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { parseRepoUrl } from '../publish/util'; import { getOctokitOptions } from './helpers'; import { Octokit } from 'octokit'; import Sodium from 'libsodium-wrappers'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubEnvironment.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubEnvironment.examples.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubEnvironment.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubEnvironment.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubEnvironment.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubEnvironment.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts index 16859d9a26..7f6f9f7fa3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubEnvironment.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts @@ -15,9 +15,11 @@ */ import { InputError } from '@backstage/errors'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { parseRepoUrl } from '../publish/util'; import { getOctokitOptions } from './helpers'; import { Octokit } from 'octokit'; import Sodium from 'libsodium-wrappers'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.examples.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.examples.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.examples.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.examples.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts similarity index 96% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts index 73a65ffb14..90f5a89dc7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesLabel.ts @@ -18,11 +18,13 @@ import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import { assertError, InputError } from '@backstage/errors'; import { Octokit } from 'octokit'; import { getOctokitOptions } from './helpers'; -import { parseRepoUrl } from '../publish/util'; import { examples } from './githubIssuesLabel.examples'; /** diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.examples.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts index 3f13f2ab67..7e5d107026 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.test.ts @@ -23,10 +23,7 @@ import { ScmIntegrations, } from '@backstage/integration'; import { PassThrough } from 'stream'; -import { - OctokitWithPullRequestPluginClient, - createPublishGithubPullRequestAction, -} from './githubPullRequest'; +import { createPublishGithubPullRequestAction } from './githubPullRequest'; import yaml from 'yaml'; import { examples } from './githubPullRequest.examples'; import { createMockDirectory } from '@backstage/backend-test-utils'; @@ -103,9 +100,7 @@ describe('publish:github:pull-request examples', () => { }, }; - const clientFactory = jest.fn( - async () => fakeClient as unknown as OctokitWithPullRequestPluginClient, - ); + const clientFactory = jest.fn(async () => fakeClient as any); mockDir.setContent({ [workspacePath]: { 'file.txt': 'Hello there!' }, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.examples.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index a1f3712a9d..c5b1038a01 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -26,10 +26,7 @@ import { } from '@backstage/plugin-scaffolder-node'; import fs from 'fs-extra'; import { Writable } from 'stream'; -import { - createPublishGithubPullRequestAction, - OctokitWithPullRequestPluginClient, -} from './githubPullRequest'; +import { createPublishGithubPullRequestAction } from './githubPullRequest'; import { createMockDirectory } from '@backstage/backend-test-utils'; // Make sure root logger is initialized ahead of FS mock @@ -78,9 +75,7 @@ describe('createPublishGithubPullRequestAction', () => { }, }, }; - const clientFactory = jest.fn( - async () => fakeClient as unknown as OctokitWithPullRequestPluginClient, - ); + const clientFactory = jest.fn(async () => fakeClient as any); const githubCredentialsProvider: GithubCredentialsProvider = { getCredentials: jest.fn(), }; @@ -382,7 +377,7 @@ describe('createPublishGithubPullRequestAction', () => { branchName: 'new-app', description: 'This PR is really good', reviewers: ['foobar'], - teamReviewers: ['team-foo'], + teamReviewers: ['team-foo', 'team-foo', 'team-bar'], }; mockDir.setContent({ [workspacePath]: {} }); @@ -406,7 +401,7 @@ describe('createPublishGithubPullRequestAction', () => { repo: 'myrepo', pull_number: 123, reviewers: ['foobar'], - team_reviewers: ['team-foo'], + team_reviewers: ['team-foo', 'team-bar'], }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts similarity index 86% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index 3b6e7c3ced..2d8dc7ce8d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -15,21 +15,21 @@ */ import path from 'path'; -import { parseRepoUrl } from './util'; import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + createTemplateAction, + parseRepoUrl, + SerializedFile, + serializeDirectoryContents, +} from '@backstage/plugin-scaffolder-node'; import { Octokit } from 'octokit'; import { InputError, CustomErrorBase } from '@backstage/errors'; import { resolveSafeChildPath } from '@backstage/backend-common'; import { createPullRequest } from 'octokit-plugin-create-pull-request'; -import { getOctokitOptions } from '../github/helpers'; -import { - SerializedFile, - serializeDirectoryContents, -} from '../../../../lib/files'; +import { getOctokitOptions } from './helpers'; import { Logger } from 'winston'; import { examples } from './githubPullRequest.examples'; @@ -37,57 +37,32 @@ export type Encoding = 'utf-8' | 'base64'; class GithubResponseError extends CustomErrorBase {} -/** @public */ -export type OctokitWithPullRequestPluginClient = Octokit & { - createPullRequest(options: createPullRequest.Options): Promise<{ - data: { - html_url: string; - number: number; - base: { - ref: string; - }; - }; - } | null>; -}; - -/** - * The options passed to the client factory function. - * @public - */ -export type CreateGithubPullRequestClientFactoryInput = { - integrations: ScmIntegrationRegistry; - githubCredentialsProvider?: GithubCredentialsProvider; - host: string; - owner: string; - repo: string; - token?: string; -}; - -export const defaultClientFactory = async ({ - integrations, - githubCredentialsProvider, - owner, - repo, - host = 'github.com', - token: providedToken, -}: CreateGithubPullRequestClientFactoryInput): Promise => { - const [encodedHost, encodedOwner, encodedRepo] = [host, owner, repo].map( - encodeURIComponent, - ); - - const octokitOptions = await getOctokitOptions({ +export const defaultClientFactory: CreateGithubPullRequestActionOptions['clientFactory'] = + async ({ integrations, - credentialsProvider: githubCredentialsProvider, - repoUrl: `${encodedHost}?owner=${encodedOwner}&repo=${encodedRepo}`, + githubCredentialsProvider, + owner, + repo, + host = 'github.com', token: providedToken, - }); + }) => { + const [encodedHost, encodedOwner, encodedRepo] = [host, owner, repo].map( + encodeURIComponent, + ); - const OctokitPR = Octokit.plugin(createPullRequest); - return new OctokitPR({ - ...octokitOptions, - ...{ throttle: { enabled: false } }, - }); -}; + const octokitOptions = await getOctokitOptions({ + integrations, + credentialsProvider: githubCredentialsProvider, + repoUrl: `${encodedHost}?owner=${encodedOwner}&repo=${encodedRepo}`, + token: providedToken, + }); + + const OctokitPR = Octokit.plugin(createPullRequest); + return new OctokitPR({ + ...octokitOptions, + ...{ throttle: { enabled: false } }, + }); + }; /** * The options passed to {@link createPublishGithubPullRequestAction} method @@ -105,9 +80,26 @@ export interface CreateGithubPullRequestActionOptions { /** * A method to return the Octokit client with the Pull Request Plugin. */ - clientFactory?: ( - input: CreateGithubPullRequestClientFactoryInput, - ) => Promise; + clientFactory?: (input: { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; + host: string; + owner: string; + repo: string; + token?: string; + }) => Promise< + Octokit & { + createPullRequest(options: createPullRequest.Options): Promise<{ + data: { + html_url: string; + number: number; + base: { + ref: string; + }; + }; + } | null>; + } + >; } type GithubPullRequest = { @@ -380,7 +372,7 @@ export const createPublishGithubPullRequestAction = ( repo: pr.repo, pull_number: pr.number, reviewers, - team_reviewers: teamReviewers, + team_reviewers: teamReviewers ? [...new Set(teamReviewers)] : undefined, }); const addedUsers = result.data.requested_reviewers?.join(', ') ?? ''; const addedTeams = result.data.requested_teams?.join(', ') ?? ''; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts similarity index 96% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts index 71bbc7c1c6..2377e9a7eb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts @@ -16,7 +16,12 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -jest.mock('../helpers'); +jest.mock('./gitHelpers', () => { + return { + ...jest.requireActual('./gitHelpers'), + entityRefToName: jest.fn(), + }; +}); import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; @@ -27,7 +32,7 @@ import { } from '@backstage/integration'; import { PassThrough } from 'stream'; import { createGithubRepoCreateAction } from './githubRepoCreate'; -import { entityRefToName } from '../helpers'; +import { entityRefToName } from './gitHelpers'; import yaml from 'yaml'; import { examples } from './githubRepoCreate.examples'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts similarity index 99% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts index b5b31d0773..4cb007275e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts @@ -16,7 +16,12 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -jest.mock('../helpers'); +jest.mock('./gitHelpers', () => { + return { + ...jest.requireActual('./gitHelpers'), + entityRefToName: jest.fn(), + }; +}); import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; @@ -28,7 +33,7 @@ import { import { when } from 'jest-when'; import { PassThrough } from 'stream'; import { createGithubRepoCreateAction } from './githubRepoCreate'; -import { entityRefToName } from '../helpers'; +import { entityRefToName } from './gitHelpers'; const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts index e0fde3b193..735919794f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts @@ -20,8 +20,10 @@ import { ScmIntegrationRegistry, } from '@backstage/integration'; import { Octokit } from 'octokit'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { parseRepoUrl } from '../publish/util'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import { createGithubRepoWithCollaboratorsAndTopics, getOctokitOptions, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts similarity index 85% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts index 84d489272d..8f8c9bf8af 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.test.ts @@ -13,7 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +jest.mock('@backstage/plugin-scaffolder-node', () => { + return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), + initRepoAndPush: jest.fn().mockResolvedValue({ + commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', + }), + commitAndPushRepo: jest.fn().mockResolvedValue({ + commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', + }), + }; +}); + +import { + TemplateAction, + initRepoAndPush, +} from '@backstage/plugin-scaffolder-node'; import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { @@ -22,7 +37,6 @@ import { ScmIntegrations, } from '@backstage/integration'; import { PassThrough } from 'stream'; -import { initRepoAndPush } from '../helpers'; import { createGithubRepoPushAction } from './githubRepoPush'; import { examples } from './githubRepoPush.examples'; import yaml from 'yaml'; @@ -48,7 +62,12 @@ jest.mock('@backstage/backend-common', () => ({ getVoidLogger: jest.requireActual('@backstage/backend-common').getVoidLogger, })); -jest.mock('../helpers'); +jest.mock('./helpers', () => { + return { + ...jest.requireActual('./helpers'), + entityRefToName: jest.fn(), + }; +}); const initRepoAndPushMocked = initRepoAndPush as jest.Mock< Promise<{ commitHash: string }> @@ -98,6 +117,7 @@ describe('github:repo:push examples', () => { githubCredentialsProvider = DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubRepoPushAction({ integrations, config, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts similarity index 96% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts index 8630dfab56..e16da0981d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts @@ -35,9 +35,30 @@ jest.mock('@backstage/backend-common', () => ({ getVoidLogger: jest.requireActual('@backstage/backend-common').getVoidLogger, })); -jest.mock('../helpers'); +jest.mock('./gitHelpers', () => { + return { + ...jest.requireActual('./gitHelpers'), + entityRefToName: jest.fn(), + enableBranchProtectionOnDefaultRepoBranch: jest.fn(), + }; +}); -import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +jest.mock('@backstage/plugin-scaffolder-node', () => { + return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), + initRepoAndPush: jest.fn().mockResolvedValue({ + commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', + }), + commitAndPushRepo: jest.fn().mockResolvedValue({ + commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', + }), + }; +}); + +import { + TemplateAction, + initRepoAndPush, +} from '@backstage/plugin-scaffolder-node'; import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { @@ -46,10 +67,7 @@ import { ScmIntegrations, } from '@backstage/integration'; import { PassThrough } from 'stream'; -import { - enableBranchProtectionOnDefaultRepoBranch, - initRepoAndPush, -} from '../helpers'; +import { enableBranchProtectionOnDefaultRepoBranch } from './gitHelpers'; import { createGithubRepoPushAction } from './githubRepoPush'; const initRepoAndPushMocked = initRepoAndPush as jest.Mock< diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts index e30eedad02..5b9db78f48 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts @@ -21,8 +21,10 @@ import { ScmIntegrationRegistry, } from '@backstage/integration'; import { Octokit } from 'octokit'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { parseRepoUrl } from '../publish/util'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import { getOctokitOptions, initRepoPushAndProtect } from './helpers'; import * as inputProps from './inputProperties'; import * as outputProps from './outputProperties'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.examples.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.examples.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubWebhook.examples.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.examples.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubWebhook.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubWebhook.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts similarity index 97% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts index 5589c951a5..984029076e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubWebhook.ts @@ -18,12 +18,14 @@ import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import { emitterEventNames } from '@octokit/webhooks'; import { assertError, InputError } from '@backstage/errors'; import { Octokit } from 'octokit'; import { getOctokitOptions } from './helpers'; -import { parseRepoUrl } from '../publish/util'; import { examples } from './githubWebhook.examples'; /** diff --git a/plugins/scaffolder-backend-module-github/src/actions/helper.test.ts b/plugins/scaffolder-backend-module-github/src/actions/helper.test.ts new file mode 100644 index 0000000000..3934e07db7 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/helper.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { getGitCommitMessage } from './helpers'; + +describe('getGitCommitMessage', () => { + it('should return gitCommitMessage when provided', () => { + const mockConfig = new ConfigReader({}); + const gitCommitMessage = 'Custom commit message'; + + const result = getGitCommitMessage(gitCommitMessage, mockConfig); + + expect(result).toEqual('Custom commit message'); + }); + + it('should return default commit message from config when gitCommitMessage is undefined', () => { + const mockConfig = new ConfigReader({ + scaffolder: { + defaultCommitMessage: 'Default commit message', + }, + }); + const result = getGitCommitMessage(undefined, mockConfig); + + expect(result).toEqual('Default commit message'); + }); + + it('should return undefined when both gitCommitMessage and default commit message are undefined', () => { + const mockConfig = new ConfigReader({}); + const result = getGitCommitMessage(undefined, mockConfig); + + expect(result).toBeUndefined(); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts similarity index 97% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts rename to plugins/scaffolder-backend-module-github/src/actions/helpers.ts index d91f3bf3a2..4d95a6494f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts @@ -24,13 +24,18 @@ import { import { OctokitOptions } from '@octokit/core/dist-types/types'; import { Octokit } from 'octokit'; import { Logger } from 'winston'; + +import { + getRepoSourceDirectory, + initRepoAndPush, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; + +import Sodium from 'libsodium-wrappers'; import { enableBranchProtectionOnDefaultRepoBranch, - initRepoAndPush, -} from '../helpers'; -import { getRepoSourceDirectory, parseRepoUrl } from '../publish/util'; -import { entityRefToName } from '../../builtin/helpers'; -import Sodium from 'libsodium-wrappers'; + entityRefToName, +} from './gitHelpers'; const DEFAULT_TIMEOUT_MS = 60_000; @@ -349,9 +354,8 @@ export async function initRepoPushAndProtect( : config.getOptionalString('scaffolder.defaultAuthor.email'), }; - const commitMessage = gitCommitMessage - ? gitCommitMessage - : config.getOptionalString('scaffolder.defaultCommitMessage'); + const commitMessage = + getGitCommitMessage(gitCommitMessage, config) || 'initial commit'; const commitResult = await initRepoAndPush({ dir: getRepoSourceDirectory(workspacePath, sourcePath), @@ -422,3 +426,12 @@ async function validateAccessTeam(client: Octokit, access: string) { } } } + +export function getGitCommitMessage( + gitCommitMessage: string | undefined, + config: Config, +): string | undefined { + return gitCommitMessage + ? gitCommitMessage + : config.getOptionalString('scaffolder.defaultCommitMessage'); +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/index.ts b/plugins/scaffolder-backend-module-github/src/actions/index.ts similarity index 81% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/index.ts rename to plugins/scaffolder-backend-module-github/src/actions/index.ts index ecc8ce59f2..27eaca0e85 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/index.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/index.ts @@ -21,3 +21,10 @@ export { createGithubRepoPushAction } from './githubRepoPush'; export { createGithubWebhookAction } from './githubWebhook'; export { createGithubDeployKeyAction } from './githubDeployKey'; export { createGithubEnvironmentAction } from './githubEnvironment'; + +export { + createPublishGithubPullRequestAction, + type CreateGithubPullRequestActionOptions, +} from './githubPullRequest'; +export { createPublishGithubAction } from './github'; +export { createGithubAutolinksAction } from './githubAutolinks'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/inputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/inputProperties.ts rename to plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/outputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/outputProperties.ts rename to plugins/scaffolder-backend-module-github/src/actions/outputProperties.ts diff --git a/plugins/scaffolder-backend-module-github/src/index.ts b/plugins/scaffolder-backend-module-github/src/index.ts new file mode 100644 index 0000000000..219ba54dbb --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/index.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * A module for the scaffolder backend that lets you interact with github + * + * @packageDocumentation + */ + +export * from './actions'; +export { githubModule as default } from './module'; diff --git a/plugins/scaffolder-backend-module-github/src/module.ts b/plugins/scaffolder-backend-module-github/src/module.ts new file mode 100644 index 0000000000..79c954a6dd --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/module.ts @@ -0,0 +1,97 @@ +/* + * Copyright 2024 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 { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { + createGithubActionsDispatchAction, + createGithubAutolinksAction, + createGithubDeployKeyAction, + createGithubEnvironmentAction, + createGithubIssuesLabelAction, + createGithubRepoCreateAction, + createGithubRepoPushAction, + createGithubWebhookAction, + createPublishGithubAction, + createPublishGithubPullRequestAction, +} from './actions'; +import { + DefaultGithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; + +/** + * @public + * The GitHub Module for the Scaffolder Backend + */ +export const githubModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'github', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + config: coreServices.rootConfig, + }, + async init({ scaffolder, config }) { + const integrations = ScmIntegrations.fromConfig(config); + const githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + + scaffolder.addActions( + createGithubActionsDispatchAction({ + integrations, + githubCredentialsProvider, + }), + createGithubAutolinksAction({ + integrations, + githubCredentialsProvider, + }), + createGithubDeployKeyAction({ + integrations, + }), + createGithubEnvironmentAction({ + integrations, + }), + createGithubIssuesLabelAction({ + integrations, + githubCredentialsProvider, + }), + createGithubRepoCreateAction({ + integrations, + githubCredentialsProvider, + }), + createGithubRepoPushAction({ integrations, config }), + createGithubWebhookAction({ + integrations, + githubCredentialsProvider, + }), + createPublishGithubAction({ + integrations, + config, + githubCredentialsProvider, + }), + createPublishGithubPullRequestAction({ + integrations, + githubCredentialsProvider, + }), + ); + }, + }); + }, +}); diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index 7b9fd3b55d..fe7abefeda 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,106 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.2.13-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.2.12 + +### Patch Changes + +- 604c9dd: Add Scaffolder custom action that creates GitLab issues called `gitlab:issues:create` +- 7c522c5: Add `gitlab:repo:push` scaffolder action to push files to arbitrary branch without creating a Merge Request +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + +## 0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## 0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## 0.2.11 + +### Patch Changes + +- 219d7f0: Extract some more actions to this library +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.2.11-next.3 + +### Patch Changes + +- 219d7f0: Extract some more actions to this library +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + +## 0.2.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + ## 0.2.11-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/README.md b/plugins/scaffolder-backend-module-gitlab/README.md index 340b57bbf8..c946c8b476 100644 --- a/plugins/scaffolder-backend-module-gitlab/README.md +++ b/plugins/scaffolder-backend-module-gitlab/README.md @@ -24,6 +24,7 @@ import { createGitlabProjectDeployTokenAction, createGitlabProjectVariableAction, createGitlabGroupEnsureExistsAction, + createGitlabIssueAction, } from '@backstage/plugin-scaffolder-backend-module-gitlab'; // Create BuiltIn Actions @@ -41,6 +42,7 @@ const actions = [ createGitlabProjectDeployTokenAction({ integrations: integrations }), createGitlabProjectVariableAction({ integrations: integrations }), createGitlabGroupEnsureExistsAction({ integrations: integrations }), + createGitlabIssueAction({ integrations: integrations }), ]; // Create Scaffolder Router @@ -160,8 +162,26 @@ spec: repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} catalogInfoPath: '/catalog-info.yaml' + - id: gitlabIssue + name: Issues + action: gitlab:issues:create + input: + repoUrl: ${{ parameters.repoUrl }} + token: ${{ secrets.USER_OAUTH_TOKEN }} + projectId: 1111111 + title: Test Issue + assignees: + - 2222222 + description: This is the description of the issue + confidential: true + createdAt: 2022-09-27 18:00:00.000 + dueDate: 2024-09-28 12:00:00.000 + epicId: 3333333 + labels: phase1:label1,phase2:label2 output: links: - title: Repository url: ${{ steps['publish'].output.remoteUrl }} + - title: Link to new issue + url: ${{ steps['gitlabIssue'].output.issueUrl }} ``` diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index eccada5d51..94aabe9b72 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -3,6 +3,8 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -21,6 +23,34 @@ export const createGitlabGroupEnsureExistsAction: (options: { } >; +// @public +export const createGitlabIssueAction: (options: { + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + title: string; + repoUrl: string; + projectId: number; + token?: string | undefined; + assignees?: number[] | undefined; + confidential?: boolean | undefined; + description?: string | undefined; + createdAt?: string | undefined; + dueDate?: string | undefined; + discussionToResolve?: string | undefined; + epicId?: number | undefined; + labels?: string | undefined; + issueType?: IssueType | undefined; + mergeRequestToResolveDiscussionsOf?: number | undefined; + milestoneId?: number | undefined; + weight?: number | undefined; + }, + { + issueUrl: string; + issueId: number; + } +>; + // @public export const createGitlabProjectAccessTokenAction: (options: { integrations: ScmIntegrationRegistry; @@ -74,4 +104,105 @@ export const createGitlabProjectVariableAction: (options: { }, JsonObject >; + +// @public +export const createGitlabRepoPushAction: (options: { + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + repoUrl: string; + branchName: string; + commitMessage: string; + sourcePath?: string | undefined; + targetPath?: string | undefined; + token?: string | undefined; + commitAction?: 'update' | 'delete' | 'create' | undefined; + }, + JsonObject +>; + +// @public +export function createPublishGitlabAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction< + { + repoUrl: string; + defaultBranch?: string | undefined; + repoVisibility?: 'internal' | 'private' | 'public' | undefined; + sourcePath?: string | undefined; + token?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + setUserAsOwner?: boolean | undefined; + topics?: string[] | undefined; + settings?: + | { + path?: string | undefined; + auto_devops_enabled?: boolean | undefined; + ci_config_path?: string | undefined; + description?: string | undefined; + topics?: string[] | undefined; + visibility?: 'internal' | 'private' | 'public' | undefined; + } + | undefined; + branches?: + | { + name: string; + protect?: boolean | undefined; + create?: boolean | undefined; + ref?: string | undefined; + }[] + | undefined; + projectVariables?: + | { + key: string; + value: string; + description?: string | undefined; + variable_type?: string | undefined; + protected?: boolean | undefined; + masked?: boolean | undefined; + raw?: boolean | undefined; + environment_scope?: string | undefined; + }[] + | undefined; + }, + JsonObject +>; + +// @public +export const createPublishGitlabMergeRequestAction: (options: { + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + repoUrl: string; + title: string; + description: string; + branchName: string; + targetBranchName?: string | undefined; + sourcePath?: string | undefined; + targetPath?: string | undefined; + token?: string | undefined; + commitAction?: 'update' | 'delete' | 'create' | undefined; + projectid?: string | undefined; + removeSourceBranch?: boolean | undefined; + assignee?: string | undefined; + }, + JsonObject +>; + +// @public +const gitlabModule: () => BackendFeature; +export default gitlabModule; + +// @public +export enum IssueType { + // (undocumented) + INCIDENT = 'incident', + // (undocumented) + ISSUE = 'issue', + // (undocumented) + TEST = 'test_case', +} ``` diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index eccb663b54..bf709c6b7f 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,13 +1,11 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.2.11-next.0", + "version": "0.2.13-next.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" + "access": "public" }, "backstage": { "role": "backend-plugin-module" @@ -21,6 +19,17 @@ "keywords": [ "backstage" ], + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", @@ -31,18 +40,23 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { + "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", + "@gitbeaker/core": "^35.8.0", "@gitbeaker/node": "^35.8.0", + "@gitbeaker/rest": "^39.25.0", "yaml": "^2.0.0", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "devDependencies": { - "@backstage/backend-common": "workspace:^", + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@backstage/core-app-api": "workspace:^" + "@backstage/core-app-api": "workspace:^", + "jest-date-mock": "^1.0.8" }, "files": [ "dist" diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.examples.ts new file mode 100644 index 0000000000..760caf0f9f --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.examples.ts @@ -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. + */ +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; +import { commonGitlabConfigExample } from '../commonGitlabConfig'; + +export const examples: TemplateExample[] = [ + { + description: 'Create a GitLab issue with minimal options', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Test Issue', + description: 'This is the description of the issue', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue with assignees and date options', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Test Issue', + assignees: [18], + description: 'This is the description of the issue', + createdAt: '2022-09-27 18:00:00.000', + dueDate: '2022-09-28 12:00:00.000', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab Issue with several options', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Test Issue', + assignees: [18, 15], + description: 'This is the description of the issue', + confidential: false, + createdAt: '2022-09-27 18:00:00.000', + dueDate: '2022-09-28 12:00:00.000', + discussionToResolve: 1, + epicId: 1, + labels: 'phase1:label1,phase2:label2', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts new file mode 100644 index 0000000000..fe9e556656 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.test.ts @@ -0,0 +1,217 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PassThrough } from 'stream'; +import { getVoidLogger } from '@backstage/backend-common'; +import { createGitlabIssueAction, IssueType } from './createGitlabIssueAction'; +import { ConfigReader } from '@backstage/core-app-api'; +import { ScmIntegrations } from '@backstage/integration'; +import { advanceTo, clear } from 'jest-date-mock'; + +const mockGitlabClient = { + Issues: { + create: jest.fn(), + }, +}; +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:issues:create', () => { + beforeEach(() => { + jest.clearAllMocks(); + advanceTo(new Date(1988, 5, 3, 12, 0, 0)); // Set the desired date and time + }); + + afterEach(() => { + clear(); // Reset the date mock after each test + }); + + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = createGitlabIssueAction({ integrations }); + + it('should return a Gitlab issue when called with minimal input params', async () => { + const mockContext = { + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 123, + 'Computer banks to rule the world', + { + issueType: undefined, + description: '', + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: undefined, + weight: undefined, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it('should return a Gitlab issue when called with oAuth Token', async () => { + const mockContext = { + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 123, + 'Computer banks to rule the world', + { + issueType: undefined, + description: '', + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: undefined, + weight: undefined, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it('should return a Gitlab issue when called with several input params', async () => { + const mockContext = { + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueType: IssueType.INCIDENT, + title: 'Computer banks to rule the world', + description: + 'this issue should kickstart research on instruments to sight the stars', + dueDate: '1990-08-20T23:59:59Z', + token: 'myAwesomeToken', + assignees: [3, 14, 15], + labels: 'operation:mindcrime', + }, + workspacePath: 'seen2much', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 123, + 'Computer banks to rule the world', + { + issueType: 'incident', + description: + 'this issue should kickstart research on instruments to sight the stars', + assigneeIds: [3, 14, 15], + confidential: false, + epicId: undefined, + labels: 'operation:mindcrime', + createdAt: new Date().toISOString(), + dueDate: '1990-08-20T23:59:59.000Z', + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: undefined, + weight: undefined, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.ts new file mode 100644 index 0000000000..bc9c796253 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabIssueAction.ts @@ -0,0 +1,209 @@ +/* + * 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 { InputError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import commonGitlabConfig from '../commonGitlabConfig'; +import { examples } from './createGitlabIssueAction.examples'; +import { z } from 'zod'; +import { checkEpicScope, convertDate, getClient, parseRepoUrl } from '../util'; +import { Gitlab, CreateIssueOptions, IssueSchema } from '@gitbeaker/rest'; + +/** + * Gitlab issue types + * + * @public + */ +export enum IssueType { + ISSUE = 'issue', + INCIDENT = 'incident', + TEST = 'test_case', +} + +const issueInputProperties = z.object({ + projectId: z.number().describe('Project Id'), + title: z.string({ description: 'Title of the issue' }), + assignees: z + .array(z.number(), { + description: 'IDs of the users to assign the issue to.', + }) + .optional(), + confidential: z.boolean({ description: 'Issue Confidentiality' }).optional(), + description: z.string().describe('Issue description').max(1048576).optional(), + createdAt: z + .string() + .describe('Creation date/time') + .regex( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, + 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', + ) + .optional(), + dueDate: z + .string() + .describe('Due date/time') + .regex( + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, + 'Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ', + ) + .optional(), + discussionToResolve: z + .string({ + description: + 'Id of a discussion to resolve. Use in combination with "merge_request_to_resolve_discussions_of"', + }) + .optional(), + epicId: z + .number({ description: 'Id of the linked Epic' }) + .min(0, 'Valid values should be equal or greater than zero') + .optional(), + labels: z.string({ description: 'Labels to apply' }).optional(), + issueType: z + .nativeEnum(IssueType, { + description: 'Type of the issue', + }) + .optional(), + mergeRequestToResolveDiscussionsOf: z + .number({ + description: 'IID of a merge request in which to resolve all issues', + }) + .optional(), + milestoneId: z + .number({ description: 'Global ID of a milestone to assign the issue' }) + .optional(), + weight: z + .number({ description: 'The issue weight' }) + .min(0) + .refine(value => { + const isValid = value >= 0; + if (!isValid) { + return { + message: 'Valid values should be equal or greater than zero', + }; + } + return isValid; + }) + .optional(), +}); + +const issueOutputProperties = z.object({ + issueUrl: z.string({ description: 'Issue Url' }), + issueId: z.number({ description: 'Issue Id' }), +}); + +/** + * Creates a `gitlab:issues:create` Scaffolder action. + * + * @param options - Templating configuration. + * @public + */ +export const createGitlabIssueAction = (options: { + integrations: ScmIntegrationRegistry; +}) => { + const { integrations } = options; + return createTemplateAction({ + id: 'gitlab:issues:create', + description: 'Creates a Gitlab issue.', + examples, + schema: { + input: commonGitlabConfig.merge(issueInputProperties), + output: issueOutputProperties, + }, + async handler(ctx) { + try { + const { + repoUrl, + projectId, + title, + description = '', + confidential = false, + assignees = [], + createdAt = '', + dueDate, + discussionToResolve = '', + epicId, + labels = '', + issueType, + mergeRequestToResolveDiscussionsOf, + milestoneId, + weight, + token, + } = commonGitlabConfig.merge(issueInputProperties).parse(ctx.input); + + const { host } = parseRepoUrl(repoUrl, integrations); + const api = getClient({ host, integrations, token }); + + let isEpicScoped = false; + + if (epicId) { + isEpicScoped = await checkEpicScope( + api as any as InstanceType, + projectId, + epicId, + ); + + if (isEpicScoped) { + ctx.logger.info('Epic is within Project Scope'); + } else { + ctx.logger.warn( + 'Chosen epic is not within the Project Scope. The issue will be created without an associated epic.', + ); + } + } + const mappedCreatedAt = convertDate( + String(createdAt), + new Date().toISOString(), + ); + const mappedDueDate = dueDate + ? convertDate(String(dueDate), new Date().toISOString()) + : undefined; + + const issueOptions: CreateIssueOptions = { + description, + assigneeIds: assignees, + confidential, + epicId: isEpicScoped ? epicId : undefined, + labels, + createdAt: mappedCreatedAt, + dueDate: mappedDueDate, + discussionToResolve, + issueType, + mergeRequestToResolveDiscussionsOf, + milestoneId, + weight, + }; + + const response = (await api.Issues.create( + projectId, + title, + issueOptions, + )) as IssueSchema; + + ctx.output('issueId', response.id); + ctx.output('issueUrl', response.web_url); + } catch (error: any) { + if (error instanceof z.ZodError) { + // Handling Zod validation errors + throw new InputError(`Validation error: ${error.message}`, { + validationErrors: error.errors, + }); + } + // Handling other errors + throw new InputError(`Failed to create GitLab issue: ${error.message}`); + } + }, + }); +}; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.test.ts similarity index 94% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.examples.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.test.ts index 5d58431c61..e5aa610ebc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.test.ts @@ -15,8 +15,9 @@ */ import yaml from 'yaml'; -jest.mock('../helpers', () => { +jest.mock('@backstage/plugin-scaffolder-node', () => { return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), initRepoAndPush: jest.fn().mockResolvedValue({ commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', }), @@ -27,11 +28,11 @@ jest.mock('../helpers', () => { }); import { createPublishGitlabAction } from './gitlab'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { initRepoAndPush } from '../helpers'; import { examples } from './gitlab.examples'; const mockGitlabClient = { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.examples.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts similarity index 98% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts index b2f3dc6480..5aca0a73e5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -jest.mock('../helpers', () => { +jest.mock('@backstage/plugin-scaffolder-node', () => { return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), initRepoAndPush: jest.fn().mockResolvedValue({ commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6', }), @@ -30,7 +31,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { initRepoAndPush } from '../helpers'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; const mockGitlabClient = { Namespaces: { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts similarity index 99% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts index 6d86841a20..22f875ca8d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts @@ -18,8 +18,11 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { Gitlab } from '@gitbeaker/node'; -import { initRepoAndPush } from '../helpers'; -import { getRepoSourceDirectory, parseRepoUrl } from './util'; +import { + initRepoAndPush, + getRepoSourceDirectory, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; import { Config } from '@backstage/config'; import { examples } from './gitlab.examples'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.test.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts similarity index 89% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts rename to plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index 8abb53288c..86f69ee357 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -14,15 +14,17 @@ * limitations under the License. */ -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { Gitlab } from '@gitbeaker/node'; +import { + createTemplateAction, + parseRepoUrl, + serializeDirectoryContents, +} from '@backstage/plugin-scaffolder-node'; import { Types } from '@gitbeaker/core'; import path from 'path'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { InputError } from '@backstage/errors'; -import { parseRepoUrl } from './util'; import { resolveSafeChildPath } from '@backstage/backend-common'; -import { serializeDirectoryContents } from '../../../../lib/files'; +import { createGitlabApi } from './helpers'; /** * Create a new action that creates a gitlab merge request. @@ -156,33 +158,16 @@ export const createPublishGitlabMergeRequestAction = (options: { targetPath, sourcePath, title, - token: providedToken, + token, } = ctx.input; - const { host, owner, repo, project } = parseRepoUrl( - repoUrl, - integrations, - ); + const { owner, repo, project } = parseRepoUrl(repoUrl, integrations); const repoID = project ? project : `${owner}/${repo}`; - const integrationConfig = integrations.gitlab.byHost(host); - - if (!integrationConfig) { - throw new InputError( - `No matching integration configuration for host ${host}, please check your integrations config`, - ); - } - - if (!integrationConfig.config.token && !providedToken) { - throw new InputError(`No token available for host ${host}`); - } - - const token = providedToken ?? integrationConfig.config.token!; - const tokenType = providedToken ? 'oauthToken' : 'token'; - - const api = new Gitlab({ - host: integrationConfig.config.baseUrl, - [tokenType]: token, + const api = createGitlabApi({ + integrations, + token, + repoUrl, }); let assigneeId = undefined; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts new file mode 100644 index 0000000000..840f506540 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts @@ -0,0 +1,436 @@ +/* + * 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 { createRootLogger, getRootLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { Writable } from 'stream'; +import { createMockDirectory } from '@backstage/backend-test-utils'; +import { createGitlabRepoPushAction } from './gitlabRepoPush'; + +// Make sure root logger is initialized ahead of FS mock +createRootLogger(); + +const mockGitlabClient = { + Projects: { + create: jest.fn(), + show: jest.fn(async (_: any) => { + return { + default_branch: 'main', + }; + }), + }, + Branches: { + create: jest.fn(), + show: jest.fn(), + }, + Commits: { + create: jest.fn(async (_: any) => ({ + id: 'bb6bce457ed069a38ef8d16ef38602972c7735c5', + })), + }, +}; + +jest.mock('@gitbeaker/node', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('createGitLabCommit', () => { + let instance: TemplateAction; + + const mockDir = createMockDirectory(); + const workspacePath = mockDir.resolve('workspace'); + + beforeEach(() => { + mockDir.clear(); + + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'token', + apiBaseUrl: 'https://api.gitlab.com', + }, + { + host: 'hosted.gitlab.com', + apiBaseUrl: 'https://api.hosted.gitlab.com', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + instance = createGitlabRepoPushAction({ integrations }); + }); + + describe('createGitLabCommitWithoutCommitAction', () => { + it('default commitAction is create', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + commitMessage: 'Create my new commit', + branchName: 'some-branch', + }; + mockDir.setContent({ + [workspacePath]: { + 'foo.txt': 'Hello there!', + }, + }); + const ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + await instance.handler(ctx); + + expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(0); + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'some-branch', + 'Create my new commit', + [ + { + action: 'create', + filePath: 'foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + expect(ctx.output).toHaveBeenCalledWith( + 'commitHash', + 'bb6bce457ed069a38ef8d16ef38602972c7735c5', + ); + }); + }); + + describe('createGitLabCommitWithCommitAction', () => { + it('commitAction is create when create is passed in options', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + commitMessage: 'Create my new commit', + branchName: 'some-branch', + commitAction: 'create', + }; + mockDir.setContent({ + [workspacePath]: { + 'foo.txt': 'Hello there!', + }, + }); + + const ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + await instance.handler(ctx); + + expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(0); + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'some-branch', + 'Create my new commit', + [ + { + action: 'create', + filePath: 'foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + expect(ctx.output).toHaveBeenCalledWith( + 'commitHash', + 'bb6bce457ed069a38ef8d16ef38602972c7735c5', + ); + }); + + it('commitAction is update when update is passed in options', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + commitMessage: 'Create my new commit', + branchName: 'some-branch', + commitAction: 'update', + }; + mockDir.setContent({ + [workspacePath]: { + 'foo.txt': 'Hello there!', + }, + }); + + const ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + await instance.handler(ctx); + + expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(0); + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'some-branch', + 'Create my new commit', + [ + { + action: 'update', + filePath: 'foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + expect(ctx.output).toHaveBeenCalledWith( + 'commitHash', + 'bb6bce457ed069a38ef8d16ef38602972c7735c5', + ); + }); + + it('commitAction is delete when delete is passed in options', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + commitMessage: 'Create my new commit', + branchName: 'some-branch', + commitAction: 'delete', + }; + mockDir.setContent({ + [workspacePath]: { + 'foo.txt': 'Hello there!', + }, + }); + + const ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + await instance.handler(ctx); + + expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(0); + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'some-branch', + 'Create my new commit', + [ + { + action: 'delete', + filePath: 'foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + expect(ctx.output).toHaveBeenCalledWith( + 'commitHash', + 'bb6bce457ed069a38ef8d16ef38602972c7735c5', + ); + }); + }); + + describe('with sourcePath', () => { + it('creates a commit with only relevant files', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + commitMessage: 'Create my new commit', + branchName: 'some-branch', + sourcePath: 'source', + commitAction: 'create', + }; + + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + + const ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + + await instance.handler(ctx); + + expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(0); + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'some-branch', + 'Create my new commit', + [ + { + action: 'create', + filePath: 'foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + expect(ctx.output).toHaveBeenCalledWith( + 'commitHash', + 'bb6bce457ed069a38ef8d16ef38602972c7735c5', + ); + }); + + it('creates a commit with only relevant files placed under different targetPath', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + commitMessage: 'Create my new commit', + branchName: 'some-branch', + sourcePath: 'source', + targetPath: 'target', + commitAction: 'create', + }; + + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + + const ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + + await instance.handler(ctx); + + expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(0); + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'some-branch', + 'Create my new commit', + [ + { + action: 'create', + filePath: 'target/foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + expect(ctx.output).toHaveBeenCalledWith( + 'commitHash', + 'bb6bce457ed069a38ef8d16ef38602972c7735c5', + ); + }); + + it('should not allow to use files outside of the workspace', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + commitMessage: 'Create my new commit', + branchName: 'some-branch', + sourcePath: '../../test', + commitAction: 'create', + }; + + const ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + + await expect(instance.handler(ctx)).rejects.toThrow( + 'Relative path is not allowed to refer to a directory outside its parent', + ); + }); + }); + + describe('createCommitToBranchThatDoesNotExist', () => { + it('should create a new branch', async () => { + mockGitlabClient.Branches.show.mockRejectedValue({ + response: { + statusCode: 404, + }, + }); + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + commitMessage: 'Create my new commit', + branchName: 'some-branch', + }; + mockDir.setContent({ + [workspacePath]: { + 'foo.txt': 'Hello there!', + }, + }); + const ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + await instance.handler(ctx); + + expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith( + 'owner/repo', + 'some-branch', + 'main', + ); + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'some-branch', + 'Create my new commit', + [ + { + action: 'create', + filePath: 'foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + expect(ctx.output).toHaveBeenCalledWith( + 'commitHash', + 'bb6bce457ed069a38ef8d16ef38602972c7735c5', + ); + }); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts new file mode 100644 index 0000000000..330d0f6f08 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -0,0 +1,194 @@ +/* + * 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 { + createTemplateAction, + parseRepoUrl, + serializeDirectoryContents, +} from '@backstage/plugin-scaffolder-node'; +import { Types } from '@gitbeaker/core'; +import path from 'path'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { InputError } from '@backstage/errors'; +import { resolveSafeChildPath } from '@backstage/backend-common'; +import { createGitlabApi } from './helpers'; + +/** + * Create a new action that commits into a gitlab repository. + * + * @public + */ +export const createGitlabRepoPushAction = (options: { + integrations: ScmIntegrationRegistry; +}) => { + const { integrations } = options; + + return createTemplateAction<{ + repoUrl: string; + branchName: string; + commitMessage: string; + sourcePath?: string; + targetPath?: string; + token?: string; + commitAction?: 'create' | 'delete' | 'update'; + }>({ + id: 'gitlab:repo:push', + schema: { + input: { + required: ['repoUrl', 'branchName', 'commitMessage'], + type: 'object', + properties: { + repoUrl: { + type: 'string', + title: 'Repository Location', + description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`, + }, + branchName: { + type: 'string', + title: 'Source Branch Name', + description: 'The branch name for the commit', + }, + commitMessage: { + type: 'string', + title: 'Commit Message', + description: `The commit message`, + }, + sourcePath: { + type: 'string', + title: 'Working Subdirectory', + description: + 'Subdirectory of working directory to copy changes from', + }, + targetPath: { + type: 'string', + title: 'Repository Subdirectory', + description: 'Subdirectory of repository to apply changes to', + }, + token: { + title: 'Authentication Token', + type: 'string', + description: 'The token to use for authorization to GitLab', + }, + commitAction: { + title: 'Commit action', + type: 'string', + enum: ['create', 'update', 'delete'], + description: + 'The action to be used for git commit. Defaults to create.', + }, + }, + }, + output: { + type: 'object', + properties: { + projectid: { + title: 'Gitlab Project id/Name(slug)', + type: 'string', + }, + projectPath: { + title: 'Gitlab Project path', + type: 'string', + }, + commitHash: { + title: 'The git commit hash of the commit', + type: 'string', + }, + }, + }, + }, + async handler(ctx) { + const { + branchName, + repoUrl, + targetPath, + sourcePath, + token, + commitAction, + } = ctx.input; + + const { owner, repo, project } = parseRepoUrl(repoUrl, integrations); + const repoID = project ? project : `${owner}/${repo}`; + + const api = createGitlabApi({ + integrations, + token, + repoUrl, + }); + + let fileRoot: string; + if (sourcePath) { + fileRoot = resolveSafeChildPath(ctx.workspacePath, sourcePath); + } else { + fileRoot = ctx.workspacePath; + } + + const fileContents = await serializeDirectoryContents(fileRoot, { + gitignore: true, + }); + + const actions: Types.CommitAction[] = fileContents.map(file => ({ + action: commitAction ?? 'create', + filePath: targetPath + ? path.posix.join(targetPath, file.path) + : file.path, + encoding: 'base64', + content: file.content.toString('base64'), + execute_filemode: file.executable, + })); + + let branchExists = false; + try { + await api.Branches.show(repoID, branchName); + branchExists = true; + } catch (e: any) { + if (e.response?.statusCode !== 404) { + throw new InputError( + `Failed to check status of branch '${branchName}'. Please make sure that branch already exists or Backstage has permissions to create one. ${e}`, + ); + } + } + + if (!branchExists) { + // create a branch using the default branch as ref + try { + const projects = await api.Projects.show(repoID); + const { default_branch: defaultBranch } = projects; + await api.Branches.create(repoID, branchName, String(defaultBranch)); + } catch (e) { + throw new InputError( + `The branch '${branchName}' was not found and creation failed with error. Please make sure that branch already exists or Backstage has permissions to create one. ${e}`, + ); + } + } + + try { + const commit = await api.Commits.create( + repoID, + branchName, + ctx.input.commitMessage, + actions, + ); + ctx.output('projectid', repoID); + ctx.output('projectPath', repoID); + ctx.output('commitHash', commit.id); + } catch (e) { + throw new InputError( + `Committing the changes to ${branchName} failed. Please check that none of the files created by the template already exists. ${e}`, + ); + } + }, + }); +}; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/helpers.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/helpers.ts new file mode 100644 index 0000000000..1d12a6d394 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/helpers.ts @@ -0,0 +1,50 @@ +/* + * 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 { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; +import { InputError } from '@backstage/errors'; +import { Gitlab } from '@gitbeaker/node'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { Resources } from '@gitbeaker/core'; + +export function createGitlabApi(options: { + integrations: ScmIntegrationRegistry; + token?: string; + repoUrl: string; +}): Resources.Gitlab { + const { integrations, token: providedToken, repoUrl } = options; + + const { host } = parseRepoUrl(repoUrl, integrations); + + const integrationConfig = integrations.gitlab.byHost(host); + + if (!integrationConfig) { + throw new InputError( + `No matching integration configuration for host ${host}, please check your integrations config`, + ); + } + + if (!integrationConfig.config.token && !providedToken) { + throw new InputError(`No token available for host ${host}`); + } + + const token = providedToken ?? integrationConfig.config.token!; + const tokenType = providedToken ? 'oauthToken' : 'token'; + + return new Gitlab({ + host: integrationConfig.config.baseUrl, + [tokenType]: token, + }); +} diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/index.ts new file mode 100644 index 0000000000..4a5ca06c98 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/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. + */ +export * from './createGitlabGroupEnsureExistsAction'; +export * from './createGitlabProjectDeployTokenAction'; +export * from './createGitlabProjectAccessTokenAction'; +export * from './createGitlabProjectVariableAction'; +export * from './createGitlabIssueAction'; +export * from './gitlab'; +export * from './gitlabMergeRequest'; +export * from './gitlabRepoPush'; diff --git a/plugins/scaffolder-backend-module-gitlab/src/commonGitlabConfig.ts b/plugins/scaffolder-backend-module-gitlab/src/commonGitlabConfig.ts index 02724a3674..3c83eacf19 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/commonGitlabConfig.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/commonGitlabConfig.ts @@ -24,3 +24,8 @@ const commonGitlabConfig = z.object({ }); export default commonGitlabConfig; + +export const commonGitlabConfigExample = { + repoUrl: 'gitlab.com?owner=namespace-or-owner&repo=project-name', + token: '${{ secrets.USER_OAUTH_TOKEN }}', +}; diff --git a/plugins/scaffolder-backend-module-gitlab/src/index.ts b/plugins/scaffolder-backend-module-gitlab/src/index.ts index 4fd4dee35b..628b892296 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/index.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Backstage Authors + * 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. @@ -19,7 +19,6 @@ * * @packageDocumentation */ -export * from './actions/createGitlabGroupEnsureExistsAction'; -export * from './actions/createGitlabProjectDeployTokenAction'; -export * from './actions/createGitlabProjectAccessTokenAction'; -export * from './actions/createGitlabProjectVariableAction'; + +export * from './actions'; +export { gitlabModule as default } from './module'; diff --git a/plugins/scaffolder-backend-module-gitlab/src/module.ts b/plugins/scaffolder-backend-module-gitlab/src/module.ts new file mode 100644 index 0000000000..3571575827 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/module.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2024 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 { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { + createGitlabGroupEnsureExistsAction, + createGitlabIssueAction, + createGitlabProjectAccessTokenAction, + createGitlabProjectDeployTokenAction, + createGitlabProjectVariableAction, + createGitlabRepoPushAction, + createPublishGitlabAction, + createPublishGitlabMergeRequestAction, +} from './actions'; +import { ScmIntegrations } from '@backstage/integration'; + +/** + * @public + * The GitLab Module for the Scaffolder Backend + */ +export const gitlabModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'gitlab', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + config: coreServices.rootConfig, + }, + async init({ scaffolder, config }) { + const integrations = ScmIntegrations.fromConfig(config); + + scaffolder.addActions( + createGitlabGroupEnsureExistsAction({ integrations }), + createGitlabIssueAction({ integrations }), + createGitlabProjectAccessTokenAction({ integrations }), + createGitlabProjectDeployTokenAction({ integrations }), + createGitlabProjectVariableAction({ integrations }), + createGitlabRepoPushAction({ integrations }), + createPublishGitlabAction({ config, integrations }), + createPublishGitlabMergeRequestAction({ integrations }), + ); + }, + }); + }, +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/util.test.ts b/plugins/scaffolder-backend-module-gitlab/src/util.test.ts new file mode 100644 index 0000000000..61c61e87c3 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/util.test.ts @@ -0,0 +1,388 @@ +/* + * 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 * as util from './util'; +import { Gitlab, GroupSchema } from '@gitbeaker/rest'; +import { InputError } from '@backstage/errors'; + +// Mock the Gitlab client and its methods +const mockGitlabClient = { + Groups: { + show: jest.fn(), + }, + Projects: { + show: jest.fn(), + }, + Epics: { + all: jest.fn(), + }, +}; + +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +const mockConfig = { + gitlab: [ + { + host: 'gitlab.com', + token: 'withToken', + apiBaseUrl: 'gitlab.com/api/v4', + }, + { + host: 'gitlab.com', + apiBaseUrl: 'gitlab.com/api/v4', + }, + ], +}; +describe('getTopLevelParentGroup', () => { + afterEach(() => jest.resetAllMocks()); + + // Mocked nested groups + const mockGroups: GroupSchema[] = [ + { + id: 789, + parent_id: 0, + path: '', + description: '', + visibility: '', + share_with_group_lock: false, + require_two_factor_authentication: false, + two_factor_grace_period: 0, + project_creation_level: '', + subgroup_creation_level: '', + lfs_enabled: false, + default_branch_protection: 0, + request_access_enabled: false, + created_at: '', + avatar_url: '', + full_name: '', + full_path: '', + web_url: '', + name: '', + }, + { + id: 456, + parent_id: 789, + path: '', + description: '', + visibility: '', + share_with_group_lock: false, + require_two_factor_authentication: false, + two_factor_grace_period: 0, + project_creation_level: '', + subgroup_creation_level: '', + lfs_enabled: false, + default_branch_protection: 0, + request_access_enabled: false, + created_at: '', + avatar_url: '', + full_name: '', + full_path: '', + web_url: '', + name: '', + }, + { + id: 123, + parent_id: 456, + path: '', + description: '', + visibility: '', + share_with_group_lock: false, + require_two_factor_authentication: false, + two_factor_grace_period: 0, + project_creation_level: '', + subgroup_creation_level: '', + lfs_enabled: false, + default_branch_protection: 0, + request_access_enabled: false, + created_at: '', + avatar_url: '', + full_name: '', + full_path: '', + web_url: '', + name: '', + }, + ]; + + // Top level group + const mockTopParentGroup: GroupSchema = { + id: 789, + parent_id: 0, + path: '', + description: '', + visibility: '', + share_with_group_lock: false, + require_two_factor_authentication: false, + two_factor_grace_period: 0, + project_creation_level: '', + subgroup_creation_level: '', + lfs_enabled: false, + default_branch_protection: 0, + request_access_enabled: false, + created_at: '', + avatar_url: '', + full_name: '', + full_path: '', + web_url: '', + name: '', + }; + + it('should return the top-level parent group if the input group has a parent in the hierarchy', async () => { + // Instance with token + const apiClient = new Gitlab({ + host: mockConfig.gitlab[0].host, + token: mockConfig.gitlab[0].token!, + }); + + const showSpy = jest.spyOn(mockGitlabClient.Groups, 'show'); + + // Mock implementation of Groups.show + showSpy.mockImplementation( + async (groupId: string | number): Promise => { + const id = + typeof groupId === 'number' ? groupId : parseInt(groupId, 10); + const mockGroup = mockGroups.find(group => group.id === id) || null; + return mockGroup as GroupSchema; + }, + ); + + const action = util.getTopLevelParentGroup(apiClient, 123); + + const result = await action; + expect(result).toEqual(mockTopParentGroup); + }); + + it('should return the input group if it has no parents in the hierarchy', async () => { + // Instance with token + const apiClient = new Gitlab({ + host: mockConfig.gitlab[0].host, + token: mockConfig.gitlab[0].token!, + }); + + const showSpy = jest.spyOn(mockGitlabClient.Groups, 'show'); + + // Mock implementation of Groups.show + showSpy.mockImplementation( + async (groupId: string | number): Promise => { + const id = + typeof groupId === 'number' ? groupId : parseInt(groupId, 10); + const mockGroup = mockGroups.find(group => group.id === id) || null; + return mockGroup as GroupSchema; + }, + ); + + const action = util.getTopLevelParentGroup(apiClient, 789); + + const result = await action; + expect(result).toEqual(mockTopParentGroup); + }); +}); + +describe('checkEpicScope', () => { + afterEach(() => jest.resetAllMocks()); + + it('should return true if the project is inside the epic scope', async () => { + const apiClient = new Gitlab({ + host: mockConfig.gitlab[0].host, + token: mockConfig.gitlab[0].token!, + }); + + const projectId = 123; + const epicId = 456; + + // Mock project, top-level parent group, and epic + const mockProject = { + id: 123, + name: 'You learn', + namespace: { id: 789 }, + path_with_namespace: 'at-once/you-learn', + }; + const mockTopParentGroup = { + id: 789, + name: 'LivingTwice', + full_path: 'at-once/you-learn', + }; + const mockEpic = { id: epicId, group_id: 789 }; + + mockGitlabClient.Projects.show.mockResolvedValue(mockProject); + mockGitlabClient.Groups.show.mockResolvedValue(mockTopParentGroup); + mockGitlabClient.Epics.all.mockResolvedValue([mockEpic]); + + const result = await util.checkEpicScope(apiClient, projectId, epicId); + + expect(result).toBe(true); + expect(mockGitlabClient.Projects.show).toHaveBeenCalledWith(projectId); + expect(mockGitlabClient.Groups.show).toHaveBeenCalledWith( + mockProject.namespace.id, + ); + expect(mockGitlabClient.Epics.all).toHaveBeenCalledWith( + mockTopParentGroup.id, + ); + }); + + it('should return false if the project is not inside the epic scope', async () => { + const apiClient = new Gitlab({ + host: mockConfig.gitlab[0].host, + token: mockConfig.gitlab[0].token!, + }); + + const projectId = 123; + const epicId = 45; + + // Mock project, top-level parent group, and epic + const mockProject = { + id: 123, + name: 'You learn', + namespace: { id: 32 }, + path_with_namespace: 'at-once/you-learn', + }; + const mockTopParentGroup = { + id: 32, + name: 'TheWalls', + full_path: 'you-built/within', + }; + + const mockEpic = { id: epicId, group_id: 32 }; + + mockGitlabClient.Projects.show.mockResolvedValue(mockProject); + mockGitlabClient.Groups.show.mockResolvedValue(mockTopParentGroup); + mockGitlabClient.Epics.all.mockResolvedValue([mockEpic]); + + const result = await util.checkEpicScope(apiClient, projectId, epicId); + + expect(result).toBe(false); + expect(mockGitlabClient.Projects.show).toHaveBeenCalledWith(projectId); + expect(mockGitlabClient.Groups.show).toHaveBeenCalledWith( + mockProject.namespace.id, + ); + expect(mockGitlabClient.Epics.all).toHaveBeenCalledWith( + mockTopParentGroup.id, + ); + }); + + it('should throw an InputError if the project is not found', async () => { + const apiClient = new Gitlab({ + host: mockConfig.gitlab[0].host, + token: mockConfig.gitlab[0].token!, + }); + + const projectId = 123; + const epicId = 456; + + mockGitlabClient.Projects.show.mockResolvedValue(null); + + await expect( + util.checkEpicScope(apiClient, projectId, epicId), + ).rejects.toThrow(InputError); + expect(mockGitlabClient.Projects.show).toHaveBeenCalledWith(projectId); + }); + + it('should throw an InputError if the top-level parent group is not found', async () => { + const apiClient = new Gitlab({ + host: mockConfig.gitlab[0].host, + token: mockConfig.gitlab[0].token!, + }); + + const projectId = 123; + const epicId = 456; + + mockGitlabClient.Projects.show.mockResolvedValue({ + id: 123, + name: 'You learn', + namespace: { id: 789 }, + path_with_namespace: 'at-once/you-learn', + }); + mockGitlabClient.Groups.show.mockResolvedValue(null); + + await expect( + util.checkEpicScope(apiClient, projectId, epicId), + ).rejects.toThrow(InputError); + expect(mockGitlabClient.Projects.show).toHaveBeenCalledWith(projectId); + expect(mockGitlabClient.Groups.show).toHaveBeenCalledWith(789); + }); + + it('should throw an InputError if the epic is not found', async () => { + const apiClient = new Gitlab({ + host: mockConfig.gitlab[0].host, + token: mockConfig.gitlab[0].token!, + }); + + const projectId = 123; + const epicId = 456; + + mockGitlabClient.Projects.show.mockResolvedValue({ + id: 123, + name: 'You learn', + namespace: { id: 789 }, + path_with_namespace: 'at-once/you-learn', + }); + mockGitlabClient.Groups.show.mockResolvedValue({ + id: 789, + name: 'LivingTwice', + full_path: 'at-once/you-learn', + }); + mockGitlabClient.Epics.all.mockResolvedValue([]); + + await expect( + util.checkEpicScope(apiClient, projectId, epicId), + ).rejects.toThrow(InputError); + expect(mockGitlabClient.Projects.show).toHaveBeenCalledWith(projectId); + expect(mockGitlabClient.Groups.show).toHaveBeenCalledWith(789); + expect(mockGitlabClient.Epics.all).toHaveBeenCalledWith(789); + }); +}); + +describe('convertDate', () => { + it('should convert a valid input date with miliseconds to an ISO string', () => { + const inputDate = '1970-01-01T12:00:00.000Z'; + const defaultDate = '1978-10-09T12:00:00Z'; + + const result = util.convertDate(inputDate, defaultDate); + + expect(result).toEqual('1970-01-01T12:00:00.000Z'); + }); + + it('should convert a valid input date to an ISO string', () => { + const inputDate = '1970-01-01T12:00:00Z'; + const defaultDate = '1978-10-09T12:00:00Z'; + + const result = util.convertDate(inputDate, defaultDate); + + expect(result).toEqual('1970-01-01T12:00:00.000Z'); + }); + + it('should use default date if input date is undefined', () => { + const inputDate = undefined; + const defaultDate = '1970-01-01T12:00:00Z'; + + const result = util.convertDate(inputDate, defaultDate); + + expect(result).toEqual('1970-01-01T12:00:00.000Z'); + }); + + it('should throw an InputError if input date is invalid', () => { + const inputDate = 'invalidDate'; + const defaultDate = '2023-02-01T12:00:00Z'; + + // Expecting an InputError to be thrown + expect(() => util.convertDate(inputDate, defaultDate)).toThrow(InputError); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/util.ts b/plugins/scaffolder-backend-module-gitlab/src/util.ts index 13c95e48e8..a4d819d490 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/util.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/util.ts @@ -21,6 +21,8 @@ import { } from '@backstage/integration'; import { z } from 'zod'; import commonGitlabConfig from './commonGitlabConfig'; +import { Gitlab, GroupSchema } from '@gitbeaker/rest'; +import * as util from './util'; export const parseRepoHost = (repoUrl: string): string => { let parsed; @@ -56,3 +58,138 @@ export const getToken = ( return { token: token, integrationConfig: integrationConfig }; }; + +export type RepoSpec = { + repo: string; + host: string; + owner?: string; +}; + +export const parseRepoUrl = ( + repoUrl: string, + integrations: ScmIntegrationRegistry, +): RepoSpec => { + let parsed; + try { + parsed = new URL(`https://${repoUrl}`); + } catch (error) { + throw new InputError( + `Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`, + ); + } + const host = parsed.host; + const owner = parsed.searchParams.get('owner') ?? undefined; + const repo: string = parsed.searchParams.get('repo')!; + + const type = integrations.byHost(host)?.type; + + if (!type) { + throw new InputError( + `No matching integration configuration for host ${host}, please check your integrations config`, + ); + } + + return { host, owner, repo }; +}; + +export function getClient(props: { + host: string; + token?: string; + integrations: ScmIntegrationRegistry; +}): InstanceType { + const { host, token, integrations } = props; + const integrationConfig = integrations.gitlab.byHost(host); + + if (!integrationConfig) { + throw new InputError( + `No matching integration configuration for host ${host}, please check your integrations config`, + ); + } + + const { config } = integrationConfig; + + if (!config.token && !token) { + throw new InputError(`No token available for host ${host}`); + } + + const requestToken = token || config.token!; + const tokenType = token ? 'oauthToken' : 'token'; + + const gitlabOptions: any = { + host: config.baseUrl, + }; + + gitlabOptions[tokenType] = requestToken; + return new Gitlab(gitlabOptions); +} + +export function convertDate( + inputDate: string | undefined, + defaultDate: string, +) { + try { + return inputDate + ? new Date(inputDate).toISOString() + : new Date(defaultDate).toISOString(); + } catch (error) { + throw new InputError(`Error converting input date - ${error}`); + } +} + +export async function getTopLevelParentGroup( + client: InstanceType, + groupId: number, +): Promise { + try { + const topParentGroup = await client.Groups.show(groupId); + if (topParentGroup.parent_id) { + return util.getTopLevelParentGroup( + client, + topParentGroup.parent_id as number, + ); + } + return topParentGroup as GroupSchema; + } catch (error: any) { + throw new InputError( + `Error finding top-level parent group ID: ${error.message}`, + ); + } +} + +export async function checkEpicScope( + client: InstanceType, + projectId: number, + epicId: number, +) { + try { + // If project exists, get the top level group id + const project = await client.Projects.show(projectId); + if (!project) { + throw new InputError( + `Project with id ${projectId} not found. Check your GitLab instance.`, + ); + } + const topParentGroup = await getTopLevelParentGroup( + client, + project.namespace.id, + ); + if (!topParentGroup) { + throw new InputError(`Couldn't find a suitable top-level parent group.`); + } + // Get the epic + const epic = (await client.Epics.all(topParentGroup.id)).find( + (x: any) => x.id === epicId, + ); + if (!epic) { + throw new InputError( + `Epic with id ${epicId} not found in the top-level parent group ${topParentGroup.name}.`, + ); + } + + const epicGroup = await client.Groups.show(epic.group_id as number); + const projectNamespace: string = project.path_with_namespace as string; + return projectNamespace.startsWith(epicGroup.full_path as string); + } catch (error: any) { + throw new InputError(`Could not find epic scope: ${error.message}`); + } +} diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index ebf034b594..e3aa1f0fe9 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,112 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.27-next.0 + +### Patch Changes + +- e9a5228: Make `containerRunner` argument optional +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## 0.4.26 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## 0.4.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## 0.4.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## 0.4.25 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/integration@1.8.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + +## 0.4.25-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + +## 0.4.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## 0.4.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + ## 0.4.25-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/Rails.dockerfile b/plugins/scaffolder-backend-module-rails/Rails.dockerfile index 921faa601d..8ea3fc370a 100644 --- a/plugins/scaffolder-backend-module-rails/Rails.dockerfile +++ b/plugins/scaffolder-backend-module-rails/Rails.dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.2 +FROM ruby:3.3 RUN apt-get update -qq && \ apt-get install -y nodejs postgresql-client git && \ diff --git a/plugins/scaffolder-backend-module-rails/api-report.md b/plugins/scaffolder-backend-module-rails/api-report.md index 06b7dc457f..33ad052b14 100644 --- a/plugins/scaffolder-backend-module-rails/api-report.md +++ b/plugins/scaffolder-backend-module-rails/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { ContainerRunner } from '@backstage/backend-common'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; @@ -13,7 +14,7 @@ import { UrlReader } from '@backstage/backend-common'; export function createFetchRailsAction(options: { reader: UrlReader; integrations: ScmIntegrations; - containerRunner: ContainerRunner; + containerRunner?: ContainerRunner; allowedImageNames?: string[]; }): TemplateAction< { @@ -24,4 +25,8 @@ export function createFetchRailsAction(options: { }, JsonObject >; + +// @public +const railsModule: () => BackendFeature; +export default railsModule; ``` diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index abd4742845..6c52665976 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,14 +1,12 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", "description": "A module for the scaffolder backend that lets you template projects using Rails", - "version": "0.4.25-next.0", + "version": "0.4.27-next.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" + "access": "public" }, "backstage": { "role": "backend-plugin-module" @@ -19,6 +17,17 @@ "url": "https://github.com/backstage/backstage", "directory": "plugins/scaffolder-backend-module-rails" }, + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, "scripts": { "build": "backstage-cli package build", "start": "backstage-cli package start", @@ -30,6 +39,7 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts index 2b34f978e3..dfc7bea418 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts @@ -40,7 +40,7 @@ import { RailsNewRunner } from './railsNewRunner'; export function createFetchRailsAction(options: { reader: UrlReader; integrations: ScmIntegrations; - containerRunner: ContainerRunner; + containerRunner?: ContainerRunner; /** A list of image names that are allowed to be passed as imageName input */ allowedImageNames?: string[]; }) { diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts index 7e78c25d62..31cfea4a76 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts @@ -27,9 +27,9 @@ import { JsonObject } from '@backstage/types'; import { Writable } from 'stream'; export class RailsNewRunner { - private readonly containerRunner: ContainerRunner; + private readonly containerRunner?: ContainerRunner; - constructor({ containerRunner }: { containerRunner: ContainerRunner }) { + constructor({ containerRunner }: { containerRunner?: ContainerRunner }) { this.containerRunner = containerRunner; } @@ -77,6 +77,11 @@ export class RailsNewRunner { if (!imageName) { throw new Error('No imageName provided'); } + if (!this.containerRunner) { + throw new Error( + 'Command is not available and no container runner provided', + ); + } const arrayExtraArguments = railsArgumentResolver( '/input', railsArguments as RailsRunOptions, diff --git a/plugins/scaffolder-backend-module-rails/src/index.ts b/plugins/scaffolder-backend-module-rails/src/index.ts index 773e1feabd..ef89cc68d2 100644 --- a/plugins/scaffolder-backend-module-rails/src/index.ts +++ b/plugins/scaffolder-backend-module-rails/src/index.ts @@ -21,3 +21,4 @@ */ export * from './actions'; +export { railsModule as default } from './module'; diff --git a/plugins/scaffolder-backend-module-rails/src/module.ts b/plugins/scaffolder-backend-module-rails/src/module.ts new file mode 100644 index 0000000000..ec70610cd3 --- /dev/null +++ b/plugins/scaffolder-backend-module-rails/src/module.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2024 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 { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { createFetchRailsAction } from './actions'; +import { ScmIntegrations } from '@backstage/integration'; + +/** + * @public + * The Rails Module for the Scaffolder Backend + */ +export const railsModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'rails', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + config: coreServices.rootConfig, + reader: coreServices.urlReader, + }, + async init({ scaffolder, config, reader }) { + const integrations = ScmIntegrations.fromConfig(config); + + scaffolder.addActions(createFetchRailsAction({ integrations, reader })); + }, + }); + }, +}); diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index 2ba0251f7e..a6a79787c2 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,88 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.1.18-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/errors@1.2.3 + +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## 0.1.16 + +### Patch Changes + +- 7f8a801: Added examples for `sentry:project:create` scaffolder action and unit tests. +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.16-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + +## 0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## 0.1.16-next.1 + +### Patch Changes + +- 7f8a801e6d: Added examples for `sentry:project:create` scaffolder action and unit tests. +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + ## 0.1.16-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/api-report.md b/plugins/scaffolder-backend-module-sentry/api-report.md index 746cce1348..960fe4ba24 100644 --- a/plugins/scaffolder-backend-module-sentry/api-report.md +++ b/plugins/scaffolder-backend-module-sentry/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -21,5 +22,9 @@ export function createSentryCreateProjectAction(options: { JsonObject >; +// @public +const sentryModule: () => BackendFeature; +export default sentryModule; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 58b39a6b5c..0039ad35a6 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,17 +1,26 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.1.16-next.0", + "version": "0.1.18-next.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" + "access": "public" }, "backstage": { "role": "backend-plugin-module" }, + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -28,12 +37,17 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", - "@backstage/plugin-scaffolder-node": "workspace:^" + "@backstage/plugin-scaffolder-node": "workspace:^", + "yaml": "^2.3.3" }, "devDependencies": { - "@backstage/cli": "workspace:^" + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@backstage/types": "workspace:^", + "msw": "^2.0.0" }, "files": [ "dist" diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts new file mode 100644 index 0000000000..9d4551c1b5 --- /dev/null +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Creates a Sentry project with the specified parameters.', + example: yaml.stringify({ + steps: [ + { + id: 'create-sentry-project', + action: 'sentry:project:create', + name: 'Create a Sentry project with provided project slug.', + input: { + organizationSlug: 'my-org', + teamSlug: 'team-a', + name: 'Scaffolded project A', + slug: 'scaff-proj-a', + authToken: + 'a14711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d5df96', + }, + }, + { + id: 'create-sentry-project', + action: 'sentry:project:create', + name: 'Create a Sentry project without providing a project slug.', + input: { + organizationSlug: 'my-org', + teamSlug: 'team-b', + name: 'Scaffolded project B', + authToken: + 'b15711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d4gf93', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts new file mode 100644 index 0000000000..972b0f1e8e --- /dev/null +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts @@ -0,0 +1,217 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import { ActionContext } from '@backstage/plugin-scaffolder-node'; +import { JsonObject } from '@backstage/types'; +import { randomBytes } from 'crypto'; +import { setupServer } from 'msw/node'; +import { HttpResponse, http } from 'msw'; +import { createSentryCreateProjectAction } from './createProject'; + +describe('sentry:project:create action', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + const createScaffolderConfig = (configData: JsonObject = {}) => ({ + config: new ConfigReader({ + scaffolder: { + ...configData, + }, + }), + }); + + const getActionContext = (): ActionContext<{ + organizationSlug: string; + teamSlug: string; + name: string; + slug?: string; + authToken?: string; + }> => ({ + workspacePath: './dev/proj', + createTemporaryDirectory: jest.fn(), + logger: jest.createMockFromModule('winston'), + logStream: jest.createMockFromModule('stream'), + input: { + organizationSlug: 'org', + teamSlug: 'team', + name: 'test project', + authToken: randomBytes(5).toString('hex'), + }, + output: jest.fn(), + }); + + it('should request sentry project create with specified parameters.', async () => { + expect.assertions(3); + + const action = createSentryCreateProjectAction(createScaffolderConfig()); + const actionContext = getActionContext(); + + worker.use( + http.post( + `https://sentry.io/api/0/teams/${actionContext.input.organizationSlug}/${actionContext.input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer ${actionContext.input.authToken}`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: actionContext.input.name, + }); + return HttpResponse.json( + { detail: 'project creation mocked result' }, + { status: 201 }, + ); + }, + ), + ); + + await action.handler(actionContext); + }); + + it('should request sentry project create with added optional specified project slug', async () => { + expect.assertions(3); + + const action = createSentryCreateProjectAction(createScaffolderConfig()); + const actionContext = getActionContext(); + actionContext.input = { ...actionContext.input, slug: 'project-slug' }; + + worker.use( + http.post( + `https://sentry.io/api/0/teams/${actionContext.input.organizationSlug}/${actionContext.input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer ${actionContext.input.authToken}`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: actionContext.input.name, + slug: actionContext.input.slug, + }); + return HttpResponse.json( + { detail: 'project creation mocked result' }, + { status: 201 }, + ); + }, + ), + ); + + await action.handler(actionContext); + }); + + it('should take Sentry auth token from scaffolder config when input authToken is missing.', async () => { + expect.assertions(3); + + const sentryScaffolderConfigToken = randomBytes(5).toString('hex'); + const action = createSentryCreateProjectAction( + createScaffolderConfig({ + sentry: { + token: sentryScaffolderConfigToken, + }, + }), + ); + const actionContext = getActionContext(); + actionContext.input.authToken = undefined; + + worker.use( + http.post( + `https://sentry.io/api/0/teams/${actionContext.input.organizationSlug}/${actionContext.input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer ${sentryScaffolderConfigToken}`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: actionContext.input.name, + }); + return HttpResponse.json( + { detail: 'project creation mocked result' }, + { status: 201 }, + ); + }, + ), + ); + + await action.handler(actionContext); + }); + + it('should throw InputError when auth token is missing from input parameters and scaffolder config.', async () => { + const action = createSentryCreateProjectAction(createScaffolderConfig()); + const actionContext = getActionContext(); + actionContext.input.authToken = undefined; + + worker.use( + http.post( + `https://sentry.io/api/0/teams/${actionContext.input.organizationSlug}/${actionContext.input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer ${actionContext.input.authToken}`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: actionContext.input.name, + }); + return HttpResponse.json( + { detail: 'project creation mocked result' }, + { status: 201 }, + ); + }, + ), + ); + + await expect(() => action.handler(actionContext)).rejects.toThrow( + new InputError('No valid sentry token given'), + ); + }); + + it('should throw InputError when sentry API returns unexpected content-type.', async () => { + const action = createSentryCreateProjectAction(createScaffolderConfig()); + const actionContext = getActionContext(); + + worker.use( + http.post( + `https://sentry.io/api/0/teams/${actionContext.input.organizationSlug}/${actionContext.input.teamSlug}/projects/`, + async () => { + return HttpResponse.text('Bad response', { status: 201 }); + }, + ), + ); + + await expect(() => action.handler(actionContext)).rejects.toThrow( + new InputError(`Unexpected Sentry Response Type: Bad response`), + ); + }); + + it('should throw InputError when sentry API returns unexpected status code.', async () => { + const action = createSentryCreateProjectAction(createScaffolderConfig()); + const actionContext = getActionContext(); + + worker.use( + http.post( + `https://sentry.io/api/0/teams/${actionContext.input.organizationSlug}/${actionContext.input.teamSlug}/projects/`, + async () => { + return HttpResponse.json({ detail: 'OUCH' }, { status: 400 }); + }, + ), + ); + + await expect(() => action.handler(actionContext)).rejects.toThrow( + new InputError(`Sentry Response was: OUCH`), + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts index 85a74eda07..259b670f9e 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.ts @@ -19,7 +19,7 @@ import { InputError } from '@backstage/errors'; import { Config } from '@backstage/config'; /** - * Creates the `sentry:craete-project` Scaffolder action. + * Creates the `sentry:project:create` Scaffolder action. * * @remarks * diff --git a/plugins/scaffolder/src/components/types.ts b/plugins/scaffolder-backend-module-sentry/src/alpha.ts similarity index 93% rename from plugins/scaffolder/src/components/types.ts rename to plugins/scaffolder-backend-module-sentry/src/alpha.ts index b1e99a84e6..94adcafa53 100644 --- a/plugins/scaffolder/src/components/types.ts +++ b/plugins/scaffolder-backend-module-sentry/src/alpha.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2024 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. diff --git a/plugins/scaffolder-backend-module-sentry/src/index.ts b/plugins/scaffolder-backend-module-sentry/src/index.ts index f6c0820874..dc1a68675b 100644 --- a/plugins/scaffolder-backend-module-sentry/src/index.ts +++ b/plugins/scaffolder-backend-module-sentry/src/index.ts @@ -15,3 +15,4 @@ */ export { createSentryCreateProjectAction } from './actions/createProject'; +export { sentryModule as default } from './module'; diff --git a/plugins/scaffolder-backend-module-sentry/src/module.ts b/plugins/scaffolder-backend-module-sentry/src/module.ts new file mode 100644 index 0000000000..dcde344262 --- /dev/null +++ b/plugins/scaffolder-backend-module-sentry/src/module.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2024 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 { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { createSentryCreateProjectAction } from './actions/createProject'; + +/** + * @public + * The Sentry Module for the Scaffolder Backend + */ +export const sentryModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'sentry', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + config: coreServices.rootConfig, + reade: coreServices.urlReader, + }, + async init({ scaffolder, config }) { + scaffolder.addActions(createSentryCreateProjectAction({ config })); + }, + }); + }, +}); diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index 0e2c6901c0..b57b5a4c6e 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,84 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.2.31-next.0 + +### Patch Changes + +- e9a5228: Exporting a default module for the new Backend System +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/types@1.1.1 + +## 0.2.30 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/types@1.1.1 + +## 0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + +## 0.2.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/types@1.1.1 + +## 0.2.30-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## 0.2.29 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 0.2.29-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + +## 0.2.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## 0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.1.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + ## 0.2.29-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/api-report.md b/plugins/scaffolder-backend-module-yeoman/api-report.md index d8bdcb7b3b..b466ee152b 100644 --- a/plugins/scaffolder-backend-module-yeoman/api-report.md +++ b/plugins/scaffolder-backend-module-yeoman/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -15,4 +16,8 @@ export function createRunYeomanAction(): TemplateAction< }, JsonObject >; + +// @public +const yeomanModule: () => BackendFeature; +export default yeomanModule; ``` diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 400e2e616b..68739803df 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,17 +1,26 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.2.29-next.0", + "version": "0.2.31-next.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" + "access": "public" }, "backstage": { "role": "backend-plugin-module" }, + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "package.json": [ + "package.json" + ] + } + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -28,7 +37,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", "winston": "^3.2.1", diff --git a/plugins/scaffolder-backend-module-yeoman/src/index.ts b/plugins/scaffolder-backend-module-yeoman/src/index.ts index 1aac8e12f9..97966d6c08 100644 --- a/plugins/scaffolder-backend-module-yeoman/src/index.ts +++ b/plugins/scaffolder-backend-module-yeoman/src/index.ts @@ -21,3 +21,4 @@ * @packageDocumentation */ export * from './actions'; +export { yeomanModule as default } from './module'; diff --git a/plugins/scaffolder-backend-module-yeoman/src/module.ts b/plugins/scaffolder-backend-module-yeoman/src/module.ts new file mode 100644 index 0000000000..1fdbda28a3 --- /dev/null +++ b/plugins/scaffolder-backend-module-yeoman/src/module.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2024 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 { createBackendModule } from '@backstage/backend-plugin-api'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; +import { createRunYeomanAction } from './actions'; + +/** + * @public + * The Yeoman Module for the Scaffolder Backend + */ +export const yeomanModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'yeoman', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + }, + async init({ scaffolder }) { + scaffolder.addActions(createRunYeomanAction()); + }, + }); + }, +}); diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 531f437ca3..5e8c96ed91 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,263 @@ # @backstage/plugin-scaffolder-backend +## 1.21.0-next.0 + +### Minor Changes + +- 11b9a08: Introduced the first version of recoverable tasks. +- e9a5228: The built-in module list has been trimmed down when using the new Backend System. Provider specific modules should now be installed with `backend.add` to provide additional actions to the scaffolder. These modules are as follows: + + - `@backstage/plugin-scaffolder-backend-module-github` + - `@backstage/plugin-scaffolder-backend-module-gitlab` + - `@backstage/plugin-scaffolder-backend-module-bitbucket` + - `@backstage/plugin-scaffolder-backend-module-gitea` + - `@backstage/plugin-scaffolder-backend-module-gerrit` + - `@backstage/plugin-scaffolder-backend-module-confluence-to-markdown` + - `@backstage/plugin-scaffolder-backend-module-cookiecutter` + - `@backstage/plugin-scaffolder-backend-module-rails` + - `@backstage/plugin-scaffolder-backend-module-sentry` + - `@backstage/plugin-scaffolder-backend-module-yeoman` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.2-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.2-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.1.2-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.13-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.2-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-scaffolder-node@0.3.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/backend-tasks@0.5.15-next.0 + - @backstage/plugin-auth-node@0.4.4-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.7-next.0 + - @backstage/plugin-catalog-node@1.6.2-next.0 + - @backstage/plugin-permission-node@0.7.21-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + +## 1.20.0 + +### Minor Changes + +- a694f71: The Scaffolder builtin actions now contains an action for running pipelines from Bitbucket Cloud Rest API +- 7c522c5: Add `gitlab:repo:push` scaffolder action to push files to arbitrary branch without creating a Merge Request + +### Patch Changes + +- e9ab1c4: Fixed an issue where not passing a `value` to any of the action's permission conditions caused an error. +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-backend-module-github@0.1.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.12 + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.1 + - @backstage/catalog-client@1.5.2 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6 + - @backstage/plugin-catalog-node@1.6.1 + - @backstage/plugin-permission-common@0.7.12 + - @backstage/plugin-permission-node@0.7.20 + - @backstage/backend-tasks@0.5.14 + - @backstage/plugin-auth-node@0.4.3 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.1 + - @backstage/plugin-scaffolder-node@0.2.10 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## 1.19.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + - @backstage/plugin-auth-node@0.4.3-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.2 + - @backstage/plugin-catalog-node@1.6.1-next.2 + - @backstage/plugin-permission-node@0.7.20-next.2 + - @backstage/plugin-scaffolder-node@0.2.10-next.2 + - @backstage/backend-tasks@0.5.14-next.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.1-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.1-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.1-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.1.1-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.12-next.2 + +## 1.19.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.12-next.1 + - @backstage/config@1.1.1 + - @backstage/backend-tasks@0.5.14-next.1 + - @backstage/plugin-auth-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.7.20-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.1-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.1-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.1-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.1.1-next.1 + - @backstage/plugin-scaffolder-node@0.2.10-next.1 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.1 + - @backstage/plugin-catalog-node@1.6.1-next.1 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.19.3-next.0 + +### Patch Changes + +- e9ab1c4: Fixed an issue where not passing a `value` to any of the action's permission conditions caused an error. +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.1.1-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.1-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.6-next.0 + - @backstage/plugin-catalog-node@1.6.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/backend-tasks@0.5.14-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.3-next.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.20-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.1-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.1-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.12-next.0 + - @backstage/plugin-scaffolder-common@1.4.4 + - @backstage/plugin-scaffolder-node@0.2.10-next.0 + +## 1.19.2 + +### Patch Changes + +- 219d7f0: Refactor some methods to `-node` instead and use the new external modules +- aff34fc: Fix issue with Circular JSON dependencies in templating +- 48667b4: Fix creating env secret in github:environment:create action +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- 28949ea: Add a new action for creating github-autolink references for a repository: `github:autolinks:create` +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/plugin-scaffolder-backend-module-github@0.1.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.11 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.0 + - @backstage/plugin-catalog-node@1.6.0 + - @backstage/catalog-client@1.5.0 + - @backstage/plugin-scaffolder-node@0.2.9 + - @backstage/backend-tasks@0.5.13 + - @backstage/integration@1.8.0 + - @backstage/plugin-auth-node@0.4.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.0 + - @backstage/plugin-permission-common@0.7.11 + - @backstage/plugin-permission-node@0.7.19 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.19.2-next.3 + +### Patch Changes + +- 219d7f0: Refactor some methods to `-node` instead and use the new external modules +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.2.11-next.3 + - @backstage/plugin-scaffolder-node@0.2.9-next.3 + - @backstage/backend-common@0.20.0-next.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.1.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.0-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.1.0-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.0-next.0 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/backend-tasks@0.5.13-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.3 + - @backstage/plugin-catalog-node@1.6.0-next.3 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.3 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.19.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.6.0-next.2 + - @backstage/plugin-catalog-backend@1.16.0-next.2 + - @backstage/backend-common@0.20.0-next.2 + - @backstage/plugin-auth-node@0.4.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/backend-tasks@0.5.13-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.2 + - @backstage/plugin-scaffolder-common@1.4.3 + - @backstage/plugin-scaffolder-node@0.2.9-next.2 + +## 1.19.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.15.1-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/backend-tasks@0.5.13-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.2-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.5-next.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-node@1.5.1-next.1 + - @backstage/plugin-permission-common@0.7.10 + - @backstage/plugin-permission-node@0.7.19-next.1 + - @backstage/plugin-scaffolder-common@1.4.3 + - @backstage/plugin-scaffolder-node@0.2.9-next.1 + ## 1.19.2-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend/alpha-api-report.md b/plugins/scaffolder-backend/api-report-alpha.md similarity index 100% rename from plugins/scaffolder-backend/alpha-api-report.md rename to plugins/scaffolder-backend/api-report-alpha.md diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index b92b883767..cc16791390 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -4,21 +4,24 @@ ```ts import { ActionContext as ActionContext_2 } from '@backstage/plugin-scaffolder-node'; +import * as azure from '@backstage/plugin-scaffolder-backend-module-azure'; +import * as bitbucket from '@backstage/plugin-scaffolder-backend-module-bitbucket'; import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; -import { createPullRequest } from 'octokit-plugin-create-pull-request'; import { Duration } from 'luxon'; import { executeShellCommand as executeShellCommand_2 } from '@backstage/plugin-scaffolder-node'; import { ExecuteShellCommandOptions } from '@backstage/plugin-scaffolder-node'; import express from 'express'; import { fetchContents as fetchContents_2 } from '@backstage/plugin-scaffolder-node'; -import { GithubCredentialsProvider } from '@backstage/integration'; +import * as gerrit from '@backstage/plugin-scaffolder-backend-module-gerrit'; +import * as github from '@backstage/plugin-scaffolder-backend-module-github'; +import * as gitlab from '@backstage/plugin-scaffolder-backend-module-gitlab'; import { HumanDuration } from '@backstage/types'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { JsonObject } from '@backstage/types'; import { Knex } from 'knex'; +import { LifecycleService } from '@backstage/backend-plugin-api'; import { Logger } from 'winston'; -import { Octokit } from 'octokit'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { PermissionRuleParams } from '@backstage/plugin-permission-common'; @@ -38,6 +41,7 @@ import { TaskBrokerDispatchResult as TaskBrokerDispatchResult_2 } from '@backsta import { TaskCompletionState as TaskCompletionState_2 } from '@backstage/plugin-scaffolder-node'; import { TaskContext as TaskContext_2 } from '@backstage/plugin-scaffolder-node'; import { TaskEventType as TaskEventType_2 } from '@backstage/plugin-scaffolder-node'; +import { TaskRecovery } from '@backstage/plugin-scaffolder-common'; import { TaskSecrets as TaskSecrets_2 } from '@backstage/plugin-scaffolder-node'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common'; @@ -197,454 +201,58 @@ export const createFilesystemRenameAction: () => TemplateAction_2< JsonObject >; -// @public -export function createGithubActionsDispatchAction(options: { - integrations: ScmIntegrations; - githubCredentialsProvider?: GithubCredentialsProvider; -}): TemplateAction_2< - { - repoUrl: string; - workflowId: string; - branchOrTagName: string; - workflowInputs?: - | { - [key: string]: string; - } - | undefined; - token?: string | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createGithubActionsDispatchAction: typeof github.createGithubActionsDispatchAction; -// @public -export function createGithubDeployKeyAction(options: { - integrations: ScmIntegrationRegistry; -}): TemplateAction_2< - { - repoUrl: string; - publicKey: string; - privateKey: string; - deployKeyName: string; - privateKeySecretName?: string | undefined; - token?: string | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createGithubDeployKeyAction: typeof github.createGithubDeployKeyAction; -// @public -export function createGithubEnvironmentAction(options: { - integrations: ScmIntegrationRegistry; -}): TemplateAction_2< - { - repoUrl: string; - name: string; - deploymentBranchPolicy?: - | { - protected_branches: boolean; - custom_branch_policies: boolean; - } - | undefined; - customBranchPolicyNames?: string[] | undefined; - environmentVariables?: - | { - [key: string]: string; - } - | undefined; - secrets?: - | { - [key: string]: string; - } - | undefined; - token?: string | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createGithubEnvironmentAction: typeof github.createGithubEnvironmentAction; -// @public -export function createGithubIssuesLabelAction(options: { - integrations: ScmIntegrationRegistry; - githubCredentialsProvider?: GithubCredentialsProvider; -}): TemplateAction_2< - { - repoUrl: string; - number: number; - labels: string[]; - token?: string | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createGithubIssuesLabelAction: typeof github.createGithubIssuesLabelAction; -// @public -export interface CreateGithubPullRequestActionOptions { - clientFactory?: ( - input: CreateGithubPullRequestClientFactoryInput, - ) => Promise; - githubCredentialsProvider?: GithubCredentialsProvider; - integrations: ScmIntegrationRegistry; -} +// @public @deprecated (undocumented) +export type CreateGithubPullRequestActionOptions = + github.CreateGithubPullRequestActionOptions; -// @public -export type CreateGithubPullRequestClientFactoryInput = { - integrations: ScmIntegrationRegistry; - githubCredentialsProvider?: GithubCredentialsProvider; - host: string; - owner: string; - repo: string; - token?: string; -}; +// @public @deprecated (undocumented) +export const createGithubRepoCreateAction: typeof github.createGithubRepoCreateAction; -// @public -export function createGithubRepoCreateAction(options: { - integrations: ScmIntegrationRegistry; - githubCredentialsProvider?: GithubCredentialsProvider; -}): TemplateAction_2< - { - repoUrl: string; - description?: string | undefined; - homepage?: string | undefined; - access?: string | undefined; - deleteBranchOnMerge?: boolean | undefined; - gitAuthorName?: string | undefined; - gitAuthorEmail?: string | undefined; - allowRebaseMerge?: boolean | undefined; - allowSquashMerge?: boolean | undefined; - squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE' | undefined; - squashMergeCommitMessage?: - | 'PR_BODY' - | 'COMMIT_MESSAGES' - | 'BLANK' - | undefined; - allowMergeCommit?: boolean | undefined; - allowAutoMerge?: boolean | undefined; - requireCodeOwnerReviews?: boolean | undefined; - bypassPullRequestAllowances?: - | { - users?: string[] | undefined; - teams?: string[] | undefined; - apps?: string[] | undefined; - } - | undefined; - requiredApprovingReviewCount?: number | undefined; - restrictions?: - | { - users: string[]; - teams: string[]; - apps?: string[] | undefined; - } - | undefined; - requiredStatusCheckContexts?: string[] | undefined; - requireBranchesToBeUpToDate?: boolean | undefined; - requiredConversationResolution?: boolean | undefined; - repoVisibility?: 'internal' | 'private' | 'public' | undefined; - collaborators?: - | ( - | { - user: string; - access: string; - } - | { - team: string; - access: string; - } - | { - username: string; - access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; - } - )[] - | undefined; - hasProjects?: boolean | undefined; - hasWiki?: boolean | undefined; - hasIssues?: boolean | undefined; - token?: string | undefined; - topics?: string[] | undefined; - repoVariables?: - | { - [key: string]: string; - } - | undefined; - secrets?: - | { - [key: string]: string; - } - | undefined; - requireCommitSigning?: boolean | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createGithubRepoPushAction: typeof github.createGithubRepoPushAction; -// @public -export function createGithubRepoPushAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; - githubCredentialsProvider?: GithubCredentialsProvider; -}): TemplateAction_2< - { - repoUrl: string; - description?: string | undefined; - defaultBranch?: string | undefined; - protectDefaultBranch?: boolean | undefined; - protectEnforceAdmins?: boolean | undefined; - gitCommitMessage?: string | undefined; - gitAuthorName?: string | undefined; - gitAuthorEmail?: string | undefined; - requireCodeOwnerReviews?: boolean | undefined; - dismissStaleReviews?: boolean | undefined; - bypassPullRequestAllowances?: - | { - users?: string[]; - teams?: string[]; - apps?: string[]; - } - | undefined; - requiredApprovingReviewCount?: number | undefined; - restrictions?: - | { - users: string[]; - teams: string[]; - apps?: string[]; - } - | undefined; - requiredStatusCheckContexts?: string[] | undefined; - requireBranchesToBeUpToDate?: boolean | undefined; - requiredConversationResolution?: boolean | undefined; - sourcePath?: string | undefined; - token?: string | undefined; - requiredCommitSigning?: boolean | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createGithubWebhookAction: typeof github.createGithubWebhookAction; -// @public -export function createGithubWebhookAction(options: { - integrations: ScmIntegrationRegistry; - defaultWebhookSecret?: string; - githubCredentialsProvider?: GithubCredentialsProvider; -}): TemplateAction_2< - { - repoUrl: string; - webhookUrl: string; - webhookSecret?: string | undefined; - events?: string[] | undefined; - active?: boolean | undefined; - contentType?: 'form' | 'json' | undefined; - insecureSsl?: boolean | undefined; - token?: string | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createPublishAzureAction: typeof azure.createPublishAzureAction; -// @public -export function createPublishAzureAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction_2< - { - repoUrl: string; - description?: string | undefined; - defaultBranch?: string | undefined; - sourcePath?: string | undefined; - token?: string | undefined; - gitCommitMessage?: string | undefined; - gitAuthorName?: string | undefined; - gitAuthorEmail?: string | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createPublishBitbucketAction: typeof bitbucket.createPublishBitbucketAction; -// @public @deprecated -export function createPublishBitbucketAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction_2< - { - repoUrl: string; - description?: string | undefined; - defaultBranch?: string | undefined; - repoVisibility?: 'private' | 'public' | undefined; - sourcePath?: string | undefined; - enableLFS?: boolean | undefined; - token?: string | undefined; - gitCommitMessage?: string | undefined; - gitAuthorName?: string | undefined; - gitAuthorEmail?: string | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createPublishBitbucketCloudAction: typeof bitbucket.createPublishBitbucketCloudAction; -// @public -export function createPublishBitbucketCloudAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction_2< - { - repoUrl: string; - description?: string | undefined; - defaultBranch?: string | undefined; - repoVisibility?: 'private' | 'public' | undefined; - sourcePath?: string | undefined; - token?: string | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createPublishBitbucketServerAction: typeof bitbucket.createPublishBitbucketServerAction; -// @public -export function createPublishBitbucketServerAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction_2< - { - repoUrl: string; - description?: string | undefined; - defaultBranch?: string | undefined; - repoVisibility?: 'private' | 'public' | undefined; - sourcePath?: string | undefined; - enableLFS?: boolean | undefined; - token?: string | undefined; - gitCommitMessage?: string | undefined; - gitAuthorName?: string | undefined; - gitAuthorEmail?: string | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createPublishBitbucketServerPullRequestAction: typeof bitbucket.createPublishBitbucketServerPullRequestAction; -// @public -export function createPublishBitbucketServerPullRequestAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction_2< - { - repoUrl: string; - title: string; - description?: string | undefined; - targetBranch?: string | undefined; - sourceBranch: string; - token?: string | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createPublishGerritAction: typeof gerrit.createPublishGerritAction; -// @public -export function createPublishGerritAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction_2< - { - repoUrl: string; - description: string; - defaultBranch?: string | undefined; - gitCommitMessage?: string | undefined; - gitAuthorName?: string | undefined; - gitAuthorEmail?: string | undefined; - sourcePath?: string | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createPublishGerritReviewAction: typeof gerrit.createPublishGerritReviewAction; -// @public -export function createPublishGerritReviewAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction_2< - { - repoUrl: string; - branch?: string | undefined; - sourcePath?: string | undefined; - gitCommitMessage?: string | undefined; - gitAuthorName?: string | undefined; - gitAuthorEmail?: string | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createPublishGithubAction: typeof github.createPublishGithubAction; -// @public -export function createPublishGithubAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; - githubCredentialsProvider?: GithubCredentialsProvider; -}): TemplateAction_2< - { - repoUrl: string; - description?: string | undefined; - homepage?: string | undefined; - access?: string | undefined; - defaultBranch?: string | undefined; - protectDefaultBranch?: boolean | undefined; - protectEnforceAdmins?: boolean | undefined; - deleteBranchOnMerge?: boolean | undefined; - gitCommitMessage?: string | undefined; - gitAuthorName?: string | undefined; - gitAuthorEmail?: string | undefined; - allowRebaseMerge?: boolean | undefined; - allowSquashMerge?: boolean | undefined; - squashMergeCommitTitle?: 'PR_TITLE' | 'COMMIT_OR_PR_TITLE' | undefined; - squashMergeCommitMessage?: - | 'PR_BODY' - | 'COMMIT_MESSAGES' - | 'BLANK' - | undefined; - allowMergeCommit?: boolean | undefined; - allowAutoMerge?: boolean | undefined; - sourcePath?: string | undefined; - bypassPullRequestAllowances?: - | { - users?: string[]; - teams?: string[]; - apps?: string[]; - } - | undefined; - requiredApprovingReviewCount?: number | undefined; - restrictions?: - | { - users: string[]; - teams: string[]; - apps?: string[]; - } - | undefined; - requireCodeOwnerReviews?: boolean | undefined; - dismissStaleReviews?: boolean | undefined; - requiredStatusCheckContexts?: string[] | undefined; - requireBranchesToBeUpToDate?: boolean | undefined; - requiredConversationResolution?: boolean | undefined; - repoVisibility?: 'internal' | 'private' | 'public' | undefined; - collaborators?: - | ( - | { - user: string; - access: string; - } - | { - team: string; - access: string; - } - | { - username: string; - access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; - } - )[] - | undefined; - hasProjects?: boolean | undefined; - hasWiki?: boolean | undefined; - hasIssues?: boolean | undefined; - token?: string | undefined; - topics?: string[] | undefined; - repoVariables?: - | { - [key: string]: string; - } - | undefined; - secrets?: - | { - [key: string]: string; - } - | undefined; - requiredCommitSigning?: boolean | undefined; - }, - JsonObject ->; - -// @public +// @public @deprecated (undocumented) export const createPublishGithubPullRequestAction: ( - options: CreateGithubPullRequestActionOptions, + options: github.CreateGithubPullRequestActionOptions, ) => TemplateAction_2< { title: string; @@ -664,57 +272,10 @@ export const createPublishGithubPullRequestAction: ( JsonObject >; -// @public -export function createPublishGitlabAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction_2< - { - repoUrl: string; - defaultBranch?: string | undefined; - repoVisibility?: 'internal' | 'private' | 'public' | undefined; - sourcePath?: string | undefined; - token?: string | undefined; - gitCommitMessage?: string | undefined; - gitAuthorName?: string | undefined; - gitAuthorEmail?: string | undefined; - setUserAsOwner?: boolean | undefined; - topics?: string[] | undefined; - settings?: - | { - path?: string | undefined; - auto_devops_enabled?: boolean | undefined; - ci_config_path?: string | undefined; - description?: string | undefined; - topics?: string[] | undefined; - visibility?: 'internal' | 'private' | 'public' | undefined; - } - | undefined; - branches?: - | { - name: string; - protect?: boolean | undefined; - create?: boolean | undefined; - ref?: string | undefined; - }[] - | undefined; - projectVariables?: - | { - key: string; - value: string; - description?: string | undefined; - variable_type?: string | undefined; - protected?: boolean | undefined; - masked?: boolean | undefined; - raw?: boolean | undefined; - environment_scope?: string | undefined; - }[] - | undefined; - }, - JsonObject ->; +// @public @deprecated (undocumented) +export const createPublishGitlabAction: typeof gitlab.createPublishGitlabAction; -// @public +// @public @deprecated (undocumented) export const createPublishGitlabMergeRequestAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction_2< @@ -841,9 +402,14 @@ export class DatabaseTaskStore implements TaskStore { listStaleTasks(options: { timeoutS: number }): Promise<{ tasks: { taskId: string; + recovery?: TaskRecovery; }[]; }>; // (undocumented) + recoverTasks(options: TaskStoreRecoverTaskOptions): Promise<{ + ids: string[]; + }>; + // (undocumented) shutdownTask(options: TaskStoreShutDownTaskOptions): Promise; } @@ -858,19 +424,6 @@ export const executeShellCommand: typeof executeShellCommand_2; // @public @deprecated export const fetchContents: typeof fetchContents_2; -// @public (undocumented) -export type OctokitWithPullRequestPluginClient = Octokit & { - createPullRequest(options: createPullRequest.Options): Promise<{ - data: { - html_url: string; - number: number; - base: { - ref: string; - }; - }; - } | null>; -}; - // @public export interface RouterOptions { // (undocumented) @@ -889,6 +442,8 @@ export interface RouterOptions { // (undocumented) identity?: IdentityApi; // (undocumented) + lifecycle?: LifecycleService; + // (undocumented) logger: Logger; // (undocumented) permissionRules?: Array< @@ -1006,6 +561,10 @@ export interface TaskStore { }[]; }>; // (undocumented) + recoverTasks?(options: TaskStoreRecoverTaskOptions): Promise<{ + ids: string[]; + }>; + // (undocumented) shutdownTask?(options: TaskStoreShutDownTaskOptions): Promise; } @@ -1033,6 +592,11 @@ export type TaskStoreListEventsOptions = { after?: number | undefined; }; +// @public +export type TaskStoreRecoverTaskOptions = { + timeout: HumanDuration; +}; + // @public export type TaskStoreShutDownTaskOptions = { taskId: string; @@ -1045,9 +609,13 @@ export class TaskWorker { // (undocumented) protected onReadyToClaimTask(): Promise; // (undocumented) + recoverTasks(): Promise; + // (undocumented) runOneTask(task: TaskContext): Promise; // (undocumented) start(): void; + // (undocumented) + stop(): void; } // @public @deprecated (undocumented) diff --git a/plugins/scaffolder-backend/config.d.ts b/plugins/scaffolder-backend/config.d.ts index 5914cddce7..477defe026 100644 --- a/plugins/scaffolder-backend/config.d.ts +++ b/plugins/scaffolder-backend/config.d.ts @@ -40,6 +40,22 @@ export interface Config { */ concurrentTasksLimit?: number; + /** + * Sets the tasks recoverability on system start up. + * + * If not specified, the default value is false. + */ + EXPERIMENTAL_recoverTasks?: boolean; + + /** + * Every task which is in progress state and having a last heartbeat longer than a specified timeout is going to + * be attempted to recover. + * + * If not specified, the default value is 5 seconds. + * + */ + EXPERIMENTAL_recoverTasksTimeout?: HumanDuration; + /** * Makes sure to auto-expire and clean up things that time out or for other reasons should not be left lingering. * diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index b1d5b86671..2325f7d17e 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend", "description": "The Backstage backend plugin that helps you create new things", - "version": "1.19.2-next.0", + "version": "1.21.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -55,42 +55,31 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", - "@backstage/plugin-catalog-backend": "workspace:^", "@backstage/plugin-catalog-backend-module-scaffolder-entity-model": "workspace:^", - "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", + "@backstage/plugin-scaffolder-backend-module-azure": "workspace:^", + "@backstage/plugin-scaffolder-backend-module-bitbucket": "workspace:^", + "@backstage/plugin-scaffolder-backend-module-gerrit": "workspace:^", + "@backstage/plugin-scaffolder-backend-module-github": "workspace:^", + "@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", - "@gitbeaker/core": "^35.6.0", - "@gitbeaker/node": "^35.1.0", - "@octokit/webhooks": "^10.0.0", "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", - "azure-devops-node-api": "^11.0.1", - "command-exists": "^1.2.9", - "compression": "^1.7.4", - "cors": "^2.8.5", "express": "^4.17.1", "express-promise-router": "^4.1.0", "fs-extra": "10.1.0", - "git-url-parse": "^13.0.0", "globby": "^11.0.0", "isbinaryfile": "^5.0.0", "isolated-vm": "^4.5.0", - "isomorphic-git": "^1.23.0", "jsonschema": "^1.2.6", "knex": "^3.0.0", - "libsodium-wrappers": "^0.7.11", "lodash": "^4.17.21", "luxon": "^3.0.0", - "morgan": "^1.10.0", - "node-fetch": "^2.6.7", "nunjucks": "^3.2.3", - "octokit": "^2.0.0", - "octokit-plugin-create-pull-request": "^3.10.0", "p-limit": "^3.1.0", "p-queue": "^6.6.2", "prom-client": "^14.0.1", @@ -98,25 +87,19 @@ "winston": "^3.2.1", "yaml": "^2.0.0", "zen-observable": "^0.10.0", - "zod": "^3.21.4" + "zod": "^3.22.4" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", - "@types/git-url-parse": "^9.0.0", - "@types/libsodium-wrappers": "^0.7.10", "@types/nunjucks": "^3.1.4", "@types/supertest": "^2.0.8", "@types/zen-observable": "^0.8.0", "esbuild": "^0.19.0", - "jest-when": "^3.1.0", - "msw": "^1.0.0", "strip-ansi": "^7.1.0", "supertest": "^6.1.3", - "wait-for-expect": "^3.0.2", - "yaml": "^2.0.0" + "wait-for-expect": "^3.0.2" }, "files": [ "dist", diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index beabaa50aa..61c5ed12ee 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -32,7 +32,18 @@ import { scaffolderTaskBrokerExtensionPoint, scaffolderTemplatingExtensionPoint, } from '@backstage/plugin-scaffolder-node/alpha'; -import { createBuiltinActions } from './scaffolder'; +import { + createCatalogRegisterAction, + createCatalogWriteAction, + createDebugLogAction, + createFetchCatalogEntityAction, + createFetchPlainAction, + createFetchPlainFileAction, + createFetchTemplateAction, + createFilesystemDeleteAction, + createFilesystemRenameAction, + createWaitAction, +} from './scaffolder'; import { createRouter } from './service/router'; /** @@ -75,6 +86,7 @@ export const scaffolderPlugin = createBackendPlugin({ deps: { logger: coreServices.logger, config: coreServices.rootConfig, + lifecycle: coreServices.rootLifecycle, reader: coreServices.urlReader, permissions: coreServices.permissions, database: coreServices.database, @@ -84,6 +96,7 @@ export const scaffolderPlugin = createBackendPlugin({ async init({ logger, config, + lifecycle, reader, database, httpRouter, @@ -91,20 +104,39 @@ export const scaffolderPlugin = createBackendPlugin({ permissions, }) { const log = loggerToWinstonLogger(logger); + const integrations = ScmIntegrations.fromConfig(config); const actions = [ + // actions provided from other modules ...addedActions, - ...createBuiltinActions({ - integrations: ScmIntegrations.fromConfig(config), - catalogClient, + + // built-in actions for the scaffolder + createFetchPlainAction({ + reader, + integrations, + }), + createFetchPlainFileAction({ + reader, + integrations, + }), + createFetchTemplateAction({ + integrations, reader, - config, additionalTemplateFilters, additionalTemplateGlobals, }), + createDebugLogAction(), + createWaitAction(), + // todo(blam): maybe these should be a -catalog module? + createCatalogRegisterAction({ catalogClient, integrations }), + createFetchCatalogEntityAction({ catalogClient }), + createCatalogWriteAction(), + createFilesystemDeleteAction(), + createFilesystemRenameAction(), ]; const actionIds = actions.map(action => action.id).join(', '); + log.info( `Starting scaffolder with the following actions enabled ${actionIds}`, ); @@ -115,6 +147,7 @@ export const scaffolderPlugin = createBackendPlugin({ database, catalogClient, reader, + lifecycle, actions, taskBroker, additionalTemplateFilters, diff --git a/plugins/scaffolder-backend/src/lib/templating/filters.ts b/plugins/scaffolder-backend/src/lib/templating/filters.ts index 7808d6d136..21b43a6164 100644 --- a/plugins/scaffolder-backend/src/lib/templating/filters.ts +++ b/plugins/scaffolder-backend/src/lib/templating/filters.ts @@ -17,7 +17,7 @@ import { parseEntityRef } from '@backstage/catalog-model'; import { ScmIntegrations } from '@backstage/integration'; import type { JsonObject, JsonValue } from '@backstage/types'; import { TemplateFilter } from '..'; -import { parseRepoUrl } from '../../scaffolder/actions/builtin/publish/util'; +import { parseRepoUrl } from '@backstage/plugin-scaffolder-node'; import get from 'lodash/get'; export const createDefaultFilters = ({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index 4dcd2c2a0d..02b96e9fa7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -42,26 +42,37 @@ import { } from './filesystem'; import { createGithubActionsDispatchAction, + createGithubAutolinksAction, createGithubDeployKeyAction, createGithubEnvironmentAction, createGithubIssuesLabelAction, createGithubRepoCreateAction, createGithubRepoPushAction, createGithubWebhookAction, -} from './github'; + createPublishGithubAction, + createPublishGithubPullRequestAction, +} from '@backstage/plugin-scaffolder-backend-module-github'; + +import { createPublishAzureAction } from '@backstage/plugin-scaffolder-backend-module-azure'; + import { - createPublishAzureAction, createPublishBitbucketAction, createPublishBitbucketCloudAction, createPublishBitbucketServerAction, createPublishBitbucketServerPullRequestAction, + createBitbucketPipelinesRunAction, +} from '@backstage/plugin-scaffolder-backend-module-bitbucket'; + +import { createPublishGerritAction, createPublishGerritReviewAction, - createPublishGithubAction, - createPublishGithubPullRequestAction, +} from '@backstage/plugin-scaffolder-backend-module-gerrit'; + +import { createPublishGitlabAction, + createGitlabRepoPushAction, createPublishGitlabMergeRequestAction, -} from './publish'; +} from '@backstage/plugin-scaffolder-backend-module-gitlab'; /** * The options passed to {@link createBuiltinActions} @@ -96,8 +107,11 @@ export interface CreateBuiltInActionsOptions { * A function to generate create a list of default actions that the scaffolder provides. * Is called internally in the default setup, but can be used when adding your own actions or overriding the default ones * + * TODO(blam): version 2 of the scaffolder shouldn't ship with the additional modules. We should ship the basics, and let people install + * modules for the providers they want to use. * @public * @returns A list of actions that can be used in the scaffolder + * */ export const createBuiltinActions = ( options: CreateBuiltInActionsOptions, @@ -153,6 +167,9 @@ export const createBuiltinActions = ( createPublishGitlabMergeRequestAction({ integrations, }), + createGitlabRepoPushAction({ + integrations, + }), createPublishBitbucketAction({ integrations, config, @@ -207,6 +224,13 @@ export const createBuiltinActions = ( createGithubDeployKeyAction({ integrations, }), + createGithubAutolinksAction({ + integrations, + githubCredentialsProvider, + }), + createBitbucketPipelinesRunAction({ + integrations, + }), ]; return actions as TemplateAction[]; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts index 7ab98482ff..6fdbb24484 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts @@ -19,5 +19,3 @@ export * from './createBuiltinActions'; export * from './debug'; export * from './fetch'; export * from './filesystem'; -export * from './publish'; -export * from './github'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts deleted file mode 100644 index bed3a2316c..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { createPublishAzureAction } from './azure'; -export { createPublishBitbucketAction } from './bitbucket'; -export { createPublishBitbucketCloudAction } from './bitbucketCloud'; -export { createPublishBitbucketServerAction } from './bitbucketServer'; -export { createPublishBitbucketServerPullRequestAction } from './bitbucketServerPullRequest'; -export { createPublishGerritAction } from './gerrit'; -export { createPublishGerritReviewAction } from './gerritReview'; -export { createPublishGithubAction } from './github'; -export { createPublishGithubPullRequestAction } from './githubPullRequest'; -export type { - CreateGithubPullRequestClientFactoryInput, - CreateGithubPullRequestActionOptions, - OctokitWithPullRequestPluginClient, -} from './githubPullRequest'; -export { createPublishGitlabAction } from './gitlab'; -export { createPublishGitlabMergeRequestAction } from './gitlabMergeRequest'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.test.ts deleted file mode 100644 index 5446109056..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.test.ts +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import path from 'path'; -import { getRepoSourceDirectory, isExecutable, parseRepoUrl } from './util'; -import { ScmIntegrations } from '@backstage/integration'; -import { ConfigReader } from '@backstage/config'; - -describe('getRepoSourceDirectory', () => { - it('should return workspace root if no sub folder is given', () => { - expect( - getRepoSourceDirectory(path.join('/', 'var', 'workspace'), undefined), - ).toEqual(path.join('/', 'var', 'workspace')); - }); - - it('should return path in workspace if sub folder is given', () => { - expect( - getRepoSourceDirectory( - path.join('/', 'var', 'workspace'), - path.join('path', 'of', 'subfolder'), - ), - ).toEqual(path.join('/', 'var', 'workspace', 'path', 'of', 'subfolder')); - }); - - it('should not allow traversal outside the workspace root', () => { - // We have to construct the path manually here, as path.join would mitigate the path traversal - expect( - getRepoSourceDirectory( - path.join('/', 'var', 'workspace'), - `..${path.sep}secret`, - ), - ).toEqual(path.join('/', 'var', 'workspace', 'secret')); - expect( - getRepoSourceDirectory( - path.join('/', 'var', 'workspace'), - `.${path.sep}path${path.sep}..${path.sep}..${path.sep}secret`, - ), - ).toEqual(path.join('/', 'var', 'workspace', 'secret')); - expect( - getRepoSourceDirectory( - path.join('/', 'var', 'workspace'), - path.join('/', 'absolute', 'secret'), - ), - ).toEqual(path.join('/', 'var', 'workspace', 'absolute', 'secret')); - }); -}); - -describe('isExecutable', () => { - it('should return true for file mode 777', () => { - expect(isExecutable(0o100777)).toBe(true); - }); - it('should return true for file mode 775', () => { - expect(isExecutable(0o100775)).toBe(true); - }); - it('should return true for file mode 755', () => { - expect(isExecutable(0o100755)).toBe(true); - }); - it('should return true for file mode 700', () => { - expect(isExecutable(0o100700)).toBe(true); - }); - it('should return true for file mode 770', () => { - expect(isExecutable(0o100770)).toBe(true); - }); - it('should return true for file mode 670', () => { - expect(isExecutable(0o100670)).toBe(true); - }); - it('should return false for file mode 644', () => { - expect(isExecutable(0o100644)).toBe(false); - }); - it('should return false for file mode 600', () => { - expect(isExecutable(0o100600)).toBe(false); - }); - it('should return false for file mode 640', () => { - expect(isExecutable(0o100640)).toBe(false); - }); -}); - -describe('parseRepoUrl', () => { - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'www.gitlab.com', - token: 'token', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], - bitbucket: [ - { - host: 'www.bitbucket.net', - apiBaseUrl: 'https://api.hosted.bitbucket.net', - }, - { - host: 'www.bitbucket.org', - username: 'username', - appPassword: 'password', - apiBaseUrl: 'https://api.hosted.bitbucket.org', - }, - ], - }, - }); - const integrations = ScmIntegrations.fromConfig(config); - it('should throw an exception if no parameters are passed (gitlab)', () => { - expect(() => parseRepoUrl('www.gitlab.com', integrations)).toThrow( - 'Invalid repo URL passed to publisher: https://www.gitlab.com/, missing owner', - ); - }); - it('should return the project ID in the `project` parameter (gitlab)', () => { - expect( - parseRepoUrl('www.gitlab.com?project=234', integrations), - ).toStrictEqual({ - host: 'www.gitlab.com', - organization: undefined, - owner: undefined, - project: '234', - repo: null, - workspace: undefined, - }); - }); - it('should throw an exception if the repo parameter is missing, but owner parameter is set', () => { - expect(() => - parseRepoUrl('www.gitlab.com?owner=owner', integrations), - ).toThrow( - 'Invalid repo URL passed to publisher: https://www.gitlab.com/?owner=owner, missing repo', - ); - }); - it('should throw an exception if the owner parameter is missing, but repo parameter is set (gitlab)', () => { - expect(() => - parseRepoUrl('www.gitlab.com?repo=repo', integrations), - ).toThrow( - 'Invalid repo URL passed to publisher: https://www.gitlab.com/?repo=repo, missing owner', - ); - }); - it('should return the workspace, project and repo (bitbucket.org)', () => { - expect( - parseRepoUrl( - 'www.bitbucket.org?project=project&workspace=workspace&repo=repo', - integrations, - ), - ).toStrictEqual({ - host: 'www.bitbucket.org', - organization: undefined, - owner: undefined, - project: 'project', - repo: 'repo', - workspace: 'workspace', - }); - }); - it('should return the project and repo (another bitbucket instance)', () => { - expect( - parseRepoUrl('www.bitbucket.net?project=project&repo=repo', integrations), - ).toStrictEqual({ - host: 'www.bitbucket.net', - organization: undefined, - owner: undefined, - project: 'project', - repo: 'repo', - workspace: undefined, - }); - }); - it('should throw an exception if the workspace parameter is missing for bitbucket.org instance (bitbucket.org)', () => { - expect(() => - parseRepoUrl('www.bitbucket.org?project=project&repo=repo', integrations), - ).toThrow( - 'Invalid repo URL passed to publisher: https://www.bitbucket.org/?project=project&repo=repo, missing workspace', - ); - }); -}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/deprecated.ts b/plugins/scaffolder-backend/src/scaffolder/actions/deprecated.ts new file mode 100644 index 0000000000..d5a13fbf6d --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/deprecated.ts @@ -0,0 +1,126 @@ +/* + * 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 * as github from '@backstage/plugin-scaffolder-backend-module-github'; +import * as gitlab from '@backstage/plugin-scaffolder-backend-module-gitlab'; +import * as azure from '@backstage/plugin-scaffolder-backend-module-azure'; +import * as bitbucket from '@backstage/plugin-scaffolder-backend-module-bitbucket'; +import * as gerrit from '@backstage/plugin-scaffolder-backend-module-gerrit'; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + */ +export const createGithubActionsDispatchAction = + github.createGithubActionsDispatchAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + */ +export const createGithubDeployKeyAction = github.createGithubDeployKeyAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + */ +export const createGithubEnvironmentAction = + github.createGithubEnvironmentAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + */ +export const createGithubIssuesLabelAction = + github.createGithubIssuesLabelAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + */ +export type CreateGithubPullRequestActionOptions = + github.CreateGithubPullRequestActionOptions; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + */ +export const createGithubRepoCreateAction = github.createGithubRepoCreateAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + */ +export const createGithubRepoPushAction = github.createGithubRepoPushAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + */ +export const createGithubWebhookAction = github.createGithubWebhookAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + */ +export const createPublishGithubAction = github.createPublishGithubAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + */ +export const createPublishGithubPullRequestAction = + github.createPublishGithubPullRequestAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-bitbucket instead + */ +export const createPublishBitbucketAction = + bitbucket.createPublishBitbucketAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-bitbucket instead + */ +export const createPublishBitbucketCloudAction = + bitbucket.createPublishBitbucketCloudAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-bitbucket instead + */ +export const createPublishBitbucketServerAction = + bitbucket.createPublishBitbucketServerAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-bitbucket instead + */ +export const createPublishBitbucketServerPullRequestAction = + bitbucket.createPublishBitbucketServerPullRequestAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-azure instead + */ +export const createPublishAzureAction = azure.createPublishAzureAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-gerrit instead + */ +export const createPublishGerritAction = gerrit.createPublishGerritAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-gerrit instead + */ +export const createPublishGerritReviewAction = + gerrit.createPublishGerritReviewAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-gitlab instead + */ +export const createPublishGitlabAction = gitlab.createPublishGitlabAction; + +/** + * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-gitlab instead + */ +export const createPublishGitlabMergeRequestAction = + gitlab.createPublishGitlabMergeRequestAction; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/index.ts index 9ea0607f06..8fdad68499 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/index.ts @@ -15,4 +15,6 @@ */ export * from './builtin'; +export * from './deprecated'; + export { TemplateActionRegistry } from './TemplateActionRegistry'; diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index 655c485a6d..e202111d79 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -21,20 +21,19 @@ import { v4 as uuid } from 'uuid'; import { pathToFileURL } from 'url'; import { Logger } from 'winston'; import { + createTemplateAction, + TaskSecrets, + TemplateFilter, + TemplateGlobal, deserializeDirectoryContents, SerializedFile, serializeDirectoryContents, -} from '../../lib/files'; -import { TemplateFilter, TemplateGlobal } from '../../lib'; +} from '@backstage/plugin-scaffolder-node'; import { TemplateActionRegistry } from '../actions'; import { NunjucksWorkflowRunner } from '../tasks/NunjucksWorkflowRunner'; import { DecoratedActionsRegistry } from './DecoratedActionsRegistry'; import fs from 'fs-extra'; import { resolveSafeChildPath } from '@backstage/backend-common'; -import { - createTemplateAction, - TaskSecrets, -} from '@backstage/plugin-scaffolder-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; interface DryRunInput { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 6beb01e401..766db4646e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -29,14 +29,19 @@ import { TaskStoreCreateTaskOptions, TaskStoreCreateTaskResult, TaskStoreShutDownTaskOptions, + TaskStoreRecoverTaskOptions, } from './types'; import { SerializedTaskEvent, SerializedTask, TaskStatus, TaskEventType, + TaskSecrets, } from '@backstage/plugin-scaffolder-node'; -import { DateTime } from 'luxon'; +import { DateTime, Duration } from 'luxon'; +import { TaskRecovery, TaskSpec } from '@backstage/plugin-scaffolder-common'; +import { trimEventsTillLastRecovery } from './taskRecoveryHelper'; +import { intervalFromNowTill } from './dbUtil'; const migrationsDir = resolvePackagePath( '@backstage/plugin-scaffolder-backend', @@ -114,6 +119,30 @@ export class DatabaseTaskStore implements TaskStore { return new DatabaseTaskStore(client); } + private isRecoverableTask(spec: TaskSpec): boolean { + return ['startOver'].includes( + spec.EXPERIMENTAL_recovery?.EXPERIMENTAL_strategy ?? 'none', + ); + } + + private parseSpec({ spec, id }: { spec: string; id: string }): TaskSpec { + try { + return JSON.parse(spec); + } catch (error) { + throw new Error(`Failed to parse spec of task '${id}', ${error}`); + } + } + + private parseTaskSecrets(taskRow: RawDbTaskRow): TaskSecrets | undefined { + try { + return taskRow.secrets ? JSON.parse(taskRow.secrets) : undefined; + } catch (error) { + throw new Error( + `Failed to parse secrets of task '${taskRow.id}', ${error}`, + ); + } + } + private static async getClient( database: PluginDatabaseManager | Knex, ): Promise { @@ -223,34 +252,31 @@ export class DatabaseTaskStore implements TaskStore { return undefined; } + const spec = this.parseSpec(task); + const updateCount = await tx('tasks') .where({ id: task.id, status: 'open' }) .update({ status: 'processing', last_heartbeat_at: this.db.fn.now(), - // remove the secrets when moving to processing state. - secrets: null, + // remove the secrets for non-recoverable tasks when moving to processing state. + secrets: this.isRecoverableTask(spec) ? task.secrets : null, }); if (updateCount < 1) { return undefined; } - try { - const spec = JSON.parse(task.spec); - const secrets = task.secrets ? JSON.parse(task.secrets) : undefined; - return { - id: task.id, - spec, - status: 'processing', - lastHeartbeatAt: task.last_heartbeat_at, - createdAt: task.created_at, - createdBy: task.created_by ?? undefined, - secrets, - }; - } catch (error) { - throw new Error(`Failed to parse spec of task '${task.id}', ${error}`); - } + const secrets = this.parseTaskSecrets(task); + return { + id: task.id, + spec, + status: 'processing', + lastHeartbeatAt: task.last_heartbeat_at, + createdAt: task.created_at, + createdBy: task.created_by ?? undefined, + secrets, + }; }); } @@ -266,25 +292,15 @@ export class DatabaseTaskStore implements TaskStore { } async listStaleTasks(options: { timeoutS: number }): Promise<{ - tasks: { taskId: string }[]; + tasks: { taskId: string; recovery?: TaskRecovery }[]; }> { const { timeoutS } = options; - let heartbeatInterval = this.db.raw(`? - interval '${timeoutS} seconds'`, [ - this.db.fn.now(), - ]); - if (this.db.client.config.client.includes('mysql')) { - heartbeatInterval = this.db.raw( - `date_sub(now(), interval ${timeoutS} second)`, - ); - } else if (this.db.client.config.client.includes('sqlite3')) { - heartbeatInterval = this.db.raw(`datetime('now', ?)`, [ - `-${timeoutS} seconds`, - ]); - } + const heartbeatInterval = intervalFromNowTill(timeoutS, this.db); const rawRows = await this.db('tasks') .where('status', 'processing') .andWhere('last_heartbeat_at', '<=', heartbeatInterval); const tasks = rawRows.map(row => ({ + recovery: (JSON.parse(row.spec) as TaskSpec).EXPERIMENTAL_recovery, taskId: row.id, })); return { tasks }; @@ -297,7 +313,7 @@ export class DatabaseTaskStore implements TaskStore { }): Promise { const { taskId, status, eventBody } = options; - let oldStatus: string; + let oldStatus: TaskStatus; if (['failed', 'completed', 'cancelled'].includes(status)) { oldStatus = 'processing'; } else { @@ -322,6 +338,7 @@ export class DatabaseTaskStore implements TaskStore { .where(criteria) .update({ status, + secrets: null, }); if (updateCount !== 1) { @@ -409,7 +426,8 @@ export class DatabaseTaskStore implements TaskStore { ); } }); - return { events }; + + return trimEventsTillLastRecovery(events); } async shutdownTask(options: TaskStoreShutDownTaskOptions): Promise { @@ -462,4 +480,42 @@ export class DatabaseTaskStore implements TaskStore { body: serializedBody, }); } + + async recoverTasks( + options: TaskStoreRecoverTaskOptions, + ): Promise<{ ids: string[] }> { + const taskIdsToRecover: string[] = []; + const timeoutS = Duration.fromObject(options.timeout).as('seconds'); + + await this.db.transaction(async tx => { + const heartbeatInterval = intervalFromNowTill(timeoutS, this.db); + + const result = await tx('tasks') + .where('status', 'processing') + .andWhere('last_heartbeat_at', '<=', heartbeatInterval) + .update( + { + status: 'open', + last_heartbeat_at: this.db.fn.now(), + }, + ['id', 'spec'], + ); + + taskIdsToRecover.push(...result.map(i => i.id)); + + for (const { id, spec } of result) { + const taskSpec = JSON.parse(spec as string) as TaskSpec; + await this.db('task_events').insert({ + task_id: id, + event_type: 'recovered', + body: JSON.stringify({ + recoverStrategy: + taskSpec.EXPERIMENTAL_recovery?.EXPERIMENTAL_strategy ?? 'none', + }), + }); + } + }); + + return { ids: taskIdsToRecover }; + } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 139d91b7a2..ce1ad71c33 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -55,6 +55,7 @@ import { } from '@backstage/plugin-permission-common'; import { scaffolderActionRules } from '../../service/rules'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; +import { TaskRecovery } from '@backstage/plugin-scaffolder-common'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; @@ -68,6 +69,7 @@ type NunjucksWorkflowRunnerOptions = { type TemplateContext = { parameters: JsonObject; + EXPERIMENTAL_recovery?: TaskRecovery; steps: { [stepName: string]: { output: { [outputName: string]: JsonValue } }; }; @@ -119,6 +121,7 @@ const isActionAuthorized = createConditionAuthorizer( export class NunjucksWorkflowRunner implements WorkflowRunner { private readonly defaultTemplateFilters: Record; + constructor(private readonly options: NunjucksWorkflowRunnerOptions) { this.defaultTemplateFilters = createDefaultFilters({ integrations: this.options.integrations, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index 2255798d5c..a79624456a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -47,10 +47,16 @@ describe('StorageTaskBroker', () => { storage = await createStore(); }); + const emptyTaskSpec = { spec: { steps: [] } as unknown as TaskSpec }; + const emptyTaskWithFakeSecretsSpec = { + spec: { steps: [] } as unknown as TaskSpec, + secrets: fakeSecrets, + }; + const logger = getVoidLogger(); it('should claim a dispatched work item', async () => { const broker = new StorageTaskBroker(storage, logger); - await broker.dispatch({ spec: {} as TaskSpec }); + await broker.dispatch(emptyTaskSpec); await expect(broker.claim()).resolves.toEqual( expect.any(TaskManager as any), ); @@ -62,7 +68,7 @@ describe('StorageTaskBroker', () => { await expect(Promise.race([promise, 'waiting'])).resolves.toBe('waiting'); - await broker.dispatch({ spec: {} as TaskSpec }); + await broker.dispatch(emptyTaskSpec); await expect(promise).resolves.toEqual(expect.any(TaskManager as any)); }); @@ -85,14 +91,14 @@ describe('StorageTaskBroker', () => { it('should store secrets', async () => { const broker = new StorageTaskBroker(storage, logger); - await broker.dispatch({ spec: {} as TaskSpec, secrets: fakeSecrets }); + await broker.dispatch(emptyTaskWithFakeSecretsSpec); const task = await broker.claim(); expect(task.secrets).toEqual(fakeSecrets); }, 10000); it('should complete a task', async () => { const broker = new StorageTaskBroker(storage, logger); - const dispatchResult = await broker.dispatch({ spec: {} as TaskSpec }); + const dispatchResult = await broker.dispatch(emptyTaskSpec); const task = await broker.claim(); await task.complete('completed'); const taskRow = await storage.getTask(dispatchResult.taskId); @@ -101,10 +107,7 @@ describe('StorageTaskBroker', () => { it('should remove secrets after picking up a task', async () => { const broker = new StorageTaskBroker(storage, logger); - const dispatchResult = await broker.dispatch({ - spec: {} as TaskSpec, - secrets: fakeSecrets, - }); + const dispatchResult = await broker.dispatch(emptyTaskWithFakeSecretsSpec); await broker.claim(); const taskRow = await storage.getTask(dispatchResult.taskId); @@ -113,7 +116,7 @@ describe('StorageTaskBroker', () => { it('should fail a task', async () => { const broker = new StorageTaskBroker(storage, logger); - const dispatchResult = await broker.dispatch({ spec: {} as TaskSpec }); + const dispatchResult = await broker.dispatch(emptyTaskSpec); const task = await broker.claim(); await task.complete('failed'); const taskRow = await storage.getTask(dispatchResult.taskId); @@ -124,7 +127,7 @@ describe('StorageTaskBroker', () => { const broker1 = new StorageTaskBroker(storage, logger); const broker2 = new StorageTaskBroker(storage, logger); - const { taskId } = await broker1.dispatch({ spec: {} as TaskSpec }); + const { taskId } = await broker1.dispatch(emptyTaskSpec); const logPromise = new Promise(resolve => { const observedEvents = new Array(); @@ -169,7 +172,7 @@ describe('StorageTaskBroker', () => { it('should heartbeat', async () => { const broker = new StorageTaskBroker(storage, logger); - const { taskId } = await broker.dispatch({ spec: {} as TaskSpec }); + const { taskId } = await broker.dispatch(emptyTaskSpec); const task = await broker.claim(); const initialTask = await storage.getTask(taskId); @@ -187,7 +190,7 @@ describe('StorageTaskBroker', () => { it('should be update the status to failed if heartbeat fails', async () => { const broker = new StorageTaskBroker(storage, logger); - const { taskId } = await broker.dispatch({ spec: {} as TaskSpec }); + const { taskId } = await broker.dispatch(emptyTaskSpec); const task = await broker.claim(); jest @@ -213,7 +216,7 @@ describe('StorageTaskBroker', () => { it('should list all tasks', async () => { const broker = new StorageTaskBroker(storage, logger); - const { taskId } = await broker.dispatch({ spec: {} as TaskSpec }); + const { taskId } = await broker.dispatch(emptyTaskSpec); const promise = broker.list(); await expect(promise).resolves.toEqual({ @@ -228,7 +231,7 @@ describe('StorageTaskBroker', () => { it('should list only tasks createdBy a specific user', async () => { const broker = new StorageTaskBroker(storage, logger); const { taskId } = await broker.dispatch({ - spec: {} as TaskSpec, + spec: { steps: [] } as unknown as TaskSpec, createdBy: 'user:default/foo', }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index fc5a724e47..8b49f492e8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { Config } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TaskSecrets } from '@backstage/plugin-scaffolder-node'; import { JsonObject, Observable } from '@backstage/types'; @@ -28,6 +29,7 @@ import { TaskContext, TaskStore, } from './types'; +import { readDuration } from './helper'; /** * TaskManager @@ -160,6 +162,7 @@ export class StorageTaskBroker implements TaskBroker { constructor( private readonly storage: TaskStore, private readonly logger: Logger, + private readonly config?: Config, ) {} async list(options?: { @@ -202,6 +205,30 @@ export class StorageTaskBroker implements TaskBroker { }); } + public async recoverTasks(): Promise { + const enabled = + (this.config && + this.config.getOptionalBoolean( + 'scaffolder.EXPERIMENTAL_recoverTasks', + )) ?? + false; + + if (enabled) { + const defaultTimeout = { seconds: 30 }; + const timeout = readDuration( + this.config, + 'scaffolder.EXPERIMENTAL_recoverTasksTimeout', + defaultTimeout, + ); + const { ids: recoveredTaskIds } = (await this.storage.recoverTasks?.({ + timeout, + })) ?? { ids: [] }; + if (recoveredTaskIds.length > 0) { + this.signalDispatch(); + } + } + } + /** * {@inheritdoc TaskBroker.claim} */ diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 9f9b86b723..cd7dc06a91 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -20,9 +20,10 @@ import { NunjucksWorkflowRunner } from './NunjucksWorkflowRunner'; import { Logger } from 'winston'; import { TemplateActionRegistry } from '../actions'; import { ScmIntegrations } from '@backstage/integration'; -import { assertError } from '@backstage/errors'; +import { assertError, stringifyError } from '@backstage/errors'; import { TemplateFilter, TemplateGlobal } from '../../lib'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; + /** * TaskWorkerOptions * @@ -35,6 +36,7 @@ export type TaskWorkerOptions = { }; concurrentTasksLimit: number; permissions?: PermissionEvaluator; + logger?: Logger; }; /** @@ -73,8 +75,12 @@ export type CreateWorkerOptions = { */ export class TaskWorker { private taskQueue: PQueue; + private logger: Logger | undefined; + private stopWorkers: boolean; private constructor(private readonly options: TaskWorkerOptions) { + this.stopWorkers = false; + this.logger = options.logger; this.taskQueue = new PQueue({ concurrency: options.concurrentTasksLimit, }); @@ -111,14 +117,34 @@ export class TaskWorker { }); } + async recoverTasks() { + try { + await this.options.taskBroker.recoverTasks?.(); + } catch (err) { + this.logger?.error(stringifyError(err)); + } + } + start() { (async () => { - for (;;) { - await this.onReadyToClaimTask(); - const task = await this.options.taskBroker.claim(); - this.taskQueue.add(() => this.runOneTask(task)); + while (!this.stopWorkers) { + await new Promise(resolve => setTimeout(resolve, 10000)); + await this.recoverTasks(); } })(); + (async () => { + while (!this.stopWorkers) { + await this.onReadyToClaimTask(); + if (!this.stopWorkers) { + const task = await this.options.taskBroker.claim(); + void this.taskQueue.add(() => this.runOneTask(task)); + } + } + })(); + } + + stop() { + this.stopWorkers = true; } protected onReadyToClaimTask(): Promise { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/dbUtil.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/dbUtil.test.ts new file mode 100644 index 0000000000..9ccc17b8a4 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/dbUtil.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2024 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 knexFactory, { Knex } from 'knex'; +import { intervalFromNowTill } from './dbUtil'; + +class KnexBuilder { + public build(client: string): Knex { + return knexFactory({ client, useNullAsDefault: true }); + } +} + +describe('util', () => { + describe('intervalFromNowTill', () => { + const databases = [ + { client: 'sqlite3', expected: "datetime('now', '-5 seconds')" }, + { client: 'mysql', expected: 'date_sub(now(), interval 5 second)' }, + { client: 'pg', expected: "CURRENT_TIMESTAMP - interval '5 seconds'" }, + ]; + + it.each(databases)('for client $client', ({ client, expected }) => { + const knex = new KnexBuilder().build(client); + const result = intervalFromNowTill(5, knex); + + expect(result.toString()).toBe(expected); + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/dbUtil.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/dbUtil.ts new file mode 100644 index 0000000000..afc4e8c0d6 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/dbUtil.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2024 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 { Knex } from 'knex'; + +export const intervalFromNowTill = (timeoutS: number, knex: Knex) => { + let heartbeatInterval = knex.raw(`? - interval '${timeoutS} seconds'`, [ + knex.fn.now(), + ]); + if (knex.client.config.client.includes('mysql')) { + heartbeatInterval = knex.raw( + `date_sub(now(), interval ${timeoutS} second)`, + ); + } else if (knex.client.config.client.includes('sqlite3')) { + heartbeatInterval = knex.raw(`datetime('now', ?)`, [ + `-${timeoutS} seconds`, + ]); + } + return heartbeatInterval; +}; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts index 76951afde4..41425affc9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts @@ -14,6 +14,9 @@ * limitations under the License. */ +import { Config, readDurationFromConfig } from '@backstage/config'; +import { HumanDuration } from '@backstage/types'; + import { isArray } from 'lodash'; import { Schema } from 'jsonschema'; @@ -53,3 +56,14 @@ export function generateExampleOutput(schema: Schema): unknown { } return ''; } + +export const readDuration = ( + config: Config | undefined, + key: string, + defaultValue: HumanDuration, +) => { + if (config?.has(key)) { + return readDurationFromConfig(config, { key }); + } + return defaultValue; +}; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts index f1c1a7bdac..9d231d7e7c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts @@ -35,5 +35,6 @@ export type { TaskBrokerDispatchResult, TaskBrokerDispatchOptions, TaskStoreCreateTaskOptions, + TaskStoreRecoverTaskOptions, TaskStoreCreateTaskResult, } from './types'; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/taskRecoveryHelper.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/taskRecoveryHelper.test.ts new file mode 100644 index 0000000000..8224feab8e --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/taskRecoveryHelper.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { trimEventsTillLastRecovery } from './taskRecoveryHelper'; +import { SerializedTaskEvent } from './types'; + +const toLogEvent = (stepId: string) => + ({ + type: 'log', + body: { stepId }, + } as unknown as SerializedTaskEvent); + +const toRecoveredEvent = (recoverStrategy: string) => + ({ + type: 'recovered', + body: { recoverStrategy }, + } as unknown as SerializedTaskEvent); + +describe('taskRecoveryHelper', () => { + describe('compactEvents', () => { + it('should return only events related to a restarted task. Recover strategy: "startOver"', () => { + const logEvents = [ + 'fetch', + 'mock-step-1', + 'mock-step-2', + 'mock-step-3', + ].map(toLogEvent); + + const events = [...logEvents, toRecoveredEvent('startOver')]; + + expect(trimEventsTillLastRecovery(events)).toEqual({ events: [] }); + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/taskRecoveryHelper.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/taskRecoveryHelper.ts new file mode 100644 index 0000000000..f3b8b321fe --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/taskRecoveryHelper.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SerializedTaskEvent } from '@backstage/plugin-scaffolder-node'; +import { TaskRecoverStrategy } from '@backstage/plugin-scaffolder-common'; + +export const trimEventsTillLastRecovery = ( + events: SerializedTaskEvent[], +): { events: SerializedTaskEvent[] } => { + const recoveredEventInd = events + .slice() + .reverse() + .findIndex(event => event.type === 'recovered'); + + if (recoveredEventInd >= 0) { + const ind = events.length - recoveredEventInd - 1; + const { recoverStrategy } = events[ind].body as { + recoverStrategy: TaskRecoverStrategy; + }; + if (recoverStrategy === 'startOver') { + return { + events: recoveredEventInd === 0 ? [] : events.slice(ind), + }; + } + } + + return { events }; +}; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 4560f27c0b..c5783ccb49 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { JsonValue, JsonObject } from '@backstage/types'; +import { JsonValue, JsonObject, HumanDuration } from '@backstage/types'; import { TaskSpec, TaskStep } from '@backstage/plugin-scaffolder-common'; import { TaskSecrets } from '@backstage/plugin-scaffolder-node'; import { @@ -142,6 +142,14 @@ export type TaskStoreCreateTaskOptions = { secrets?: TaskSecrets; }; +/** + * The options passed to {@link TaskStore.recoverTasks} + * @public + */ +export type TaskStoreRecoverTaskOptions = { + timeout: HumanDuration; +}; + /** * The response from {@link TaskStore.createTask} * @public @@ -162,6 +170,10 @@ export interface TaskStore { options: TaskStoreCreateTaskOptions, ): Promise; + recoverTasks?( + options: TaskStoreRecoverTaskOptions, + ): Promise<{ ids: string[] }>; + getTask(taskId: string): Promise; claimTask(): Promise; diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 7a0bcc6877..8bfbd6b915 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -85,6 +85,8 @@ const mockUrlReader = UrlReaders.default({ const getIdentity = jest.fn(); +const config = new ConfigReader({}); + describe('createRouter', () => { let app: express.Express; let loggerSpy: jest.SpyInstance; @@ -181,7 +183,7 @@ describe('createRouter', () => { const databaseTaskStore = await DatabaseTaskStore.create({ database: createDatabase(), }); - taskBroker = new StorageTaskBroker(databaseTaskStore, logger); + taskBroker = new StorageTaskBroker(databaseTaskStore, logger, config); jest.spyOn(taskBroker, 'dispatch'); jest.spyOn(taskBroker, 'get'); @@ -787,7 +789,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ const databaseTaskStore = await DatabaseTaskStore.create({ database: createDatabase(), }); - taskBroker = new StorageTaskBroker(databaseTaskStore, logger); + taskBroker = new StorageTaskBroker(databaseTaskStore, logger, config); jest.spyOn(taskBroker, 'dispatch'); jest.spyOn(taskBroker, 'get'); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index de0087e7de..c0322af283 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -78,6 +78,7 @@ import { } from '@backstage/plugin-permission-node'; import { scaffolderActionRules, scaffolderTemplateRules } from './rules'; import { Duration } from 'luxon'; +import { LifecycleService } from '@backstage/backend-plugin-api'; /** * @@ -124,6 +125,7 @@ export interface RouterOptions { logger: Logger; config: Config; reader: UrlReader; + lifecycle?: LifecycleService; database: PluginDatabaseManager; catalogClient: CatalogApi; scheduler?: PluginTaskScheduler; @@ -266,7 +268,7 @@ export async function createRouter( let taskBroker: TaskBroker; if (!options.taskBroker) { const databaseTaskStore = await DatabaseTaskStore.create({ database }); - taskBroker = new StorageTaskBroker(databaseTaskStore, logger); + taskBroker = new StorageTaskBroker(databaseTaskStore, logger, config); if (scheduler && databaseTaskStore.listStaleTasks) { await scheduler.scheduleTask({ @@ -301,7 +303,7 @@ export async function createRouter( const actionRegistry = new TemplateActionRegistry(); - const workers = []; + const workers: TaskWorker[] = []; if (concurrentTasksLimit !== 0) { for (let i = 0; i < (taskWorkers || 1); i++) { const worker = await TaskWorker.create({ @@ -331,7 +333,19 @@ export async function createRouter( }); actionsToRegister.forEach(action => actionRegistry.register(action)); - workers.forEach(worker => worker.start()); + + const launchWorkers = () => workers.forEach(worker => worker.start()); + + const shutdownWorkers = () => { + workers.forEach(worker => worker.stop()); + }; + + if (options.lifecycle) { + options.lifecycle.addStartupHook(launchWorkers); + options.lifecycle.addShutdownHook(shutdownWorkers); + } else { + launchWorkers(); + } const dryRunner = createDryRunner({ actionRegistry, @@ -462,6 +476,7 @@ export async function createRouter( id: step.id ?? `step-${index + 1}`, name: step.name ?? step.action, })), + EXPERIMENTAL_recovery: template.spec.EXPERIMENTAL_recovery, output: template.spec.output ?? {}, parameters: values, user: { diff --git a/plugins/scaffolder-backend/src/service/rules.test.ts b/plugins/scaffolder-backend/src/service/rules.test.ts index e6032dde15..8e24cacb32 100644 --- a/plugins/scaffolder-backend/src/service/rules.test.ts +++ b/plugins/scaffolder-backend/src/service/rules.test.ts @@ -23,6 +23,9 @@ import { hasStringProperty, hasTag, } from './rules'; +import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; +import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-common/alpha'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; describe('hasTag', () => { describe('apply', () => { @@ -206,6 +209,84 @@ describe('hasProperty', () => { ).toEqual(true); }, ); + + it('should throw if params are invalid', () => { + const isActionAuthorized = createConditionAuthorizer([hasProperty]); + + expect(() => + isActionAuthorized( + { + resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, + pluginId: 'scaffolder', + result: AuthorizeResult.CONDITIONAL, + conditions: { + resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, + rule: 'HAS_PROPERTY', + params: { + key: 1, + }, + }, + }, + { action: 'an-action', input: {} }, + ), + ).toThrow(); + expect(() => + isActionAuthorized( + { + resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, + pluginId: 'scaffolder', + result: AuthorizeResult.CONDITIONAL, + conditions: { + resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, + rule: 'HAS_PROPERTY', + params: {}, + }, + }, + { action: 'an-action', input: {} }, + ), + ).toThrow(); + }); + + it('should not throw if params are valid', () => { + const isActionAuthorized = createConditionAuthorizer([hasProperty]); + + expect(() => + isActionAuthorized( + { + resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, + pluginId: 'scaffolder', + result: AuthorizeResult.CONDITIONAL, + conditions: { + resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, + rule: 'HAS_PROPERTY', + params: { + key: 'key', + }, + }, + }, + { action: 'an-action', input: {} }, + ), + ).not.toThrow(); + + expect(() => + isActionAuthorized( + { + resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, + pluginId: 'scaffolder', + result: AuthorizeResult.CONDITIONAL, + conditions: { + resourceType: RESOURCE_TYPE_SCAFFOLDER_ACTION, + rule: 'HAS_PROPERTY', + params: { + key: 'key', + value: 'value', + }, + }, + }, + { action: 'an-action', input: {} }, + ), + ).not.toThrow(); + }); }); }); diff --git a/plugins/scaffolder-backend/src/service/rules.ts b/plugins/scaffolder-backend/src/service/rules.ts index 19b34b9f7c..b197810757 100644 --- a/plugins/scaffolder-backend/src/service/rules.ts +++ b/plugins/scaffolder-backend/src/service/rules.ts @@ -106,7 +106,9 @@ function buildHasProperty>({ key: z .string() .describe(`Property within the action parameters to match on`), - value: valueSchema.describe(`Value of the given property to match on`), + value: valueSchema + .optional() + .describe(`Value of the given property to match on`), }) as unknown as z.ZodType<{ key: string; value?: z.infer }>, apply: (resource, { key, value }) => { const foundValue = get(resource.input, key); diff --git a/plugins/scaffolder-backend/src/setupTests.ts b/plugins/scaffolder-backend/src/setupTests.ts new file mode 100644 index 0000000000..a494d80f22 --- /dev/null +++ b/plugins/scaffolder-backend/src/setupTests.ts @@ -0,0 +1,19 @@ +/* + * 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 { Settings } from 'luxon'; + +Settings.defaultLocale = 'en'; diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index ed63af5d36..1e25a2c66e 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-scaffolder-common +## 1.5.0-next.0 + +### Minor Changes + +- 11b9a08: Introduced the first version of recoverable tasks. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.12 + +## 1.4.5 + +### Patch Changes + +- 178b8d8: Updated Template.v1beta3.schema.json, added a missing "presentation" field +- Updated dependencies + - @backstage/plugin-permission-common@0.7.12 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + +## 1.4.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.11 + - @backstage/catalog-model@1.4.3 + - @backstage/types@1.1.1 + ## 1.4.3 ### Patch Changes diff --git a/plugins/scaffolder-common/alpha-api-report.md b/plugins/scaffolder-common/api-report-alpha.md similarity index 100% rename from plugins/scaffolder-common/alpha-api-report.md rename to plugins/scaffolder-common/api-report-alpha.md diff --git a/plugins/scaffolder-common/api-report.md b/plugins/scaffolder-common/api-report.md index 8cfbd2aaac..8d7a61910d 100644 --- a/plugins/scaffolder-common/api-report.md +++ b/plugins/scaffolder-common/api-report.md @@ -16,12 +16,21 @@ export const isTemplateEntityV1beta3: ( entity: Entity, ) => entity is TemplateEntityV1beta3; +// @public +export type TaskRecoverStrategy = 'none' | 'startOver'; + +// @public +export interface TaskRecovery { + EXPERIMENTAL_strategy?: TaskRecoverStrategy; +} + // @public export type TaskSpec = TaskSpecV1beta3; // @public export interface TaskSpecV1beta3 { apiVersion: 'scaffolder.backstage.io/v1beta3'; + EXPERIMENTAL_recovery?: TaskRecovery; output: { [name: string]: JsonValue; }; @@ -67,6 +76,7 @@ export interface TemplateEntityV1beta3 extends Entity { spec: { type: string; presentation?: TemplatePresentationV1beta3; + EXPERIMENTAL_recovery?: TemplateRecoveryV1beta3; parameters?: TemplateParametersV1beta3 | TemplateParametersV1beta3[]; steps: Array; output?: { @@ -108,4 +118,9 @@ export interface TemplatePresentationV1beta3 extends JsonObject { reviewButtonText?: string; }; } + +// @public +export interface TemplateRecoveryV1beta3 extends JsonObject { + EXPERIMENTAL_strategy?: 'none' | 'startOver'; +} ``` diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index fc189b3a48..29a2f9bb74 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-common", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", - "version": "1.4.3", + "version": "1.5.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-common/src/TaskSpec.ts b/plugins/scaffolder-common/src/TaskSpec.ts index 6b144b4f78..ce93c05ef0 100644 --- a/plugins/scaffolder-common/src/TaskSpec.ts +++ b/plugins/scaffolder-common/src/TaskSpec.ts @@ -44,6 +44,30 @@ export type TemplateInfo = { }; }; +/** + * + * none - not recover, let the task be marked as failed + * startOver - do recover, start the execution of the task from the first step. + * + * @public + */ +export type TaskRecoverStrategy = 'none' | 'startOver'; + +/** + * When task didn't have a chance to complete due to system restart you can define the strategy what to do with such tasks, + * by defining a strategy. + * + * By default, it is none, what means to not recover but updating the status from 'processing' to 'failed'. + * + * @public + */ +export interface TaskRecovery { + /** + * Depends on how you designed your task you might tailor the behaviour for each of them. + */ + EXPERIMENTAL_strategy?: TaskRecoverStrategy; +} + /** * An individual step of a scaffolder task, as stored in the database. * @@ -119,6 +143,10 @@ export interface TaskSpecV1beta3 { */ ref?: string; }; + /** + * How to recover the task after system restart or system crash. + */ + EXPERIMENTAL_recovery?: TaskRecovery; } /** diff --git a/plugins/scaffolder-common/src/Template.v1beta3.schema.json b/plugins/scaffolder-common/src/Template.v1beta3.schema.json index d4be9b7538..99b0e0b7cb 100644 --- a/plugins/scaffolder-common/src/Template.v1beta3.schema.json +++ b/plugins/scaffolder-common/src/Template.v1beta3.schema.json @@ -139,6 +139,34 @@ } ] }, + "presentation": { + "type": "object", + "description": "A way to redefine the labels for actionable buttons.", + "properties": { + "backButtonText": { + "type": "string", + "description": "A button which return the user to one step back." + }, + "createButtonText": { + "type": "string", + "description": "A button which start the execution of the template." + }, + "reviewButtonText": { + "type": "string", + "description": "A button which open the review step to verify the input prior to start the execution." + } + } + }, + "EXPERIMENTAL_recovery": { + "type": "object", + "description": "A task recovery section.", + "properties": { + "EXPERIMENTAL_strategy": { + "type": "string", + "description": "Recovery strategy for your task (none or startOver). By default none" + } + } + }, "steps": { "type": "array", "description": "A list of steps to execute.", diff --git a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts index 23cc3ca3d8..0e2fec4264 100644 --- a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts +++ b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts @@ -51,6 +51,11 @@ export interface TemplateEntityV1beta3 extends Entity { */ presentation?: TemplatePresentationV1beta3; + /** + * Recovery strategy for the template + */ + EXPERIMENTAL_recovery?: TemplateRecoveryV1beta3; + /** * This is a JSONSchema or an array of JSONSchema's which is used to render a form in the frontend * to collect user input and validate it against that schema. This can then be used in the `steps` part below to template @@ -73,6 +78,22 @@ export interface TemplateEntityV1beta3 extends Entity { }; } +/** + * Depends on how you designed your task you might tailor the behaviour for each of them. + * + * @public + */ +export interface TemplateRecoveryV1beta3 extends JsonObject { + /** + * + * none - not recover, let the task be marked as failed + * startOver - do recover, start the execution of the task from the first step. + * + * @public + */ + EXPERIMENTAL_strategy?: 'none' | 'startOver'; +} + /** * The presentation of the template. * diff --git a/plugins/scaffolder-common/src/index.ts b/plugins/scaffolder-common/src/index.ts index 3300aebf46..4d9b5e39c6 100644 --- a/plugins/scaffolder-common/src/index.ts +++ b/plugins/scaffolder-common/src/index.ts @@ -32,4 +32,5 @@ export type { TemplateEntityStepV1beta3, TemplateParametersV1beta3, TemplatePermissionsV1beta3, + TemplateRecoveryV1beta3, } from './TemplateEntityV1beta3'; diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index 0d2c194e06..6f47b13296 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,125 @@ # @backstage/plugin-scaffolder-node +## 0.3.0-next.0 + +### Minor Changes + +- 3a9ba42: Added functions to clone a repo, create a branch, add files and push and commit to the branch. This allows for files to be added to the a PR for use in the bitbucket pull request action for issue #21762 +- 11b9a08: Introduced the first version of recoverable tasks. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/backend-plugin-api@0.6.10-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1 + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/backend-plugin-api@0.6.9 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.9-next.2 + - @backstage/backend-common@0.20.1-next.2 + +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.9-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.1-next.0 + - @backstage/backend-plugin-api@0.6.9-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 0.2.9 + +### Patch Changes + +- 219d7f0: Refactor some methods to `-node` instead and use the new external modules +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/backend-common@0.20.0 + - @backstage/integration@1.8.0 + - @backstage/backend-plugin-api@0.6.8 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 0.2.9-next.3 + +### Patch Changes + +- 219d7f0: Refactor some methods to `-node` instead and use the new external modules +- Updated dependencies + - @backstage/backend-common@0.20.0-next.3 + - @backstage/backend-plugin-api@0.6.8-next.3 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.20.0-next.2 + - @backstage/backend-plugin-api@0.6.8-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.8.0-next.1 + - @backstage/backend-common@0.20.0-next.1 + - @backstage/backend-plugin-api@0.6.8-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.4.3 + ## 0.2.9-next.0 ### Patch Changes diff --git a/plugins/scaffolder-node/alpha-api-report.md b/plugins/scaffolder-node/api-report-alpha.md similarity index 100% rename from plugins/scaffolder-node/alpha-api-report.md rename to plugins/scaffolder-node/api-report-alpha.md diff --git a/plugins/scaffolder-node/api-report.md b/plugins/scaffolder-node/api-report.md index 08caf36812..6b1e6a54b6 100644 --- a/plugins/scaffolder-node/api-report.md +++ b/plugins/scaffolder-node/api-report.md @@ -10,6 +10,7 @@ import { JsonValue } from '@backstage/types'; import { Logger } from 'winston'; import { Observable } from '@backstage/types'; import { Schema } from 'jsonschema'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmIntegrations } from '@backstage/integration'; import { SpawnOptionsWithoutStdio } from 'child_process'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; @@ -44,6 +45,101 @@ export type ActionContext< each?: JsonObject; }; +// @public (undocumented) +export function addFiles(options: { + dir: string; + filepath: string; + auth: + | { + username: string; + password: string; + } + | { + token: string; + }; + logger?: Logger | undefined; +}): Promise; + +// @public (undocumented) +export function cloneRepo(options: { + url: string; + dir: string; + auth: + | { + username: string; + password: string; + } + | { + token: string; + }; + logger?: Logger | undefined; + ref?: string | undefined; + depth?: number | undefined; + noCheckout?: boolean | undefined; +}): Promise; + +// @public (undocumented) +export function commitAndPushBranch(options: { + dir: string; + auth: + | { + username: string; + password: string; + } + | { + token: string; + }; + logger?: Logger | undefined; + commitMessage: string; + gitAuthorInfo?: { + name?: string; + email?: string; + }; + branch?: string; + remoteRef?: string; + remote?: string; +}): Promise<{ + commitHash: string; +}>; + +// @public (undocumented) +export function commitAndPushRepo(input: { + dir: string; + auth: + | { + username: string; + password: string; + } + | { + token: string; + }; + logger: Logger; + commitMessage: string; + gitAuthorInfo?: { + name?: string; + email?: string; + }; + branch?: string; + remoteRef?: string; +}): Promise<{ + commitHash: string; +}>; + +// @public (undocumented) +export function createBranch(options: { + dir: string; + ref: string; + auth: + | { + username: string; + password: string; + } + | { + token: string; + }; + logger?: Logger | undefined; +}): Promise; + // @public export const createTemplateAction: < TInputParams extends JsonObject = JsonObject, @@ -73,6 +169,12 @@ export const createTemplateAction: < >, ) => TemplateAction; +// @public +export function deserializeDirectoryContents( + targetPath: string, + files: SerializedFile[], +): Promise; + // @public export function executeShellCommand( options: ExecuteShellCommandOptions, @@ -104,6 +206,69 @@ export function fetchFile(options: { outputPath: string; }): Promise; +// @public (undocumented) +export const getRepoSourceDirectory: ( + workspacePath: string, + sourcePath: string | undefined, +) => string; + +// @public (undocumented) +export function initRepoAndPush(input: { + dir: string; + remoteUrl: string; + auth: + | { + username: string; + password: string; + } + | { + token: string; + }; + logger: Logger; + defaultBranch?: string; + commitMessage?: string; + gitAuthorInfo?: { + name?: string; + email?: string; + }; +}): Promise<{ + commitHash: string; +}>; + +// @public (undocumented) +export const parseRepoUrl: ( + repoUrl: string, + integrations: ScmIntegrationRegistry, +) => { + repo: string; + host: string; + owner?: string | undefined; + organization?: string | undefined; + workspace?: string | undefined; + project?: string | undefined; +}; + +// @public (undocumented) +export interface SerializedFile { + // (undocumented) + content: Buffer; + // (undocumented) + executable?: boolean; + // (undocumented) + path: string; + // (undocumented) + symlink?: boolean; +} + +// @public (undocumented) +export function serializeDirectoryContents( + sourcePath: string, + options?: { + gitignore?: boolean; + globPatterns?: string[]; + }, +): Promise; + // @public export type SerializedTask = { id: string; @@ -145,6 +310,8 @@ export interface TaskBroker { tasks: SerializedTask[]; }>; // (undocumented) + recoverTasks?(): Promise; + // (undocumented) vacuumTasks(options: { timeoutS: number }): Promise; } @@ -186,7 +353,7 @@ export interface TaskContext { } // @public -export type TaskEventType = 'completion' | 'log' | 'cancelled'; +export type TaskEventType = 'completion' | 'log' | 'cancelled' | 'recovered'; // @public export type TaskSecrets = Record & { diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index f9506dcc96..e0e780131f 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-node", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", - "version": "0.2.9-next.0", + "version": "0.3.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -50,12 +50,15 @@ "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/types": "workspace:^", "fs-extra": "10.1.0", + "globby": "^11.0.0", "jsonschema": "^1.2.6", + "p-limit": "^3.1.0", "winston": "^3.2.1", - "zod": "^3.21.4", + "zod": "^3.22.4", "zod-to-json-schema": "^3.20.4" }, "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/config": "workspace:^" }, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.test.ts b/plugins/scaffolder-node/src/actions/gitHelpers.test.ts similarity index 50% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.test.ts rename to plugins/scaffolder-node/src/actions/gitHelpers.test.ts index 7362e99892..b230b2cd06 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.test.ts +++ b/plugins/scaffolder-node/src/actions/gitHelpers.test.ts @@ -15,7 +15,14 @@ */ import { Git, getVoidLogger } from '@backstage/backend-common'; -import { commitAndPushRepo, entityRefToName, initRepoAndPush } from './helpers'; +import { + commitAndPushRepo, + initRepoAndPush, + commitAndPushBranch, + addFiles, + createBranch, + cloneRepo, +} from './gitHelpers'; jest.mock('@backstage/backend-common', () => ({ Git: { @@ -29,10 +36,14 @@ jest.mock('@backstage/backend-common', () => ({ fetch: jest.fn(), addRemote: jest.fn(), push: jest.fn(), + clone: jest.fn(), }), }, getVoidLogger: jest.requireActual('@backstage/backend-common').getVoidLogger, })); +jest.mock('fs-extra', () => ({ + cpSync: jest.fn(), +})); const mockedGit = Git.fromAuth({ logger: getVoidLogger(), @@ -304,16 +315,332 @@ describe('commitAndPushRepo', () => { }); }); -describe('entityRefToName', () => { - it.each([ - 'user:default/catpants', - 'group:default/catpants', - 'user:catpants', - 'default/catpants', - 'user:custom/catpants', - 'group:custom/catpants', - 'catpants', - ])('should parse: "%s"', (entityName: string) => { - expect(entityRefToName(entityName)).toEqual('catpants'); +describe('cloneRepo', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('with minimal parameters', () => { + beforeEach(async () => { + await cloneRepo({ + url: 'git@github.com:test/repo.git', + dir: '/tmp/repo/dir/', + auth: { + username: 'test-user', + password: 'test-password', + }, + }); + }); + + it('clone the repo', () => { + expect(mockedGit.clone).toHaveBeenCalledWith({ + url: 'git@github.com:test/repo.git', + dir: '/tmp/repo/dir/', + ref: undefined, + depth: undefined, + noCheckout: undefined, + }); + }); + }); + + it('with token', async () => { + await cloneRepo({ + url: 'git@github.com:test/repo.git', + dir: '/tmp/repo/dir/', + auth: { + token: 'test-token', + }, + }); + + expect(mockedGit.clone).toHaveBeenCalledWith({ + dir: '/tmp/repo/dir/', + url: 'git@github.com:test/repo.git', + ref: undefined, + depth: undefined, + noCheckout: undefined, + }); + }); + + it('allows overriding the default branch', async () => { + await cloneRepo({ + url: 'git@github.com:test/repo.git', + dir: '/tmp/repo/dir/', + ref: 'trunk', + auth: { + username: 'test-user', + password: 'test-password', + }, + }); + + expect(mockedGit.clone).toHaveBeenCalledWith({ + dir: '/tmp/repo/dir/', + url: 'git@github.com:test/repo.git', + ref: 'trunk', + depth: undefined, + noCheckout: undefined, + }); + }); + + it('allows overriding the depth', async () => { + await cloneRepo({ + url: 'git@github.com:test/repo.git', + dir: '/tmp/repo/dir/', + ref: 'trunk', + depth: 2, + auth: { + username: 'test-user', + password: 'test-password', + }, + }); + + expect(mockedGit.clone).toHaveBeenCalledWith({ + dir: '/tmp/repo/dir/', + url: 'git@github.com:test/repo.git', + ref: 'trunk', + depth: 2, + noCheckout: undefined, + }); + }); + + it('allows overriding the noCheckout', async () => { + await cloneRepo({ + url: 'git@github.com:test/repo.git', + dir: '/tmp/repo/dir/', + ref: 'trunk', + depth: 2, + noCheckout: true, + auth: { + username: 'test-user', + password: 'test-password', + }, + }); + + expect(mockedGit.clone).toHaveBeenCalledWith({ + dir: '/tmp/repo/dir/', + url: 'git@github.com:test/repo.git', + ref: 'trunk', + depth: 2, + noCheckout: true, + }); + }); +}); + +describe('createBranch', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('with minimal parameters', () => { + beforeEach(async () => { + await createBranch({ + dir: '/tmp/repo/dir/', + ref: 'trunk', + auth: { + username: 'test-user', + password: 'test-password', + }, + }); + }); + + it('create the branch', () => { + expect(mockedGit.checkout).toHaveBeenCalledWith({ + ref: 'trunk', + dir: '/tmp/repo/dir/', + }); + }); + }); + + it('with token', async () => { + await createBranch({ + dir: '/tmp/repo/dir/', + ref: 'trunk', + auth: { + token: 'test-token', + }, + }); + + expect(mockedGit.checkout).toHaveBeenCalledWith({ + ref: 'trunk', + dir: '/tmp/repo/dir/', + }); + }); +}); + +describe('addFiles', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('with minimal parameters', () => { + beforeEach(async () => { + await addFiles({ + dir: '/tmp/repo/dir/', + filepath: '.', + auth: { + username: 'test-user', + password: 'test-password', + }, + }); + }); + + it('add files', () => { + expect(mockedGit.add).toHaveBeenCalledWith({ + filepath: '.', + dir: '/tmp/repo/dir/', + }); + }); + }); + + it('with token', async () => { + await addFiles({ + dir: '/tmp/repo/dir/', + filepath: '.', + auth: { + token: 'test-token', + }, + }); + + expect(mockedGit.add).toHaveBeenCalledWith({ + filepath: '.', + dir: '/tmp/repo/dir/', + }); + }); +}); + +describe('commitAndPushBranch', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('with minimal parameters', () => { + beforeEach(async () => { + await commitAndPushBranch({ + dir: '/tmp/repo/dir/', + auth: { + username: 'test-user', + password: 'test-password', + }, + commitMessage: 'commit message', + }); + }); + + it('create commit', () => { + expect(mockedGit.commit).toHaveBeenCalledWith({ + message: 'commit message', + dir: '/tmp/repo/dir/', + author: { + name: 'Scaffolder', + email: 'scaffolder@backstage.io', + }, + committer: { + name: 'Scaffolder', + email: 'scaffolder@backstage.io', + }, + }); + }); + + it('pushes to the remote', () => { + expect(mockedGit.push).toHaveBeenCalledWith({ + dir: '/tmp/repo/dir/', + remote: 'origin', + remoteRef: 'refs/heads/master', + }); + }); + }); + + it('with token', async () => { + await commitAndPushBranch({ + dir: '/tmp/repo/dir/', + auth: { + token: 'test-token', + }, + commitMessage: 'commit message', + gitAuthorInfo: { + name: 'gitCommitter', + email: 'gitCommitter@backstage.io', + }, + logger: getVoidLogger(), + }); + + expect(mockedGit.commit).toHaveBeenCalledWith({ + message: 'commit message', + dir: '/tmp/repo/dir/', + author: { + name: 'gitCommitter', + email: 'gitCommitter@backstage.io', + }, + committer: { + name: 'gitCommitter', + email: 'gitCommitter@backstage.io', + }, + }); + + expect(mockedGit.push).toHaveBeenCalledWith({ + dir: '/tmp/repo/dir/', + remote: 'origin', + remoteRef: 'refs/heads/master', + }); + }); + + it('allows overriding the default branch', async () => { + await commitAndPushBranch({ + dir: '/tmp/repo/dir/', + auth: { + username: 'test-user', + password: 'test-password', + }, + commitMessage: 'commit message', + branch: 'trunk', + }); + + expect(mockedGit.commit).toHaveBeenCalledWith({ + message: 'commit message', + dir: '/tmp/repo/dir/', + author: { + name: 'Scaffolder', + email: 'scaffolder@backstage.io', + }, + committer: { + name: 'Scaffolder', + email: 'scaffolder@backstage.io', + }, + }); + + expect(mockedGit.push).toHaveBeenCalledWith({ + dir: '/tmp/repo/dir/', + remote: 'origin', + remoteRef: 'refs/heads/trunk', + }); + }); + + it('allows overriding the remoteRef', async () => { + await commitAndPushBranch({ + dir: '/tmp/repo/dir/', + auth: { + username: 'test-user', + password: 'test-password', + }, + commitMessage: 'commit message', + remoteRef: 'ABC123', + }); + + expect(mockedGit.commit).toHaveBeenCalledWith({ + message: 'commit message', + dir: '/tmp/repo/dir/', + author: { + name: 'Scaffolder', + email: 'scaffolder@backstage.io', + }, + committer: { + name: 'Scaffolder', + email: 'scaffolder@backstage.io', + }, + }); + + expect(mockedGit.push).toHaveBeenCalledWith({ + dir: '/tmp/repo/dir/', + remote: 'origin', + remoteRef: 'ABC123', + }); }); }); diff --git a/plugins/scaffolder-node/src/actions/gitHelpers.ts b/plugins/scaffolder-node/src/actions/gitHelpers.ts new file mode 100644 index 0000000000..2026e75247 --- /dev/null +++ b/plugins/scaffolder-node/src/actions/gitHelpers.ts @@ -0,0 +1,256 @@ +/* + * 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 { Git } from '@backstage/backend-common'; +import { Logger } from 'winston'; + +/** + * @public + */ +export async function initRepoAndPush(input: { + dir: string; + remoteUrl: string; + // For use cases where token has to be used with Basic Auth + // it has to be provided as password together with a username + // which may be a fixed value defined by the provider. + auth: { username: string; password: string } | { token: string }; + logger: Logger; + defaultBranch?: string; + commitMessage?: string; + gitAuthorInfo?: { name?: string; email?: string }; +}): Promise<{ commitHash: string }> { + const { + dir, + remoteUrl, + auth, + logger, + defaultBranch = 'master', + commitMessage = 'Initial commit', + gitAuthorInfo, + } = input; + const git = Git.fromAuth({ + ...auth, + logger, + }); + + await git.init({ + dir, + defaultBranch, + }); + + await git.add({ dir, filepath: '.' }); + + // use provided info if possible, otherwise use fallbacks + const authorInfo = { + name: gitAuthorInfo?.name ?? 'Scaffolder', + email: gitAuthorInfo?.email ?? 'scaffolder@backstage.io', + }; + + const commitHash = await git.commit({ + dir, + message: commitMessage, + author: authorInfo, + committer: authorInfo, + }); + await git.addRemote({ + dir, + url: remoteUrl, + remote: 'origin', + }); + + await git.push({ + dir, + remote: 'origin', + }); + + return { commitHash }; +} + +/** + * @public + */ +export async function commitAndPushRepo(input: { + dir: string; + // For use cases where token has to be used with Basic Auth + // it has to be provided as password together with a username + // which may be a fixed value defined by the provider. + auth: { username: string; password: string } | { token: string }; + logger: Logger; + commitMessage: string; + gitAuthorInfo?: { name?: string; email?: string }; + branch?: string; + remoteRef?: string; +}): Promise<{ commitHash: string }> { + const { + dir, + auth, + logger, + commitMessage, + gitAuthorInfo, + branch = 'master', + remoteRef, + } = input; + + const git = Git.fromAuth({ + ...auth, + logger, + }); + + await git.fetch({ dir }); + await git.checkout({ dir, ref: branch }); + await git.add({ dir, filepath: '.' }); + + // use provided info if possible, otherwise use fallbacks + const authorInfo = { + name: gitAuthorInfo?.name ?? 'Scaffolder', + email: gitAuthorInfo?.email ?? 'scaffolder@backstage.io', + }; + + const commitHash = await git.commit({ + dir, + message: commitMessage, + author: authorInfo, + committer: authorInfo, + }); + + await git.push({ + dir, + remote: 'origin', + remoteRef: remoteRef ?? `refs/heads/${branch}`, + }); + + return { commitHash }; +} + +/** + * @public + */ +export async function cloneRepo(options: { + url: string; + dir: string; + // For use cases where token has to be used with Basic Auth + // it has to be provided as password together with a username + // which may be a fixed value defined by the provider. + auth: { username: string; password: string } | { token: string }; + logger?: Logger | undefined; + ref?: string | undefined; + depth?: number | undefined; + noCheckout?: boolean | undefined; +}): Promise { + const { url, dir, auth, logger, ref, depth, noCheckout } = options; + + const git = Git.fromAuth({ + ...auth, + logger, + }); + + await git.clone({ url, dir, ref, depth, noCheckout }); +} + +/** + * @public + */ +export async function createBranch(options: { + dir: string; + ref: string; + // For use cases where token has to be used with Basic Auth + // it has to be provided as password together with a username + // which may be a fixed value defined by the provider. + auth: { username: string; password: string } | { token: string }; + logger?: Logger | undefined; +}): Promise { + const { dir, ref, auth, logger } = options; + const git = Git.fromAuth({ + ...auth, + logger, + }); + + await git.checkout({ dir, ref }); +} + +/** + * @public + */ +export async function addFiles(options: { + dir: string; + filepath: string; + // For use cases where token has to be used with Basic Auth + // it has to be provided as password together with a username + // which may be a fixed value defined by the provider. + auth: { username: string; password: string } | { token: string }; + logger?: Logger | undefined; +}): Promise { + const { dir, filepath, auth, logger } = options; + const git = Git.fromAuth({ + ...auth, + logger, + }); + + await git.add({ dir, filepath }); +} + +/** + * @public + */ +export async function commitAndPushBranch(options: { + dir: string; + // For use cases where token has to be used with Basic Auth + // it has to be provided as password together with a username + // which may be a fixed value defined by the provider. + auth: { username: string; password: string } | { token: string }; + logger?: Logger | undefined; + commitMessage: string; + gitAuthorInfo?: { name?: string; email?: string }; + branch?: string; + remoteRef?: string; + remote?: string; +}): Promise<{ commitHash: string }> { + const { + dir, + auth, + logger, + commitMessage, + gitAuthorInfo, + branch = 'master', + remoteRef, + remote = 'origin', + } = options; + const git = Git.fromAuth({ + ...auth, + logger, + }); + + // use provided info if possible, otherwise use fallbacks + const authorInfo = { + name: gitAuthorInfo?.name ?? 'Scaffolder', + email: gitAuthorInfo?.email ?? 'scaffolder@backstage.io', + }; + + const commitHash = await git.commit({ + dir, + message: commitMessage, + author: authorInfo, + committer: authorInfo, + }); + + await git.push({ + dir, + remote, + remoteRef: remoteRef ?? `refs/heads/${branch}`, + }); + + return { commitHash }; +} diff --git a/plugins/scaffolder-node/src/actions/index.ts b/plugins/scaffolder-node/src/actions/index.ts index 47af320ccb..c1c8551c53 100644 --- a/plugins/scaffolder-node/src/actions/index.ts +++ b/plugins/scaffolder-node/src/actions/index.ts @@ -25,3 +25,12 @@ export { } from './executeShellCommand'; export { fetchContents, fetchFile } from './fetch'; export { type ActionContext, type TemplateAction } from './types'; +export { + initRepoAndPush, + commitAndPushRepo, + commitAndPushBranch, + addFiles, + createBranch, + cloneRepo, +} from './gitHelpers'; +export { parseRepoUrl, getRepoSourceDirectory } from './util'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts b/plugins/scaffolder-node/src/actions/util.ts similarity index 86% rename from plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts rename to plugins/scaffolder-node/src/actions/util.ts index 61bd704a0b..327cfa9c2f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts +++ b/plugins/scaffolder-node/src/actions/util.ts @@ -19,6 +19,9 @@ import { isChildPath } from '@backstage/backend-common'; import { join as joinPath, normalize as normalizePath } from 'path'; import { ScmIntegrationRegistry } from '@backstage/integration'; +/** + * @public + */ export const getRepoSourceDirectory = ( workspacePath: string, sourcePath: string | undefined, @@ -36,19 +39,21 @@ export const getRepoSourceDirectory = ( } return workspacePath; }; -export type RepoSpec = { + +/** + * @public + */ +export const parseRepoUrl = ( + repoUrl: string, + integrations: ScmIntegrationRegistry, +): { repo: string; host: string; owner?: string; organization?: string; workspace?: string; project?: string; -}; - -export const parseRepoUrl = ( - repoUrl: string, - integrations: ScmIntegrationRegistry, -): RepoSpec => { +} => { let parsed; try { parsed = new URL(`https://${repoUrl}`); @@ -99,23 +104,7 @@ export const parseRepoUrl = ( return { host, owner, repo, organization, workspace, project }; }; -export const isExecutable = (fileMode: number) => { - const executeBitMask = 0o000111; - const res = fileMode & executeBitMask; - return res > 0; -}; -/** - * This function checks if the required parameters (given as the `params` argument) are present in the URL - * - * @param repoUrl - the URL for the repository - * @param params - a variadic list of URL query parameter names - * - * @throws - * An InputError exception is thrown if any of the required parameters is not in the URL. - * - * @public - */ function checkRequiredParams(repoUrl: URL, ...params: string[]) { for (let i = 0; i < params.length; i++) { if (!repoUrl.searchParams.get(params[i])) { diff --git a/plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.test.ts b/plugins/scaffolder-node/src/files/deserializeDirectoryContents.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.test.ts rename to plugins/scaffolder-node/src/files/deserializeDirectoryContents.test.ts diff --git a/plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.ts b/plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts similarity index 99% rename from plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.ts rename to plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts index cb567eed9d..bdcecd99f1 100644 --- a/plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.ts +++ b/plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts @@ -25,7 +25,7 @@ import { SerializedFile } from './types'; * This method uses `resolveSafeChildPath` to make sure that files are * not written outside of the target directory. * - * @internal + * @public */ export async function deserializeDirectoryContents( targetPath: string, diff --git a/plugins/scaffolder-backend/src/lib/files/index.ts b/plugins/scaffolder-node/src/files/index.ts similarity index 100% rename from plugins/scaffolder-backend/src/lib/files/index.ts rename to plugins/scaffolder-node/src/files/index.ts diff --git a/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts b/plugins/scaffolder-node/src/files/serializeDirectoryContents.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts rename to plugins/scaffolder-node/src/files/serializeDirectoryContents.test.ts diff --git a/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.ts b/plugins/scaffolder-node/src/files/serializeDirectoryContents.ts similarity index 99% rename from plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.ts rename to plugins/scaffolder-node/src/files/serializeDirectoryContents.ts index 1197db000a..7e52f6323b 100644 --- a/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.ts +++ b/plugins/scaffolder-node/src/files/serializeDirectoryContents.ts @@ -41,6 +41,9 @@ async function asyncFilter( return array.filter((_value, index) => filterMap[index]); } +/** + * @public + */ export async function serializeDirectoryContents( sourcePath: string, options?: { diff --git a/plugins/scaffolder-backend/src/lib/files/types.ts b/plugins/scaffolder-node/src/files/types.ts similarity index 97% rename from plugins/scaffolder-backend/src/lib/files/types.ts rename to plugins/scaffolder-node/src/files/types.ts index d3a1eaa008..cd471a7881 100644 --- a/plugins/scaffolder-backend/src/lib/files/types.ts +++ b/plugins/scaffolder-node/src/files/types.ts @@ -14,6 +14,9 @@ * limitations under the License. */ +/** + * @public + */ export interface SerializedFile { path: string; content: Buffer; diff --git a/plugins/scaffolder-node/src/index.ts b/plugins/scaffolder-node/src/index.ts index 98691c2afa..0ec492dd32 100644 --- a/plugins/scaffolder-node/src/index.ts +++ b/plugins/scaffolder-node/src/index.ts @@ -22,4 +22,5 @@ export * from './actions'; export * from './tasks'; +export * from './files'; export type { TemplateFilter, TemplateGlobal } from './types'; diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index 8c9cc29e31..7cdc044bdf 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -65,7 +65,7 @@ export type SerializedTask = { * * @public */ -export type TaskEventType = 'completion' | 'log' | 'cancelled'; +export type TaskEventType = 'completion' | 'log' | 'cancelled' | 'recovered'; /** * SerializedTaskEvent @@ -131,6 +131,8 @@ export interface TaskBroker { claim(): Promise; + recoverTasks?(): Promise; + dispatch( options: TaskBrokerDispatchOptions, ): Promise; diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index a66eb35b63..ba40bc17fb 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,202 @@ # @backstage/plugin-scaffolder-react +## 1.8.0-next.0 + +### Minor Changes + +- c56f1a2: Remove the old legacy exports from `/alpha` +- 11b9a08: Introduced the first version of recoverable tasks. + +### Patch Changes + +- 0b0c6b6: Allow defining default output text to be shown +- 6a74ffd: Updated dependency `@rjsf/utils` to `5.16.1`. + Updated dependency `@rjsf/core` to `5.16.1`. + Updated dependency `@rjsf/material-ui` to `5.16.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.7.1 + +### Patch Changes + +- c28f281: Scaffolder form now shows a list of errors at the top of the form. +- 0b9ce2b: Fix for a step with no properties +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- 4016f21: Remove some unused dependencies +- d16f85f: Show first scaffolder output text by default +- Updated dependencies + - @backstage/core-components@0.13.10 + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + +## 1.7.1-next.2 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.9.3-next.2 + +## 1.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.7.1-next.0 + +### Patch Changes + +- c28f281: Scaffolder form now shows a list of errors at the top of the form. +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/theme@0.5.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.7.0 + +### Minor Changes + +- 33edf50: Added support for dealing with user provided secrets using a new field extension `ui:field: Secret` + +### Patch Changes + +- 670c7cc: Fix bug where `properties` is set to empty object when it should be empty for schema dependencies +- fa66d1b: Fixed bug in `ReviewState` where `enum` value was displayed in step review instead of the corresponding label when using `enumNames` +- e516bf4: Step titles in the Stepper are now clickable and redirect the user to the corresponding step, as an alternative to using the back buttons. +- aaa6fb3: Minor updates for TypeScript 5.2.2+ compatibility +- 2aee53b: Add horizontal slider if stepper overflows +- 2b72591: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- 6cd12f2: Updated dependency `@rjsf/utils` to `5.14.1`. + Updated dependency `@rjsf/core` to `5.14.1`. + Updated dependency `@rjsf/material-ui` to `5.14.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- 63c494e: Updated dependency `@rjsf/utils` to `5.14.2`. + Updated dependency `@rjsf/core` to `5.14.2`. + Updated dependency `@rjsf/material-ui` to `5.14.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. +- c8908d4: Use new option from RJSF 5.15 +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- 5bb5240: Fixed issue for showing undefined for hidden form items +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/catalog-client@1.5.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.6.2-next.3 + +### Patch Changes + +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- c8908d4: Use new option from RJSF 5.15 +- Updated dependencies + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.6.2-next.2 + +### Patch Changes + +- 5bb5240: Fixed issue for showing undefined for hidden form items +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.6.2-next.1 + +### Patch Changes + +- fa66d1b5b3: Fixed bug in `ReviewState` where `enum` value was displayed in step review instead of the corresponding label when using `enumNames` +- 2aee53bbeb: Add horizontal slider if stepper overflows +- 2b725913c1: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.7 + - @backstage/plugin-scaffolder-common@1.4.3 + ## 1.6.2-next.0 ### Patch Changes diff --git a/plugins/scaffolder-react/alpha-api-report.md b/plugins/scaffolder-react/api-report-alpha.md similarity index 85% rename from plugins/scaffolder-react/alpha-api-report.md rename to plugins/scaffolder-react/api-report-alpha.md index 615b54df84..6976951670 100644 --- a/plugins/scaffolder-react/alpha-api-report.md +++ b/plugins/scaffolder-react/api-report-alpha.md @@ -7,11 +7,8 @@ import { ApiHolder } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; -import { CustomFieldExtensionSchema } from '@backstage/plugin-scaffolder-react'; import { CustomFieldValidator } from '@backstage/plugin-scaffolder-react'; import { Dispatch } from 'react'; -import { Extension } from '@backstage/core-plugin-api'; -import { FieldExtensionComponent } from '@backstage/plugin-scaffolder-react'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; import { FieldValidation } from '@rjsf/utils'; import { FormProps } from '@backstage/plugin-scaffolder-react'; @@ -24,7 +21,6 @@ import { default as React_2 } from 'react'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { ReviewStepProps } from '@backstage/plugin-scaffolder-react'; -import { ScaffolderRJSFFieldProps } from '@backstage/plugin-scaffolder-react'; import { ScaffolderRJSFFormProps } from '@backstage/plugin-scaffolder-react'; import { ScaffolderStep } from '@backstage/plugin-scaffolder-react'; import { ScaffolderTaskOutput } from '@backstage/plugin-scaffolder-react'; @@ -51,14 +47,6 @@ export const createAsyncValidators: ( // @alpha export const createFieldValidation: () => FieldValidation; -// @alpha -export function createLegacyScaffolderFieldExtension< - TReturnValue = unknown, - TInputProps = unknown, ->( - options: LegacyFieldExtensionOptions, -): Extension>; - // @alpha export const DefaultTemplateOutputs: (props: { output?: ScaffolderTaskOutput; @@ -83,39 +71,6 @@ export type FormValidation = { [name: string]: FieldValidation | FormValidation; }; -// @alpha -export type LegacyCustomFieldValidator = ( - data: TFieldReturnValue, - field: FieldValidation, - context: { - apiHolder: ApiHolder; - }, -) => void | Promise; - -// @alpha -export interface LegacyFieldExtensionComponentProps< - TFieldReturnValue, - TUiOptions = unknown, -> extends ScaffolderRJSFFieldProps { - // (undocumented) - uiSchema: ScaffolderRJSFFieldProps['uiSchema'] & { - 'ui:options'?: TUiOptions; - }; -} - -// @alpha -export type LegacyFieldExtensionOptions< - TFieldReturnValue = unknown, - TInputProps = unknown, -> = { - name: string; - component: ( - props: LegacyFieldExtensionComponentProps, - ) => JSX.Element | null; - validation?: LegacyCustomFieldValidator; - schema?: CustomFieldExtensionSchema; -}; - // @alpha export interface ParsedTemplateSchema { // (undocumented) diff --git a/plugins/scaffolder-react/api-report.md b/plugins/scaffolder-react/api-report.md index 8589078f1e..400cf24c53 100644 --- a/plugins/scaffolder-react/api-report.md +++ b/plugins/scaffolder-react/api-report.md @@ -152,7 +152,7 @@ export type ListActionsResponse = Array; // @public export type LogEvent = { - type: 'log' | 'completion' | 'cancelled'; + type: 'log' | 'completion' | 'cancelled' | 'recovered'; body: { message: string; stepId?: string; @@ -277,6 +277,7 @@ export type ScaffolderOutputText = { title?: string; icon?: string; content?: string; + default?: boolean; }; // @public diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 22cf4ba5a2..bb038cd2ee 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-react", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", - "version": "1.6.2-next.0", + "version": "1.8.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -50,7 +50,6 @@ "@backstage/catalog-model": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", - "@backstage/errors": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/theme": "workspace:^", @@ -59,11 +58,11 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "@react-hookz/web": "^20.0.0", - "@rjsf/core": "5.14.2", - "@rjsf/material-ui": "5.14.2", - "@rjsf/utils": "5.14.2", - "@rjsf/validator-ajv8": "5.14.2", + "@react-hookz/web": "^23.0.0", + "@rjsf/core": "5.16.1", + "@rjsf/material-ui": "5.16.1", + "@rjsf/utils": "5.16.1", + "@rjsf/validator-ajv8": "5.16.1", "@types/json-schema": "^7.0.9", "@types/react": "^16.13.1 || ^17.0.0", "classnames": "^2.2.6", @@ -77,7 +76,7 @@ "react-use": "^17.2.4", "use-immer": "^0.9.0", "zen-observable": "^0.10.0", - "zod": "^3.21.4", + "zod": "^3.22.4", "zod-to-json-schema": "^3.20.4" }, "peerDependencies": { diff --git a/plugins/scaffolder-react/src/alpha.ts b/plugins/scaffolder-react/src/alpha.ts index 224ee97c06..36c3fcf264 100644 --- a/plugins/scaffolder-react/src/alpha.ts +++ b/plugins/scaffolder-react/src/alpha.ts @@ -15,4 +15,3 @@ */ export * from './next'; -export * from './legacy'; diff --git a/plugins/scaffolder-react/src/api/types.ts b/plugins/scaffolder-react/src/api/types.ts index 2adecdd0d2..b4a3deea54 100644 --- a/plugins/scaffolder-react/src/api/types.ts +++ b/plugins/scaffolder-react/src/api/types.ts @@ -89,6 +89,7 @@ export type ScaffolderOutputText = { title?: string; icon?: string; content?: string; + default?: boolean; }; /** @public */ @@ -105,7 +106,7 @@ export type ScaffolderTaskOutput = { * @public */ export type LogEvent = { - type: 'log' | 'completion' | 'cancelled'; + type: 'log' | 'completion' | 'cancelled' | 'recovered'; body: { message: string; stepId?: string; diff --git a/plugins/scaffolder-react/src/hooks/useEventStream.ts b/plugins/scaffolder-react/src/hooks/useEventStream.ts index ad802dba99..f63f6024e6 100644 --- a/plugins/scaffolder-react/src/hooks/useEventStream.ts +++ b/plugins/scaffolder-react/src/hooks/useEventStream.ts @@ -62,12 +62,14 @@ type ReducerLogEntry = { message: string; output?: ScaffolderTaskOutput; error?: Error; + recoverStrategy?: 'none' | 'startOver'; }; }; type ReducerAction = | { type: 'INIT'; data: ScaffolderTask } | { type: 'CANCELLED' } + | { type: 'RECOVERED'; data: ReducerLogEntry } | { type: 'LOGS'; data: ReducerLogEntry[] } | { type: 'COMPLETED'; data: ReducerLogEntry } | { type: 'ERROR'; data: Error }; @@ -105,17 +107,19 @@ function reducer(draft: TaskStream, action: ReducerAction) { const currentStepLog = draft.stepLogs?.[entry.body.stepId]; const currentStep = draft.steps?.[entry.body.stepId]; - if (entry.body.status && entry.body.status !== currentStep.status) { - currentStep.status = entry.body.status; + if (currentStep) { + if (entry.body.status && entry.body.status !== currentStep.status) { + currentStep.status = entry.body.status; - if (currentStep.status === 'processing') { - currentStep.startedAt = entry.createdAt; - } + if (currentStep.status === 'processing') { + currentStep.startedAt = entry.createdAt; + } - if ( - ['cancelled', 'completed', 'failed'].includes(currentStep.status) - ) { - currentStep.endedAt = entry.createdAt; + if ( + ['cancelled', 'completed', 'failed'].includes(currentStep.status) + ) { + currentStep.endedAt = entry.createdAt; + } } } @@ -138,6 +142,17 @@ function reducer(draft: TaskStream, action: ReducerAction) { return; } + case 'RECOVERED': { + for (const stepId in draft.steps) { + if (draft.steps.hasOwnProperty(stepId)) { + draft.steps[stepId].startedAt = undefined; + draft.steps[stepId].endedAt = undefined; + draft.steps[stepId].status = 'open'; + } + } + return; + } + case 'ERROR': { draft.error = action.data; draft.loading = false; @@ -202,6 +217,7 @@ export const useTaskEventStream = (taskId: string): TaskStream => { subscription = observable.subscribe({ next: event => { + retryCount = 1; switch (event.type) { case 'log': return collectedLogEvents.push(event); @@ -212,6 +228,9 @@ export const useTaskEventStream = (taskId: string): TaskStream => { emitLogs(); dispatch({ type: 'COMPLETED', data: event }); return undefined; + case 'recovered': + dispatch({ type: 'RECOVERED', data: event }); + return undefined; default: throw new Error( `Unhandled event type ${event.type} in observer`, @@ -226,16 +245,18 @@ export const useTaskEventStream = (taskId: string): TaskStream => { // just to restart the fetch process // details here https://github.com/backstage/backstage/issues/15002 + const maxRetries = 3; + if (!error.message) { - error.message = `We cannot connect at the moment, trying again in some seconds... Retrying (${retryCount}/3 retries)`; + error.message = `We cannot connect at the moment, trying again in some seconds... Retrying (${ + retryCount > maxRetries ? maxRetries : retryCount + }/${maxRetries} retries)`; } - if (retryCount <= 3) { - setTimeout(() => { - retryCount += 1; - startStreamLogProcess(); - }, 15000); - } + setTimeout(() => { + retryCount += 1; + void startStreamLogProcess(); + }, 15000); dispatch({ type: 'ERROR', data: error }); }, @@ -247,7 +268,7 @@ export const useTaskEventStream = (taskId: string): TaskStream => { } }, ); - startStreamLogProcess(); + void startStreamLogProcess(); return () => { didCancel = true; if (subscription) { diff --git a/plugins/scaffolder-react/src/legacy/extensions/index.tsx b/plugins/scaffolder-react/src/legacy/extensions/index.tsx deleted file mode 100644 index 870d797fc4..0000000000 --- a/plugins/scaffolder-react/src/legacy/extensions/index.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - LegacyCustomFieldValidator, - LegacyFieldExtensionOptions, - LegacyFieldExtensionComponentProps, -} from './types'; -import { Extension, attachComponentData } from '@backstage/core-plugin-api'; -import { FIELD_EXTENSION_KEY } from '../../extensions/keys'; -import { FieldExtensionComponent } from '@backstage/plugin-scaffolder-react'; - -/** - * Method for creating field extensions that can be used in the scaffolder - * frontend form. - * @alpha - */ -export function createLegacyScaffolderFieldExtension< - TReturnValue = unknown, - TInputProps = unknown, ->( - options: LegacyFieldExtensionOptions, -): Extension> { - return { - expose() { - const FieldExtensionDataHolder: any = () => null; - - attachComponentData( - FieldExtensionDataHolder, - FIELD_EXTENSION_KEY, - options, - ); - - return FieldExtensionDataHolder; - }, - }; -} - -export type { - LegacyCustomFieldValidator, - LegacyFieldExtensionOptions, - LegacyFieldExtensionComponentProps, -}; diff --git a/plugins/scaffolder-react/src/legacy/extensions/types.ts b/plugins/scaffolder-react/src/legacy/extensions/types.ts deleted file mode 100644 index 54c38ad2cd..0000000000 --- a/plugins/scaffolder-react/src/legacy/extensions/types.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { ApiHolder } from '@backstage/core-plugin-api'; -import { FieldValidation } from '@rjsf/utils'; -import { - CustomFieldExtensionSchema, - ScaffolderRJSFFieldProps, -} from '@backstage/plugin-scaffolder-react'; - -/** - * Field validation type for Custom Field Extensions. - * - * @alpha - */ -export type LegacyCustomFieldValidator = ( - data: TFieldReturnValue, - field: FieldValidation, - context: { apiHolder: ApiHolder }, -) => void | Promise; - -/** - * Type for the Custom Field Extension with the - * name and components and validation function. - * - * @alpha - */ -export type LegacyFieldExtensionOptions< - TFieldReturnValue = unknown, - TInputProps = unknown, -> = { - name: string; - component: ( - props: LegacyFieldExtensionComponentProps, - ) => JSX.Element | null; - validation?: LegacyCustomFieldValidator; - schema?: CustomFieldExtensionSchema; -}; - -/** - * Type for field extensions and being able to type - * incoming props easier. - * - * @alpha - */ -export interface LegacyFieldExtensionComponentProps< - TFieldReturnValue, - TUiOptions = unknown, -> extends ScaffolderRJSFFieldProps { - uiSchema: ScaffolderRJSFFieldProps['uiSchema'] & { - 'ui:options'?: TUiOptions; - }; -} diff --git a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx index 0f56a32d1d..e9277b2230 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx @@ -94,10 +94,10 @@ describe('ReviewState', () => { }, ]; - const { queryByRole } = render( + const { queryByRole, getAllByRole } = render( , ); - + expect(getAllByRole('row').length).toEqual(1); expect(queryByRole('row', { name: 'Name ******' })).not.toBeInTheDocument(); }); @@ -136,4 +136,70 @@ describe('ReviewState', () => { expect(getByRole('row', { name: 'Name lols' })).toBeInTheDocument(); }); + + it('should display enum label from enumNames', async () => { + const formState = { + name: 'type2', + }; + + const schemas: ParsedTemplateSchema[] = [ + { + mergedSchema: { + type: 'object', + properties: { + name: { + type: 'string', + default: 'type1', + enum: ['type1', 'type2', 'type3'], + enumNames: ['Label-type1', 'Label-type2', 'Label-type3'], + }, + }, + }, + schema: {}, + title: 'test', + uiSchema: {}, + description: 'asd', + }, + ]; + + const { queryByRole } = render( + , + ); + + expect( + queryByRole('row', { name: 'Name Label-type2' }), + ).toBeInTheDocument(); + }); + + it('should display enum value if no corresponding enumNames', async () => { + const formState = { + name: 'type4', + }; + + const schemas: ParsedTemplateSchema[] = [ + { + mergedSchema: { + type: 'object', + properties: { + name: { + type: 'string', + default: 'type1', + enum: ['type1', 'type2', 'type3', 'type4'], + enumNames: ['Label-type1', 'Label-type2', 'Label-type3'], + }, + }, + }, + schema: {}, + title: 'test', + uiSchema: {}, + description: 'asd', + }, + ]; + + const { queryByRole } = render( + , + ); + + expect(queryByRole('row', { name: 'Name type4' })).toBeInTheDocument(); + }); }); diff --git a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx index 52ad80c4c2..b601f77bf6 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx @@ -34,33 +34,44 @@ export type ReviewStateProps = { */ export const ReviewState = (props: ReviewStateProps) => { const reviewData = Object.fromEntries( - Object.entries(props.formState).map(([key, value]) => { - for (const step of props.schemas) { - const parsedSchema = new JSONSchema(step.mergedSchema); - const definitionInSchema = parsedSchema.getSchema( - `#/${key}`, - props.formState, - ); - if (definitionInSchema) { - const backstageReviewOptions = - definitionInSchema['ui:backstage']?.review; + Object.entries(props.formState) + .map(([key, value]) => { + for (const step of props.schemas) { + const parsedSchema = new JSONSchema(step.mergedSchema); + const definitionInSchema = parsedSchema.getSchema( + `#/${key}`, + props.formState, + ); + if (definitionInSchema) { + const backstageReviewOptions = + definitionInSchema['ui:backstage']?.review; - if (backstageReviewOptions) { - if (backstageReviewOptions.mask) { - return [key, backstageReviewOptions.mask]; + if (backstageReviewOptions) { + if (backstageReviewOptions.mask) { + return [key, backstageReviewOptions.mask]; + } + if (backstageReviewOptions.show === false) { + return []; + } } - if (backstageReviewOptions.show === false) { - return []; - } - } - if (definitionInSchema['ui:widget'] === 'password') { - return [key, '******']; + if (definitionInSchema['ui:widget'] === 'password') { + return [key, '******']; + } + + if (definitionInSchema.enum && definitionInSchema.enumNames) { + return [ + key, + definitionInSchema.enumNames[ + definitionInSchema.enum.indexOf(value) + ] || value, + ]; + } } } - } - return [key, value]; - }), + return [key, value]; + }) + .filter(prop => prop.length > 0), ); return ; }; diff --git a/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.test.tsx b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.test.tsx new file mode 100644 index 0000000000..c5ddf6854f --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.test.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ErrorListTemplate } from './errorListTemplate'; +import { renderInTestApp } from '@backstage/test-utils'; +import { ErrorListProps } from '@rjsf/utils'; + +describe('Error List Template', () => { + const errorList = { + errors: [ + { + stack: 'Test error 1', + }, + { + stack: 'Test error 2', + }, + ], + errorSchema: {}, + } as Partial as ErrorListProps; + + it('should render the error messages', async () => { + const { getByText } = await renderInTestApp( + , + ); + + for (const error of errorList.errors) { + expect(getByText(error.stack)).toBeInTheDocument(); + } + }); +}); diff --git a/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx new file mode 100644 index 0000000000..9b6920108b --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx @@ -0,0 +1,66 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ErrorListProps } from '@rjsf/utils'; +import { + List, + ListItem, + ListItemIcon, + ListItemText, + Paper, + Theme, + createStyles, + makeStyles, +} from '@material-ui/core'; +import ErrorIcon from '@material-ui/icons/Error'; + +const useStyles = makeStyles((_theme: Theme) => + createStyles({ + list: { + width: '100%', + }, + text: { + textWrap: 'wrap', + }, + }), +); + +/** + * Shows a list of errors found in the form + * + * @public + */ +export const ErrorListTemplate = ({ errors }: ErrorListProps) => { + const classes = useStyles(); + + return ( + + + {errors.map((error, index) => ( + + + + + + + ))} + + + ); +}; diff --git a/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/index.ts b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/index.ts new file mode 100644 index 0000000000..8a982bc68b --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { ErrorListTemplate } from './errorListTemplate'; diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx index 2bb66fd782..d7e271f8b3 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx @@ -21,6 +21,7 @@ import { act, fireEvent } from '@testing-library/react'; import type { RJSFValidationError } from '@rjsf/utils'; import { JsonValue } from '@backstage/types'; import { FieldExtensionComponentProps } from '../../../extensions'; +import { SecretsContextProvider } from '../../../secrets'; import { LayoutTemplate } from '../../../layouts'; describe('Stepper', () => { @@ -34,7 +35,9 @@ describe('Stepper', () => { }; const { getByText } = await renderInTestApp( - , + + + , ); for (const step of manifest.steps) { @@ -52,7 +55,9 @@ describe('Stepper', () => { }; const { getByRole } = await renderInTestApp( - , + + + , ); expect(getByRole('button', { name: 'Next' })).toBeInTheDocument(); @@ -92,7 +97,9 @@ describe('Stepper', () => { }; const { getByRole } = await renderInTestApp( - , + + + , ); await fireEvent.change(getByRole('textbox', { name: 'name' }), { @@ -140,7 +147,9 @@ describe('Stepper', () => { }; const { getByRole, getByLabelText } = await renderInTestApp( - , + + + , ); await fireEvent.change(getByRole('textbox', { name: 'name' }), { @@ -219,14 +228,16 @@ describe('Stepper', () => { }); const { getByRole } = await renderInTestApp( - , + + + , ); await fireEvent.change(getByRole('textbox', { name: 'repo' }), { @@ -275,11 +286,13 @@ describe('Stepper', () => { }; const { getByText } = await renderInTestApp( - , + + + , ); expect(getByText('im a custom field extension')).toBeInTheDocument(); @@ -308,17 +321,19 @@ describe('Stepper', () => { }; const { getByRole } = await renderInTestApp( - new Promise(r => setTimeout(r, 1000)), - }, - ]} - onCreate={jest.fn()} - />, + + new Promise(r => setTimeout(r, 1000)), + }, + ]} + onCreate={jest.fn()} + /> + , ); act(() => { @@ -356,12 +371,14 @@ describe('Stepper', () => { }; const { getByText, getByRole } = await renderInTestApp( - , + + + , ); await fireEvent.change(getByRole('textbox', { name: 'postcode' }), { @@ -401,7 +418,9 @@ describe('Stepper', () => { }); const { getByRole } = await renderInTestApp( - , + + + , ); expect(getByRole('textbox', { name: 'firstName' })).toHaveValue('John'); @@ -429,7 +448,9 @@ describe('Stepper', () => { }); const { getByRole } = await renderInTestApp( - , + + + , ); await act(async () => { @@ -464,15 +485,17 @@ describe('Stepper', () => { }; const { getByRole } = await renderInTestApp( - Make, - reviewButtonText: Inspect, - }} - />, + + Make, + reviewButtonText: Inspect, + }} + /> + , ); await act(async () => { @@ -516,12 +539,14 @@ describe('Stepper', () => { }; const { getByText, getByRole } = await renderInTestApp( - , + + + , ); expect(getByText('A Scaffolder Layout')).toBeInTheDocument(); diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 5aaf76316c..9b475870d2 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -51,6 +51,7 @@ import { FormProps, } from '@backstage/plugin-scaffolder-react'; import { ReviewStepProps } from '@backstage/plugin-scaffolder-react'; +import { ErrorListTemplate } from './ErrorListTemplate'; const useStyles = makeStyles(theme => ({ backButton: { @@ -190,7 +191,12 @@ export const Stepper = (stepperProps: StepperProps) => { return ( <> {isValidating && } - + {steps.map((step, index) => { const isAllowedLabelClick = activeStep > index; return ( @@ -223,8 +229,12 @@ export const Stepper = (stepperProps: StepperProps) => { uiSchema={currentStep.uiSchema} onSubmit={handleNext} fields={fields} - showErrorList={false} + showErrorList="top" + templates={{ ErrorListTemplate }} onChange={handleChange} + experimental_defaultFormStateBehavior={{ + allOf: 'populateDefaults', + }} {...(props.formProps ?? {})} >
    diff --git a/plugins/scaffolder-react/src/next/components/TaskSteps/TaskSteps.tsx b/plugins/scaffolder-react/src/next/components/TaskSteps/TaskSteps.tsx index 96f0d9d865..d74d298778 100644 --- a/plugins/scaffolder-react/src/next/components/TaskSteps/TaskSteps.tsx +++ b/plugins/scaffolder-react/src/next/components/TaskSteps/TaskSteps.tsx @@ -58,6 +58,7 @@ export const TaskSteps = (props: TaskStepsProps) => { activeStep={props.activeStep} alternativeLabel variant="elevation" + style={{ overflowX: 'auto' }} > {props.steps.map(step => { const isCompleted = step.status === 'completed'; diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.test.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.test.tsx index 8366dcadf8..4c631b86b0 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.test.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.test.tsx @@ -25,7 +25,10 @@ describe('', () => { it('should render template output', async () => { const output = { links: [{ title: 'Link 1', url: 'https://backstage.io/' }], - text: [{ title: 'Text 1', content: 'Hello, **world**!' }], + text: [ + { title: 'Text 1', content: 'Hello, **world**!' }, + { title: 'Text 2', content: 'Hello, **mars**!' }, + ], }; const { getByRole } = await renderInTestApp( @@ -37,6 +40,11 @@ describe('', () => { }, ); + // first text output default visible + expect(getByRole('heading', { level: 2 }).innerHTML).toBe( + output.text[0].title, + ); + // test link outputs for (const link of output.links ?? []) { expect( diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx index a495e4ca9e..d3811e5011 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx @@ -14,9 +14,12 @@ * limitations under the License. */ import { InfoCard, MarkdownContent } from '@backstage/core-components'; -import { ScaffolderTaskOutput } from '@backstage/plugin-scaffolder-react'; +import { + ScaffolderOutputText, + ScaffolderTaskOutput, +} from '@backstage/plugin-scaffolder-react'; import { Box, Paper } from '@material-ui/core'; -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { LinkOutputs } from './LinkOutputs'; import { TextOutputs } from './TextOutputs'; @@ -28,17 +31,27 @@ import { TextOutputs } from './TextOutputs'; export const DefaultTemplateOutputs = (props: { output?: ScaffolderTaskOutput; }) => { - const [textOutputIndex, setTextOutputIndex] = useState(); + const { output } = props; + const [textOutputIndex, setTextOutputIndex] = useState( + undefined, + ); + + useEffect(() => { + if (textOutputIndex === undefined && output?.text) { + const defaultIndex = output.text.findIndex( + (t: ScaffolderOutputText) => t.default, + ); + setTextOutputIndex(defaultIndex >= 0 ? defaultIndex : 0); + } + }, [textOutputIndex, output]); const textOutput = useMemo( () => - textOutputIndex !== undefined - ? props.output?.text?.[textOutputIndex] - : null, - [props.output, textOutputIndex], + textOutputIndex !== undefined ? output?.text?.[textOutputIndex] : null, + [output, textOutputIndex], ); - if (!props.output) { + if (!output) { return null; } @@ -48,11 +61,11 @@ export const DefaultTemplateOutputs = (props: { - + diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx index 7e64603e92..8f61aa6a30 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/TextOutputs.tsx @@ -47,7 +47,11 @@ export const TextOutputs = (props: { startIcon={} component="div" color="primary" - onClick={() => setIndex?.(index !== i ? i : undefined)} + onClick={() => { + if (index !== i) { + setIndex?.(i); + } + }} variant={index === i ? 'outlined' : undefined} > {title} diff --git a/plugins/scaffolder-react/src/next/hooks/useTemplateSchema.test.tsx b/plugins/scaffolder-react/src/next/hooks/useTemplateSchema.test.tsx index d2771b2eb9..d1d86ab977 100644 --- a/plugins/scaffolder-react/src/next/hooks/useTemplateSchema.test.tsx +++ b/plugins/scaffolder-react/src/next/hooks/useTemplateSchema.test.tsx @@ -266,5 +266,116 @@ describe('useTemplateSchema', () => { properties: {}, }); }); + + it('should deal with dependencies and oneOf options', () => { + const firstStepDependencies = { + preconditions: { + oneOf: [ + { + title: 'About', + description: 'you have chosen option A', + properties: { + preconditions: { + enum: ['optionA'], + }, + }, + }, + { + title: 'About', + description: 'you have chosen option B', + properties: { + preconditions: { + enum: ['optionB'], + }, + }, + }, + ], + }, + }; + + const secondStepDependencies = { + preconditions: { + oneOf: [ + { + required: ['inputA'], + properties: { + preconditions: { + enum: ['optionA'], + }, + inputA: { + title: 'Input A', + type: 'string', + }, + }, + }, + { + required: ['inputB'], + properties: { + preconditions: { + enum: ['optionB'], + }, + inputA: { + title: 'Input B', + type: 'string', + }, + }, + }, + ], + }, + }; + + const manifest: TemplateParameterSchema = { + title: 'Test Template', + description: 'Test Template Description', + steps: [ + { + title: 'First step', + schema: { + type: 'object', + properties: { + preconditions: { + title: 'Preconditions', + type: 'string', + description: 'Choose an option', + enum: ['optionA', 'optionB'], + enumNames: ['Option A', 'Option B'], + }, + }, + dependencies: firstStepDependencies, + }, + }, + { + title: 'Second step', + schema: { + dependencies: secondStepDependencies, + }, + }, + ], + }; + + const { result } = renderHook(() => useTemplateSchema(manifest), { + wrapper: ({ children }: React.PropsWithChildren<{}>) => ( + false }]]} + > + {children} + + ), + }); + + const [first, second] = result.current.steps; + + expect(first.schema).toEqual({ + dependencies: firstStepDependencies, + properties: expect.anything(), + title: undefined, + type: 'object', + }); + + expect(second.schema).toEqual({ + dependencies: secondStepDependencies, + title: undefined, + }); + }); }); }); diff --git a/plugins/scaffolder-react/src/next/hooks/useTemplateSchema.ts b/plugins/scaffolder-react/src/next/hooks/useTemplateSchema.ts index 838f8b1c30..af004910be 100644 --- a/plugins/scaffolder-react/src/next/hooks/useTemplateSchema.ts +++ b/plugins/scaffolder-react/src/next/hooks/useTemplateSchema.ts @@ -59,14 +59,19 @@ export const useTemplateSchema = ( return stepFeatureFlag ? featureFlags.isActive(stepFeatureFlag) : true; }) // Then filter out the properties that are not enabled with feature flag - .map(step => ({ - ...step, - schema: { - ...step.schema, - // Title is rendered at the top of the page, so let's ignore this from jsonschemaform - title: undefined, - properties: Object.fromEntries( - Object.entries((step.schema?.properties ?? []) as JsonObject).filter( + .map(step => { + const strippedSchema = { + ...step, + schema: { + ...step.schema, + // Title is rendered at the top of the page, so let's ignore this from jsonschemaform + title: undefined, + }, + } as ParsedTemplateSchema; + + if (step.schema?.properties || !step.schema?.dependencies) { + strippedSchema.schema.properties = Object.fromEntries( + Object.entries((step.schema?.properties ?? {}) as JsonObject).filter( ([key]) => { const stepFeatureFlag = step.uiSchema[key]?.['ui:backstage']?.featureFlag; @@ -75,9 +80,11 @@ export const useTemplateSchema = ( : true; }, ), - ), - }, - })); + ); + } + + return strippedSchema; + }); return { presentation: manifest.presentation, diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 547e59f7f3..2309a0554d 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,239 @@ # @backstage/plugin-scaffolder +## 1.18.0-next.0 + +### Minor Changes + +- c56f1a2: Remove the old legacy exports from `/alpha` +- a86920b: Introduced a new `MultiEntityPicker` field that supports selecting multiple Entities + +### Patch Changes + +- b0d1d80: Added basic support for the new frontend system, exported from the `/alpha` subpath. +- 912ca7b: Use `convertLegacyRouteRefs` to define routes in `/alpha` export plugin. +- da059d7: Removed alpha symbol from Task List header +- 6a74ffd: Updated dependency `@rjsf/utils` to `5.16.1`. + Updated dependency `@rjsf/core` to `5.16.1`. + Updated dependency `@rjsf/material-ui` to `5.16.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.16.1`. +- 11b9a08: Introduced the first version of recoverable tasks. +- Updated dependencies + - @backstage/core-compat-api@0.1.2-next.0 + - @backstage/plugin-catalog-react@1.9.4-next.0 + - @backstage/plugin-scaffolder-react@1.8.0-next.0 + - @backstage/frontend-plugin-api@0.5.1-next.0 + - @backstage/catalog-client@1.6.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.0-next.0 + - @backstage/core-components@0.13.10 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.2 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.23 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + - @backstage/plugin-permission-react@0.4.19 + +## 1.17.1 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- 4016f21: Remove some unused dependencies +- df4bc9d: Minor internal refactor +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.7.1 + - @backstage/core-components@0.13.10 + - @backstage/plugin-scaffolder-common@1.4.5 + - @backstage/core-plugin-api@1.8.2 + - @backstage/catalog-client@1.5.2 + - @backstage/plugin-permission-react@0.4.19 + - @backstage/plugin-catalog-react@1.9.3 + - @backstage/integration-react@1.1.23 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.20 + +## 1.17.1-next.2 + +### Patch Changes + +- 98ac5ab: Updated dependency `@rjsf/utils` to `5.15.1`. + Updated dependency `@rjsf/core` to `5.15.1`. + Updated dependency `@rjsf/material-ui` to `5.15.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.1`. +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.7.1-next.2 + - @backstage/plugin-catalog-react@1.9.3-next.2 + - @backstage/integration-react@1.1.23-next.0 + +## 1.17.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.8.2-next.0 + - @backstage/core-components@0.13.10-next.1 + - @backstage/integration-react@1.1.23-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.1 + - @backstage/plugin-permission-react@0.4.19-next.1 + - @backstage/plugin-scaffolder-react@1.7.1-next.1 + - @backstage/integration@1.8.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/catalog-model@1.4.3 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.17.1-next.0 + +### Patch Changes + +- 4016f21: Remove some unused dependencies +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.7.1-next.0 + - @backstage/core-components@0.13.10-next.0 + - @backstage/catalog-client@1.5.2-next.0 + - @backstage/plugin-permission-react@0.4.19-next.0 + - @backstage/plugin-catalog-react@1.9.3-next.0 + - @backstage/integration-react@1.1.22 + - @backstage/catalog-model@1.4.3 + - @backstage/core-plugin-api@1.8.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.17.0 + +### Minor Changes + +- df88d09: Add a new git repository url picker for `gitea`. This `GiteaRepoPicker` can be used in a template to scaffold a project to be cloned using gitea. +- 33edf50: Added support for dealing with user provided secrets using a new field extension `ui:field: Secret` + +### Patch Changes + +- 6806d10: Added `headerOptions` to `TemplateListPage` to optionally override default values. + Changed `themeId` of TemplateListPage from `website` to `home`. +- aaa6fb3: Minor updates for TypeScript 5.2.2+ compatibility +- 2b72591: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- 6cd12f2: Updated dependency `@rjsf/utils` to `5.14.1`. + Updated dependency `@rjsf/core` to `5.14.1`. + Updated dependency `@rjsf/material-ui` to `5.14.1`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.1`. +- a518c5a: Updated dependency `@react-hookz/web` to `^23.0.0`. +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- 63c494e: Updated dependency `@rjsf/utils` to `5.14.2`. + Updated dependency `@rjsf/core` to `5.14.2`. + Updated dependency `@rjsf/material-ui` to `5.14.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.2`. +- b5fa691: Fixing `headerOptions` not being passed through the `TemplatePage` component +- c8908d4: Use new option from RJSF 5.15 +- 0cbb03b: Fixing regular expression ReDoS with zod packages. Upgrading to latest. ref: https://security.snyk.io/vuln/SNYK-JS-ZOD-5925617 +- Updated dependencies + - @backstage/core-plugin-api@1.8.1 + - @backstage/plugin-catalog-react@1.9.2 + - @backstage/core-components@0.13.9 + - @backstage/theme@0.5.0 + - @backstage/plugin-scaffolder-react@1.7.0 + - @backstage/catalog-client@1.5.0 + - @backstage/integration@1.8.0 + - @backstage/integration-react@1.1.22 + - @backstage/plugin-permission-react@0.4.18 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.19 + - @backstage/plugin-scaffolder-common@1.4.4 + +## 1.16.2-next.3 + +### Patch Changes + +- 64301d3: Updated dependency `@rjsf/utils` to `5.15.0`. + Updated dependency `@rjsf/core` to `5.15.0`. + Updated dependency `@rjsf/material-ui` to `5.15.0`. + Updated dependency `@rjsf/validator-ajv8` to `5.15.0`. +- c8908d4: Use new option from RJSF 5.15 +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.6.2-next.3 + - @backstage/core-components@0.13.9-next.3 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/theme@0.5.0-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-catalog-react@1.9.2-next.3 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.16.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.0-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.2 + - @backstage/catalog-client@1.5.0-next.1 + - @backstage/plugin-scaffolder-react@1.6.2-next.2 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/core-components@0.13.9-next.2 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/errors@1.2.3 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/plugin-scaffolder-common@1.4.3 + +## 1.16.2-next.1 + +### Patch Changes + +- 2b725913c1: Updated dependency `@rjsf/utils` to `5.14.3`. + Updated dependency `@rjsf/core` to `5.14.3`. + Updated dependency `@rjsf/material-ui` to `5.14.3`. + Updated dependency `@rjsf/validator-ajv8` to `5.14.3`. +- a518c5a25b: Updated dependency `@react-hookz/web` to `^23.0.0`. +- b5fa6918dc: Fixing `headerOptions` not being passed through the `TemplatePage` component +- Updated dependencies + - @backstage/core-components@0.13.9-next.1 + - @backstage/core-plugin-api@1.8.1-next.1 + - @backstage/plugin-catalog-react@1.9.2-next.1 + - @backstage/catalog-client@1.5.0-next.0 + - @backstage/plugin-scaffolder-react@1.6.2-next.1 + - @backstage/integration@1.8.0-next.1 + - @backstage/integration-react@1.1.22-next.1 + - @backstage/plugin-permission-react@0.4.18-next.1 + - @backstage/catalog-model@1.4.3 + - @backstage/config@1.1.1 + - @backstage/errors@1.2.3 + - @backstage/theme@0.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.18 + - @backstage/plugin-scaffolder-common@1.4.3 + ## 1.16.2-next.0 ### Patch Changes diff --git a/plugins/scaffolder/alpha-api-report.md b/plugins/scaffolder/api-report-alpha.md similarity index 63% rename from plugins/scaffolder/alpha-api-report.md rename to plugins/scaffolder/api-report-alpha.md index e659b8f30d..3f0b60cd95 100644 --- a/plugins/scaffolder/alpha-api-report.md +++ b/plugins/scaffolder/api-report-alpha.md @@ -3,61 +3,52 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -/// - -import { ComponentType } from 'react'; -import { Entity } from '@backstage/catalog-model'; +import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; import { FieldExtensionOptions } from '@backstage/plugin-scaffolder-react'; import type { FormProps as FormProps_2 } from '@rjsf/core'; import { FormProps as FormProps_3 } from '@backstage/plugin-scaffolder-react'; -import { JSX as JSX_2 } from 'react'; import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; -import { PropsWithChildren } from 'react'; +import { PathParams } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import { ReviewStepProps } from '@backstage/plugin-scaffolder-react'; +import { RouteRef } from '@backstage/frontend-plugin-api'; +import { SubRouteRef } from '@backstage/frontend-plugin-api'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateGroupFilter } from '@backstage/plugin-scaffolder-react'; +// @alpha (undocumented) +const _default: BackstagePlugin< + { + root: RouteRef; + selectedTemplate: SubRouteRef< + PathParams<'/templates/:namespace/:templateName'> + >; + ongoingTask: SubRouteRef>; + actions: SubRouteRef; + listTasks: SubRouteRef; + edit: SubRouteRef; + }, + { + registerComponent: ExternalRouteRef; + viewTechDoc: ExternalRouteRef< + { + name: string; + kind: string; + namespace: string; + }, + true + >; + } +>; +export default _default; + // @alpha @deprecated export type FormProps = Pick< FormProps_2, 'transformErrors' | 'noHtml5Validate' >; -// @alpha -export const LegacyRouter: (props: LegacyRouterProps) => React_2.JSX.Element; - -// @alpha -export type LegacyRouterProps = { - components?: { - ReviewStepComponent?: ComponentType; - TemplateCardComponent?: - | ComponentType<{ - template: TemplateEntityV1beta3; - }> - | undefined; - TaskPageComponent?: ComponentType>; - }; - groups?: Array<{ - title?: React_2.ReactNode; - filter: (entity: Entity) => boolean; - }>; - templateFilter?: (entity: TemplateEntityV1beta3) => boolean; - defaultPreviewTemplate?: string; - headerOptions?: { - pageTitleOverride?: string; - title?: string; - subtitle?: string; - }; - contextMenu?: { - editor?: boolean; - actions?: boolean; - }; -}; - -// @alpha -export const LegacyScaffolderPage: (props: LegacyRouterProps) => JSX_2.Element; - // @alpha (undocumented) export type TemplateListPageProps = { TemplateCardComponent?: React_2.ComponentType<{ @@ -85,6 +76,11 @@ export type TemplateWizardPageProps = { }; layouts?: LayoutOptions[]; formProps?: FormProps_3; + headerOptions?: { + pageTitleOverride?: string; + title?: string; + subtitle?: string; + }; }; // (No @packageDocumentation comment for this package) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 6f8479990b..ccea88e27c 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -210,6 +210,34 @@ export function makeFieldSchemaFromZod< : never >; +// @public +export const MultiEntityPickerFieldExtension: FieldExtensionComponent_2< + string[], + { + defaultKind?: string | undefined; + allowArbitraryValues?: boolean | undefined; + defaultNamespace?: string | false | undefined; + catalogFilter?: + | Record< + string, + | string + | string[] + | { + exists?: boolean | undefined; + } + > + | Record< + string, + | string + | string[] + | { + exists?: boolean | undefined; + } + >[] + | undefined; + } +>; + // @public export const MyGroupsPickerFieldExtension: FieldExtensionComponent_2< string, @@ -379,6 +407,7 @@ export const RepoUrlPickerFieldExtension: FieldExtensionComponent_2< secretsKey: string; additionalScopes?: | { + gitea?: string[] | undefined; gerrit?: string[] | undefined; github?: string[] | undefined; gitlab?: string[] | undefined; @@ -405,6 +434,7 @@ export const RepoUrlPickerFieldSchema: FieldSchema< secretsKey: string; additionalScopes?: | { + gitea?: string[] | undefined; gerrit?: string[] | undefined; github?: string[] | undefined; gitlab?: string[] | undefined; diff --git a/plugins/scaffolder/dev/index.tsx b/plugins/scaffolder/dev/index.tsx index b683345ff5..e4425b04bf 100644 --- a/plugins/scaffolder/dev/index.tsx +++ b/plugins/scaffolder/dev/index.tsx @@ -24,7 +24,7 @@ import { } from '@backstage/plugin-catalog-react'; import React from 'react'; import { scaffolderApiRef, ScaffolderClient } from '../src'; -import { ScaffolderPage, LegacyScaffolderPage } from '../src/plugin'; +import { ScaffolderPage } from '../src/plugin'; import { discoveryApiRef, fetchApiRef, @@ -68,11 +68,6 @@ createDevApp() title: 'Create', element: , }) - .addPage({ - path: '/legacy-create', - title: 'Create (next)', - element: , - }) .addPage({ path: '/create-groups', title: 'Groups', @@ -91,22 +86,4 @@ createDevApp() /> ), }) - .addPage({ - path: '/legacy-create-groups', - title: 'Groups (next)', - element: ( - e.metadata.tags?.includes('techdocs') || false, - title: 'Techdocs', - }, - { - filter: e => e.metadata.tags?.includes('react') || false, - title: 'React', - }, - ]} - /> - ), - }) .render(); diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 17d4b131b9..eb6634218d 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder", "description": "The Backstage plugin that helps you create new things", - "version": "1.16.2-next.0", + "version": "1.18.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,13 +10,13 @@ }, "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.ts", + "./alpha": "./src/alpha.tsx", "./package.json": "./package.json" }, "typesVersions": { "*": { "alpha": [ - "src/alpha.ts" + "src/alpha.tsx" ], "package.json": [ "package.json" @@ -48,10 +48,11 @@ "dependencies": { "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", - "@backstage/config": "workspace:^", + "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/frontend-plugin-api": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/integration-react": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", @@ -59,7 +60,6 @@ "@backstage/plugin-permission-react": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-scaffolder-react": "workspace:^", - "@backstage/theme": "workspace:^", "@backstage/types": "workspace:^", "@codemirror/language": "^6.0.0", "@codemirror/legacy-modes": "^6.1.0", @@ -67,11 +67,11 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "@react-hookz/web": "^20.0.0", - "@rjsf/core": "5.14.2", - "@rjsf/material-ui": "5.14.2", - "@rjsf/utils": "5.14.2", - "@rjsf/validator-ajv8": "5.14.2", + "@react-hookz/web": "^23.0.0", + "@rjsf/core": "5.16.1", + "@rjsf/material-ui": "5.16.1", + "@rjsf/utils": "5.16.1", + "@rjsf/validator-ajv8": "5.16.1", "@types/react": "^16.13.1 || ^17.0.0", "@uiw/react-codemirror": "^4.9.3", "classnames": "^2.2.6", @@ -88,7 +88,7 @@ "react-use": "^17.2.4", "yaml": "^2.0.0", "zen-observable": "^0.10.0", - "zod": "^3.21.4", + "zod": "^3.22.4", "zod-to-json-schema": "^3.20.4" }, "peerDependencies": { diff --git a/plugins/scaffolder/src/alpha.tsx b/plugins/scaffolder/src/alpha.tsx new file mode 100644 index 0000000000..37e2978802 --- /dev/null +++ b/plugins/scaffolder/src/alpha.tsx @@ -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 React from 'react'; +import { + createApiExtension, + createApiFactory, + createNavItemExtension, + createPageExtension, + createPlugin, + discoveryApiRef, + fetchApiRef, + identityApiRef, +} from '@backstage/frontend-plugin-api'; +import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; +import { + compatWrapper, + convertLegacyRouteRef, + convertLegacyRouteRefs, +} from '@backstage/core-compat-api'; +import { scmIntegrationsApiRef } from '@backstage/integration-react'; +import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; +import { ScaffolderClient } from './api'; +import { + registerComponentRouteRef, + rootRouteRef, + viewTechDocRouteRef, + selectedTemplateRouteRef, + scaffolderTaskRouteRef, + scaffolderListTaskRouteRef, + actionsRouteRef, + editRouteRef, +} from './routes'; + +export { + type FormProps, + type TemplateListPageProps, + type TemplateWizardPageProps, +} from './next'; + +const scaffolderApi = createApiExtension({ + factory: createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + fetchApi: fetchApiRef, + identityApi: identityApiRef, + }, + factory: ({ discoveryApi, scmIntegrationsApi, fetchApi, identityApi }) => + new ScaffolderClient({ + discoveryApi, + scmIntegrationsApi, + fetchApi, + identityApi, + }), + }), +}); + +const scaffolderPage = createPageExtension({ + routeRef: convertLegacyRouteRef(rootRouteRef), + defaultPath: '/create', + loader: () => + import('./components/Router').then(m => compatWrapper()), +}); + +const scaffolderNavItem = createNavItemExtension({ + routeRef: convertLegacyRouteRef(rootRouteRef), + title: 'Create...', + icon: CreateComponentIcon, +}); + +/** @alpha */ +export default createPlugin({ + id: 'scaffolder', + routes: convertLegacyRouteRefs({ + root: rootRouteRef, + selectedTemplate: selectedTemplateRouteRef, + ongoingTask: scaffolderTaskRouteRef, + actions: actionsRouteRef, + listTasks: scaffolderListTaskRouteRef, + edit: editRouteRef, + }), + externalRoutes: convertLegacyRouteRefs({ + registerComponent: registerComponentRouteRef, + viewTechDoc: viewTechDocRouteRef, + }), + extensions: [scaffolderApi, scaffolderPage, scaffolderNavItem], +}); diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index c67a5c944e..fe0d7c11d0 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -107,6 +107,7 @@ export class ScaffolderClient implements ScaffolderApi { ...this.scmIntegrationsApi.bitbucketCloud.list(), ...this.scmIntegrationsApi.bitbucketServer.list(), ...this.scmIntegrationsApi.gerrit.list(), + ...this.scmIntegrationsApi.gitea.list(), ...this.scmIntegrationsApi.github.list(), ...this.scmIntegrationsApi.gitlab.list(), ] @@ -251,6 +252,7 @@ export class ScaffolderClient implements ScaffolderApi { : {}, }); eventSource.addEventListener('log', processEvent); + eventSource.addEventListener('recovered', processEvent); eventSource.addEventListener('cancelled', processEvent); eventSource.addEventListener('completion', (event: any) => { processEvent(event); diff --git a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx index 1edac508d8..aac0ed5c79 100644 --- a/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx +++ b/plugins/scaffolder/src/components/ListTasksPage/ListTasksPage.tsx @@ -18,7 +18,6 @@ import { EmptyState, ErrorPanel, Header, - Lifecycle, Link, Page, Progress, @@ -139,11 +138,7 @@ export const ListTasksPage = (props: MyTaskPageProps) => {
    - List template tasks - - } + title="List template tasks" subtitle="All tasks that have been started" /> diff --git a/plugins/scaffolder/src/components/Router/Router.tsx b/plugins/scaffolder/src/components/Router/Router.tsx index bdda92c0ef..c3d971bd11 100644 --- a/plugins/scaffolder/src/components/Router/Router.tsx +++ b/plugins/scaffolder/src/components/Router/Router.tsx @@ -142,6 +142,7 @@ export const Router = (props: PropsWithChildren) => { element={ ({ + apiVersion: 'scaffolder.backstage.io/v1beta3', + kind, + metadata: { namespace, name }, +}); + +describe('', () => { + let entities: Entity[]; + const onChange = jest.fn(); + const schema = {}; + const required = false; + let uiSchema: MultiEntityPickerProps['uiSchema']; + const rawErrors: string[] = []; + const formData: string[] = []; + + let props: FieldProps; + + const catalogApi: jest.Mocked = { + getLocationById: jest.fn(), + getEntityByName: jest.fn(), + getEntities: jest.fn(async () => ({ items: entities })), + addLocation: jest.fn(), + getLocationByRef: jest.fn(), + removeEntityByUid: jest.fn(), + } as any; + let Wrapper: React.ComponentType>; + + beforeEach(() => { + entities = [ + makeEntity('Group', 'default', 'team-a'), + makeEntity('Group', 'default', 'squad-b'), + ]; + + Wrapper = ({ children }: { children?: React.ReactNode }) => ( + + {children} + + ); + }); + + afterEach(() => jest.resetAllMocks()); + + describe('without allowedKinds and catalogFilter', () => { + beforeEach(() => { + uiSchema = { 'ui:options': {} }; + props = { + onChange, + schema, + required, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('searches for all entities', async () => { + await renderInTestApp( + + + , + ); + + expect(catalogApi.getEntities).toHaveBeenCalledWith(undefined); + }); + + it('updates even if there is not an exact match', async () => { + const { getByRole } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'squ' } }); + fireEvent.blur(input); + + expect(onChange).toHaveBeenCalledWith(['squ']); + }); + }); + + describe('with catalogFilter', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }, + }; + props = { + onChange, + schema, + required, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('searches for a specific group entity', async () => { + await renderInTestApp( + + + , + ); + + expect(catalogApi.getEntities).toHaveBeenCalledWith({ + filter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }); + }); + + it('allow single top level filter', async () => { + uiSchema = { + 'ui:options': { + catalogFilter: { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + }, + }; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + + await renderInTestApp( + + + , + ); + + expect(catalogApi.getEntities).toHaveBeenCalledWith({ + filter: { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + }); + }); + + it('search for entitities containing an specific key', async () => { + const uiSchemaWithBoolean = { + 'ui:options': { + catalogFilter: [ + { + kind: ['User'], + 'metadata.annotation.some/anotation': { exists: true }, + }, + ], + }, + }; + + await renderInTestApp( + + + , + ); + + expect(catalogApi.getEntities).toHaveBeenCalledWith({ + filter: [ + { + kind: ['User'], + 'metadata.annotation.some/anotation': CATALOG_FILTER_EXISTS, + }, + ], + }); + }); + }); + + describe('catalogFilter should take precedence over allowedKinds', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-group', + }, + ], + allowedKinds: ['User'], + }, + }; + props = { + onChange, + schema, + required, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('searches for a Group entity', async () => { + await renderInTestApp( + + + , + ); + + expect(catalogApi.getEntities).toHaveBeenCalledWith({ + filter: [ + { + kind: ['Group'], + 'metadata.name': 'test-group', + }, + ], + }); + }); + }); + + describe('uses full entity ref', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + defaultKind: 'Group', + }, + }; + props = { + onChange, + schema, + required, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('returns the full entityRef when entity exists in the list', async () => { + const { getByRole } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'team-a' } }); + fireEvent.blur(input); + + expect(onChange).toHaveBeenCalledWith(['group:default/team-a']); + }); + + it('returns the full entityRef when entity does not exist in the list', async () => { + const { getByRole } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'team-b' } }); + fireEvent.blur(input); + + expect(onChange).toHaveBeenCalledWith(['group:default/team-b']); + }); + }); + + describe('Required MultiEntityPicker', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }, + }; + props = { + onChange, + schema, + required: true, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('User enters clear input', async () => { + await renderInTestApp( + + +
    Outside
    +
    , + ); + + const input = screen.getByRole('textbox'); + + fireEvent.change(input, { target: { value: '' } }); + fireEvent.blur(input); + + expect(input).toHaveValue(''); + }); + + it('User selects item', async () => { + await renderInTestApp( + + + , + ); + + const input = screen.getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'team-a' } }); + fireEvent.blur(input); + + expect(onChange).toHaveBeenCalledWith(['team-a']); + }); + + it('User selects item and enters clear input', async () => { + await renderInTestApp( + + +
    Outside
    +
    , + ); + + // Open the Autocomplete dropdown + const input = screen.getByRole('textbox'); + fireEvent.click(input); + + // Select an option from the dropdown + fireEvent.change(input, { target: { value: 'team-a' } }); + + // Close the dropdown by clicking outside the Autocomplete component + const outside = screen.getByTestId('outside'); + fireEvent.mouseDown(outside); + + // Click back into the Autocomplete component + fireEvent.click(input); + + // Verify that the selected option is displayed in the input + expect(input).toHaveValue('team-a'); + + // Click the Clear button to clear the input + const clearButton = screen.getByLabelText('Clear'); + + fireEvent.click(clearButton); + + // Verify that the input is empty + expect(input).toHaveValue(''); + + // Verify that the handleChange function was called with an empty array + expect(onChange).toHaveBeenCalledWith([]); + }); + }); + + describe('Optional MultiEntityPicker', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }, + }; + props = { + onChange, + schema, + required: false, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('User enters clear input', async () => { + await renderInTestApp( + + +
    Outside
    +
    , + ); + + const input = screen.getByRole('textbox'); + + fireEvent.change(input, { target: { value: '' } }); + fireEvent.blur(input); + + expect(input).toHaveValue(''); + }); + + it('User selects item', async () => { + await renderInTestApp( + + + , + ); + + const input = screen.getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'team-a' } }); + fireEvent.blur(input); + + expect(onChange).toHaveBeenCalledWith(['team-a']); + }); + + it('User selects item and enters clear input', async () => { + await renderInTestApp( + + +
    Outside
    +
    , + ); + + // Open the Autocomplete dropdown + const input = screen.getByRole('textbox'); + fireEvent.click(input); + + // Select an option from the dropdown + fireEvent.change(input, { target: { value: 'team-a' } }); + + // Close the dropdown by clicking outside the Autocomplete component + const outside = screen.getByTestId('outside'); + fireEvent.mouseDown(outside); + + // Click back into the Autocomplete component + fireEvent.click(input); + + // Verify that the selected option is displayed in the input + expect(input).toHaveValue('team-a'); + + // Click the Clear button to clear the input + const clearButton = screen.getByLabelText('Clear'); + fireEvent.click(clearButton); + + // Verify that the input is empty + expect(input).toHaveValue(''); + + // Verify that the handleChange function was called with an empty array + expect(onChange).toHaveBeenCalledWith([]); + }); + }); + + describe('Required Free Solo', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }, + allowArbitraryValues: true, + }; + props = { + onChange, + schema, + required: true, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('User enters clear input', async () => { + await renderInTestApp( + + +
    Outside
    +
    , + ); + + const input = screen.getByRole('textbox'); + + fireEvent.change(input, { target: { value: '' } }); + fireEvent.blur(input); + + expect(input).toHaveValue(''); + }); + + it('User selects item', async () => { + await renderInTestApp( + + + , + ); + + const input = screen.getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'team-a' } }); + fireEvent.blur(input); + + expect(onChange).toHaveBeenCalledWith(['team-a']); + }); + + it('User selects item and enters clear input', async () => { + await renderInTestApp( + + +
    Outside
    +
    , + ); + + // Open the Autocomplete dropdown + const input = screen.getByRole('textbox'); + fireEvent.click(input); + + // Select an option from the dropdown + fireEvent.change(input, { target: { value: 'team-a' } }); + + // Close the dropdown by clicking outside the Autocomplete component + const outside = screen.getByTestId('outside'); + fireEvent.mouseDown(outside); + + // Click back into the Autocomplete component + fireEvent.click(input); + + // Verify that the selected option is displayed in the input + expect(input).toHaveValue('team-a'); + + // Click the Clear button to clear the input + const clearButton = screen.getByLabelText('Clear'); + fireEvent.click(clearButton); + + // Verify that the input is empty + expect(input).toHaveValue(''); + + // Verify that the handleChange function was called with an empty array + expect(onChange).toHaveBeenCalledWith([]); + }); + }); + + describe('Optional Free Solo', () => { + beforeEach(() => { + uiSchema = { + 'ui:options': { + catalogFilter: [ + { + kind: ['Group'], + 'metadata.name': 'test-entity', + }, + { + kind: ['User'], + 'metadata.name': 'test-entity', + }, + ], + }, + allowArbitraryValues: true, + }; + props = { + onChange, + schema, + required: false, + uiSchema, + rawErrors, + formData, + } as unknown as FieldProps; + + catalogApi.getEntities.mockResolvedValue({ items: entities }); + }); + + it('User enters clear input', async () => { + await renderInTestApp( + + +
    Outside
    +
    , + ); + + const input = screen.getByRole('textbox'); + + fireEvent.change(input, { target: { value: '' } }); + fireEvent.blur(input); + + expect(input).toHaveValue(''); + }); + + it('User selects item', async () => { + await renderInTestApp( + + + , + ); + + const input = screen.getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'team-a' } }); + fireEvent.blur(input); + + expect(onChange).toHaveBeenCalledWith(['team-a']); + }); + + it('User selects item and enters clear input', async () => { + await renderInTestApp( + + +
    Outside
    +
    , + ); + + // Open the Autocomplete dropdown + const input = screen.getByRole('textbox'); + fireEvent.click(input); + + // Select an option from the dropdown + fireEvent.change(input, { target: { value: 'team-a' } }); + + // Close the dropdown by clicking outside the Autocomplete component + const outside = screen.getByTestId('outside'); + fireEvent.mouseDown(outside); + + // Click back into the Autocomplete component + fireEvent.click(input); + + // Verify that the selected option is displayed in the input + expect(input).toHaveValue('team-a'); + + // Click the Clear button to clear the input + const clearButton = screen.getByLabelText('Clear'); + fireEvent.click(clearButton); + + // Verify that the input is empty + expect(input).toHaveValue(''); + + // Verify that the handleChange function was called with an empty array + expect(onChange).toHaveBeenCalledWith([]); + }); + }); +}); diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx new file mode 100644 index 0000000000..d850bbd435 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -0,0 +1,259 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + type EntityFilterQuery, + CATALOG_FILTER_EXISTS, +} from '@backstage/catalog-client'; +import { + Entity, + parseEntityRef, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { useApi } from '@backstage/core-plugin-api'; +import { + catalogApiRef, + humanizeEntityRef, +} from '@backstage/plugin-catalog-react'; +import { TextField } from '@material-ui/core'; +import FormControl from '@material-ui/core/FormControl'; +import Autocomplete, { + AutocompleteChangeReason, +} from '@material-ui/lab/Autocomplete'; +import React, { useCallback, useEffect } from 'react'; +import useAsync from 'react-use/lib/useAsync'; +import { FieldValidation } from '@rjsf/utils'; +import { + MultiEntityPickerFilterQueryValue, + MultiEntityPickerProps, + MultiEntityPickerUiOptions, + MultiEntityPickerFilterQuery, +} from './schema'; + +export { MultiEntityPickerSchema } from './schema'; + +/** + * The underlying component that is rendered in the form for the `MultiEntityPicker` + * field extension. + */ +export const MultiEntityPicker = (props: MultiEntityPickerProps) => { + const { + onChange, + schema: { title = 'Entity', description = 'An entity from the catalog' }, + required, + uiSchema, + rawErrors, + formData, + idSchema, + } = props; + const catalogFilter = buildCatalogFilter(uiSchema); + const defaultKind = uiSchema['ui:options']?.defaultKind; + const defaultNamespace = + uiSchema['ui:options']?.defaultNamespace || undefined; + + const catalogApi = useApi(catalogApiRef); + + const { value: entities, loading } = useAsync(async () => { + const { items } = await catalogApi.getEntities( + catalogFilter ? { filter: catalogFilter } : undefined, + ); + return items; + }); + const allowArbitraryValues = + uiSchema['ui:options']?.allowArbitraryValues ?? true; + + const getLabel = useCallback( + (ref: string) => { + try { + return humanizeEntityRef( + parseEntityRef(ref, { defaultKind, defaultNamespace }), + { + defaultKind, + defaultNamespace, + }, + ); + } catch (err) { + return ref; + } + }, + [defaultKind, defaultNamespace], + ); + + const onSelect = useCallback( + (_: any, refs: (string | Entity)[], reason: AutocompleteChangeReason) => { + const values = refs + .map(ref => { + if (typeof ref !== 'string') { + // if ref does not exist: pass 'undefined' to trigger validation for required value + return ref ? stringifyEntityRef(ref as Entity) : undefined; + } + if (reason === 'blur' || reason === 'create-option') { + // Add in default namespace, etc. + let entityRef = ref; + try { + // Attempt to parse the entity ref into it's full form. + entityRef = stringifyEntityRef( + parseEntityRef(ref as string, { + defaultKind, + defaultNamespace, + }), + ); + } catch (err) { + // If the passed in value isn't an entity ref, do nothing. + } + + // We need to check against formData here as that's the previous value for this field. + if (formData.includes(ref) || allowArbitraryValues) { + return entityRef; + } + } + + return undefined; + }) + .filter(ref => ref !== undefined) as string[]; + + onChange(values); + }, + [onChange, formData, defaultKind, defaultNamespace, allowArbitraryValues], + ); + + useEffect(() => { + if (entities?.length === 1) { + onChange([stringifyEntityRef(entities[0])]); + } + }, [entities, onChange]); + + return ( + 0 && !formData} + > + formData && formData.includes(stringifyEntityRef(e)), + ) ?? (allowArbitraryValues && formData ? formData.map(getLabel) : []) + } + loading={loading} + onChange={onSelect} + options={entities || []} + getOptionLabel={option => + // option can be a string due to freeSolo. + typeof option === 'string' + ? option + : humanizeEntityRef(option, { defaultKind, defaultNamespace })! + } + autoSelect + freeSolo={allowArbitraryValues} + renderInput={params => ( + + )} + /> + + ); +}; + +export const validateMultiEntityPickerValidation = ( + values: string[], + validation: FieldValidation, +) => { + values.forEach(value => { + try { + parseEntityRef(value); + } catch { + validation.addError(`${value} is not a valid entity ref`); + } + }); +}; + +/** + * Converts a special `{exists: true}` value to the `CATALOG_FILTER_EXISTS` symbol. + * + * @param value - The value to convert. + * @returns The converted value. + */ +function convertOpsValues( + value: Exclude>, +): string | symbol { + if (typeof value === 'object' && value.exists) { + return CATALOG_FILTER_EXISTS; + } + return value?.toString(); +} + +/** + * Converts schema filters to entity filter query, replacing `{exists:true}` values + * with the constant `CATALOG_FILTER_EXISTS`. + * + * @param schemaFilters - An object containing schema filters with keys as filter names + * and values as filter values. + * @returns An object with the same keys as the input object, but with `{exists:true}` values + * transformed to `CATALOG_FILTER_EXISTS` symbol. + */ +function convertSchemaFiltersToQuery( + schemaFilters: MultiEntityPickerFilterQuery, +): Exclude> { + const query: EntityFilterQuery = {}; + + for (const [key, value] of Object.entries(schemaFilters)) { + if (Array.isArray(value)) { + query[key] = value; + } else { + query[key] = convertOpsValues(value); + } + } + + return query; +} + +/** + * Builds an `EntityFilterQuery` based on the `uiSchema` passed in. + * If `catalogFilter` is specified in the `uiSchema`, it is converted to a `EntityFilterQuery`. + * + * @param uiSchema The `uiSchema` of an `EntityPicker` component. + * @returns An `EntityFilterQuery` based on the `uiSchema`, or `undefined` if `catalogFilter` is not specified in the `uiSchema`. + */ +function buildCatalogFilter( + uiSchema: MultiEntityPickerProps['uiSchema'], +): EntityFilterQuery | undefined { + const catalogFilter: MultiEntityPickerUiOptions['catalogFilter'] | undefined = + uiSchema['ui:options']?.catalogFilter; + + if (!catalogFilter) { + return undefined; + } + + if (Array.isArray(catalogFilter)) { + return catalogFilter.map(convertSchemaFiltersToQuery); + } + + return convertSchemaFiltersToQuery(catalogFilter); +} diff --git a/plugins/scaffolder/src/legacy/TaskPage/index.ts b/plugins/scaffolder/src/components/fields/MultiEntityPicker/index.ts similarity index 86% rename from plugins/scaffolder/src/legacy/TaskPage/index.ts rename to plugins/scaffolder/src/components/fields/MultiEntityPicker/index.ts index f7876888f9..9a597b9b97 100644 --- a/plugins/scaffolder/src/legacy/TaskPage/index.ts +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/index.ts @@ -13,5 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { TaskPage } from './TaskPage'; -export type { TaskPageProps } from './TaskPage'; + +export { + MultiEntityPickerFieldSchema, + type MultiEntityPickerUiOptions, +} from './schema'; diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/schema.ts b/plugins/scaffolder/src/components/fields/MultiEntityPicker/schema.ts new file mode 100644 index 0000000000..2621157d02 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/schema.ts @@ -0,0 +1,69 @@ +/* + * 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 { z } from 'zod'; +import { makeFieldSchemaFromZod } from '../utils'; + +export const entityQueryFilterExpressionSchema = z.record( + z + .string() + .or(z.object({ exists: z.boolean().optional() })) + .or(z.array(z.string())), +); + +export const MultiEntityPickerFieldSchema = makeFieldSchemaFromZod( + z.array(z.string()), + z.object({ + defaultKind: z + .string() + .optional() + .describe( + 'The default entity kind. Options of this kind will not be prefixed.', + ), + allowArbitraryValues: z + .boolean() + .optional() + .describe('Whether to allow arbitrary user input. Defaults to true'), + defaultNamespace: z + .union([z.string(), z.literal(false)]) + .optional() + .describe( + 'The default namespace. Options with this namespace will not be prefixed.', + ), + catalogFilter: z + .array(entityQueryFilterExpressionSchema) + .or(entityQueryFilterExpressionSchema) + .optional() + .describe('List of key-value filter expression for entities'), + }), +); + +/** + * The input props that can be specified under `ui:options` for the + * `EntityPicker` field extension. + */ +export type MultiEntityPickerUiOptions = + typeof MultiEntityPickerFieldSchema.uiOptionsType; + +export type MultiEntityPickerProps = typeof MultiEntityPickerFieldSchema.type; + +export const MultiEntityPickerSchema = MultiEntityPickerFieldSchema.schema; + +export type MultiEntityPickerFilterQuery = z.TypeOf< + typeof entityQueryFilterExpressionSchema +>; + +export type MultiEntityPickerFilterQueryValue = + MultiEntityPickerFilterQuery[keyof MultiEntityPickerFilterQuery]; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx new file mode 100644 index 0000000000..e66ca49af1 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.test.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { GiteaRepoPicker } from './GiteaRepoPicker'; +import { render, fireEvent } from '@testing-library/react'; + +describe('GiteaRepoPicker', () => { + describe('owner input field', () => { + it('calls onChange when the owner input changes', () => { + const onChange = jest.fn(); + const { getAllByRole } = render( + , + ); + + const ownerInput = getAllByRole('textbox')[0]; + + fireEvent.change(ownerInput, { target: { value: 'test-owner' } }); + + expect(onChange).toHaveBeenCalledWith({ owner: 'test-owner' }); + }); + }); +}); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.tsx new file mode 100644 index 0000000000..566a2732e3 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GiteaRepoPicker.tsx @@ -0,0 +1,75 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import Input from '@material-ui/core/Input'; +import InputLabel from '@material-ui/core/InputLabel'; +import { Select, SelectItem } from '@backstage/core-components'; +import { RepoUrlPickerState } from './types'; + +export const GiteaRepoPicker = (props: { + allowedOwners?: string[]; + allowedRepos?: string[]; + state: RepoUrlPickerState; + onChange: (state: RepoUrlPickerState) => void; + rawErrors: string[]; +}) => { + const { allowedOwners = [], state, onChange, rawErrors } = props; + const ownerItems: SelectItem[] = allowedOwners + ? allowedOwners.map(i => ({ label: i, value: i })) + : [{ label: 'Loading...', value: 'loading' }]; + + const { owner } = state; + + return ( + <> + 0 && !owner} + > + {allowedOwners?.length ? ( + onChange({ owner: e.target.value })} + value={owner} + /> + + )} + + Gitea namespace where this repository will belong to. It can be the + name of organization, group, subgroup, user, or the project. + + + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 7fc71d8338..ba333088ab 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -20,6 +20,7 @@ import { } from '@backstage/integration-react'; import React, { useEffect, useState, useMemo, useCallback } from 'react'; import { GithubRepoPicker } from './GithubRepoPicker'; +import { GiteaRepoPicker } from './GiteaRepoPicker'; import { GitlabRepoPicker } from './GitlabRepoPicker'; import { AzureRepoPicker } from './AzureRepoPicker'; import { BitbucketRepoPicker } from './BitbucketRepoPicker'; @@ -183,6 +184,15 @@ export const RepoUrlPicker = (props: RepoUrlPickerProps) => { state={state} /> )} + {hostType === 'gitea' && ( + + )} {hostType === 'gitlab' && ( { + onChange(Array(e.target?.value.length).fill('*').join('')); + setSecrets({ [name]: e.target?.value }); + }} + value={secrets[name] ?? ''} + type="password" + autoComplete="off" + /> + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/SecretInput/index.tsx b/plugins/scaffolder/src/components/fields/SecretInput/index.tsx new file mode 100644 index 0000000000..859a04a4d3 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/SecretInput/index.tsx @@ -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 * from './SecretInput'; diff --git a/plugins/scaffolder/src/components/fields/index.ts b/plugins/scaffolder/src/components/fields/index.ts index 7ee4448f6b..7f119f27be 100644 --- a/plugins/scaffolder/src/components/fields/index.ts +++ b/plugins/scaffolder/src/components/fields/index.ts @@ -19,4 +19,5 @@ export * from './RepoUrlPicker'; export * from './OwnedEntityPicker'; export * from './EntityTagsPicker'; export * from './MyGroupsPicker'; + export { type FieldSchema, makeFieldSchemaFromZod } from './utils'; diff --git a/plugins/scaffolder/src/components/index.ts b/plugins/scaffolder/src/components/index.ts index fd8c11e2da..552a75c99b 100644 --- a/plugins/scaffolder/src/components/index.ts +++ b/plugins/scaffolder/src/components/index.ts @@ -22,5 +22,3 @@ export type { RouterProps } from './Router'; export { OngoingTask as TaskPage } from './OngoingTask'; export type { ReviewStepProps } from '@backstage/plugin-scaffolder-react'; - -export type { TaskPageProps } from '../legacy/TaskPage'; diff --git a/plugins/scaffolder/src/deprecated.ts b/plugins/scaffolder/src/deprecated.ts index f08a83c594..907871946c 100644 --- a/plugins/scaffolder/src/deprecated.ts +++ b/plugins/scaffolder/src/deprecated.ts @@ -212,3 +212,13 @@ export type LayoutTemplate = LayoutTemplateTemp; * @deprecated use import from {@link @backstage/plugin-scaffolder-react#LayoutOptions} instead as this has now been moved. */ export type LayoutOptions = LayoutOptionsTemp; + +/** + * TaskPageProps for constructing a TaskPage + * @public + * @deprecated - this is a useless type that is no longer used. + */ +export type TaskPageProps = { + // Optional loading text shown before a task begins executing. + loadingText?: string; +}; diff --git a/plugins/scaffolder/src/extensions/default.ts b/plugins/scaffolder/src/extensions/default.ts index 013f242a12..a59f3e08e3 100644 --- a/plugins/scaffolder/src/extensions/default.ts +++ b/plugins/scaffolder/src/extensions/default.ts @@ -44,6 +44,13 @@ import { MyGroupsPickerSchema, } from '../components/fields/MyGroupsPicker/MyGroupsPicker'; +import { SecretInput } from '../components/fields/SecretInput'; +import { + MultiEntityPicker, + MultiEntityPickerSchema, + validateMultiEntityPickerValidation, +} from '../components/fields/MultiEntityPicker/MultiEntityPicker'; + export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [ { component: EntityPicker, @@ -82,4 +89,14 @@ export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS = [ name: 'MyGroupsPicker', schema: MyGroupsPickerSchema, }, + { + component: SecretInput, + name: 'Secret', + }, + { + component: MultiEntityPicker, + name: 'MultiEntityPicker', + schema: MultiEntityPickerSchema, + validation: validateMultiEntityPickerValidation, + }, ]; diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index ee91d2988c..d809ec00f9 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -29,6 +29,7 @@ export { OwnedEntityPickerFieldExtension, MyGroupsPickerFieldExtension, RepoUrlPickerFieldExtension, + MultiEntityPickerFieldExtension, ScaffolderPage, scaffolderPlugin, } from './plugin'; diff --git a/plugins/scaffolder/src/legacy/MultistepJsonForm/MultistepJsonForm.test.tsx b/plugins/scaffolder/src/legacy/MultistepJsonForm/MultistepJsonForm.test.tsx deleted file mode 100644 index 3f8e153b65..0000000000 --- a/plugins/scaffolder/src/legacy/MultistepJsonForm/MultistepJsonForm.test.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getReviewData, getUiSchemasFromSteps } from './ReviewStep'; - -describe('MultistepJsonForm', () => { - const formDataMock = { - password: 'password', - masked: 'Some info to mask', - open: 'Some open info', - hidden: 'Some info to hide', - 'other-open': 'Other open info', - }; - - const stepsMock = [ - { - title: 'The test template', - schema: { - title: 'The test template', - properties: { - password: { - title: 'Password', - type: 'string', - 'ui:widget': 'password', - }, - masked: { - title: 'Masked', - type: 'string', - 'ui:backstage': { - review: { - mask: '******', - }, - }, - }, - open: { - title: 'Open info', - type: 'string', - }, - }, - }, - }, - { - title: 'Other fields', - schema: { - title: 'Other fields', - properties: { - hidden: { - title: 'Hidden', - type: 'string', - 'ui:backstage': { - review: { - show: false, - }, - }, - }, - 'other-open': { - title: 'Other Open Info', - type: 'string', - }, - }, - }, - }, - ]; - - test('Fields are defined to be hidden or masked', () => { - const schemas = getUiSchemasFromSteps(stepsMock); - const reviewData = getReviewData(formDataMock, schemas); - - expect(reviewData.password).toBe('******'); - expect(reviewData.masked).toBe('******'); - expect(reviewData.open).toBe('Some open info'); - expect(reviewData.hidden).toBeUndefined(); - expect(reviewData['other-open']).toBe('Other open info'); - }); -}); diff --git a/plugins/scaffolder/src/legacy/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/legacy/MultistepJsonForm/MultistepJsonForm.tsx deleted file mode 100644 index 312522bc88..0000000000 --- a/plugins/scaffolder/src/legacy/MultistepJsonForm/MultistepJsonForm.tsx +++ /dev/null @@ -1,222 +0,0 @@ -/* - * 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 { - Button, - Step as StepUI, - StepContent, - StepLabel, - Stepper, - Typography, -} from '@material-ui/core'; -import { JsonObject } from '@backstage/types'; -import { - errorApiRef, - featureFlagsApiRef, - useAnalytics, - useRouteRefParams, - useApi, -} from '@backstage/core-plugin-api'; -import { FormProps, IChangeEvent, withTheme } from '@rjsf/core'; -import { Theme as MuiTheme } from '@rjsf/material-ui'; -import React, { ComponentType, useState } from 'react'; -import { transformSchemaToProps } from './schema'; -import cloneDeep from 'lodash/cloneDeep'; -import * as fieldOverrides from './FieldOverrides'; -import { ReviewStep } from './ReviewStep'; -import validator from '@rjsf/validator-ajv8'; -import { extractSchemaFromStep } from '@backstage/plugin-scaffolder-react/alpha'; -import { selectedTemplateRouteRef } from '../../routes'; -import { - LayoutOptions, - ReviewStepProps, -} from '@backstage/plugin-scaffolder-react'; - -const Form = withTheme(MuiTheme); - -type Step = { - schema: JsonObject; - title: string; -} & Partial, 'schema'>>; - -/** - * The props for a dynamic form of a scaffolder template. - */ -export type MultistepJsonFormProps = { - /** - * Steps for the form, each contains title and form schema - */ - steps: Step[]; - formData: Record; - onChange: (e: IChangeEvent) => void; - onReset: () => void; - onFinish?: () => Promise; - widgets?: FormProps['widgets']; - fields?: FormProps['fields']; - finishButtonLabel?: string; - layouts: LayoutOptions[]; - ReviewStepComponent?: ComponentType; -}; - -export function getSchemasFromSteps(steps: Step[]) { - return steps.map(({ schema }) => ({ - mergedSchema: schema, - ...extractSchemaFromStep(schema), - })); -} - -/** - * Creates the dynamic form for a scaffolder template. - */ -export const MultistepJsonForm = (props: MultistepJsonFormProps) => { - const { - formData, - onChange, - onReset, - onFinish, - fields, - widgets, - layouts, - ReviewStepComponent, - } = props; - const { templateName } = useRouteRefParams(selectedTemplateRouteRef); - const analytics = useAnalytics(); - const [activeStep, setActiveStep] = useState(0); - const [disableButtons, setDisableButtons] = useState(false); - const errorApi = useApi(errorApiRef); - const featureFlagApi = useApi(featureFlagsApiRef); - const featureFlagKey = 'backstage:featureFlag'; - const filterOutProperties = (step: Step): Step => { - const filteredStep = cloneDeep(step); - const removedPropertyKeys: Array = []; - if (filteredStep.schema.properties) { - filteredStep.schema.properties = Object.fromEntries( - Object.entries(filteredStep.schema.properties).filter( - ([key, value]) => { - if (value[featureFlagKey]) { - if (featureFlagApi.isActive(value[featureFlagKey])) { - return true; - } - removedPropertyKeys.push(key); - return false; - } - return true; - }, - ), - ); - - // remove the feature flag property key from required if they are not active - filteredStep.schema.required = Array.isArray(filteredStep.schema.required) - ? filteredStep.schema.required?.filter( - r => !removedPropertyKeys.includes(r as string), - ) - : filteredStep.schema.required; - } - return filteredStep; - }; - - const steps = props.steps - .filter(step => { - const featureFlag = step.schema[featureFlagKey]; - return ( - typeof featureFlag !== 'string' || featureFlagApi.isActive(featureFlag) - ); - }) - .map(filterOutProperties); - - const handleReset = () => { - setActiveStep(0); - onReset(); - }; - const handleNext = () => { - const stepNum = Math.min(activeStep + 1, steps.length); - setActiveStep(stepNum); - analytics.captureEvent('click', `Next Step (${stepNum})`); - }; - const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0)); - const handleCreate = async () => { - if (!onFinish) { - return; - } - - setDisableButtons(true); - try { - await onFinish(); - analytics.captureEvent('create', formData.name || `new ${templateName}`); - } catch (err) { - errorApi.post(err); - } finally { - setDisableButtons(false); - } - }; - - const ReviewStepElement = ReviewStepComponent ?? ReviewStep; - - return ( - <> - - {steps.map(({ title, schema, ...formProps }, index) => { - return ( - - - - {title} - - - -
    ) => { - if (e.errors.length === 0) handleNext(); - }} - {...formProps} - {...transformSchemaToProps(schema, layouts)} - > - - -
    -
    -
    - ); - })} -
    - {activeStep === steps.length && ( - - )} - - ); -}; diff --git a/plugins/scaffolder/src/legacy/MultistepJsonForm/ReviewStep.tsx b/plugins/scaffolder/src/legacy/MultistepJsonForm/ReviewStep.tsx deleted file mode 100644 index c3b244e921..0000000000 --- a/plugins/scaffolder/src/legacy/MultistepJsonForm/ReviewStep.tsx +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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 { Box, Button, Paper, Typography } from '@material-ui/core'; -import React from 'react'; -import { Content, StructuredMetadataTable } from '@backstage/core-components'; -import { UiSchema } from '@rjsf/utils'; -import { JsonObject } from '@backstage/types'; -import { ReviewStepProps } from '@backstage/plugin-scaffolder-react'; - -export function getReviewData( - formData: Record, - uiSchemas: UiSchema[], -) { - const reviewData: Record = {}; - const orderedReviewProperties = new Set( - uiSchemas.map(us => us.name).concat(Object.getOwnPropertyNames(formData)), - ); - - for (const key of orderedReviewProperties) { - const uiSchema = uiSchemas.find(us => us.name === key); - - if (!uiSchema) { - reviewData[key] = formData[key]; - continue; - } - - if (uiSchema['ui:widget'] === 'password') { - reviewData[key] = '******'; - continue; - } - - if (!uiSchema['ui:backstage'] || !uiSchema['ui:backstage'].review) { - reviewData[key] = formData[key]; - continue; - } - - const review = uiSchema['ui:backstage'].review as JsonObject; - if (review.mask) { - reviewData[key] = review.mask; - continue; - } - - if (!review.show) { - continue; - } - reviewData[key] = formData[key]; - } - - return reviewData; -} - -export function getUiSchemasFromSteps( - steps: { - schema: JsonObject; - }[], -): UiSchema[] { - const uiSchemas: Array = []; - steps.forEach(step => { - const schemaProps = step.schema.properties as JsonObject; - for (const key in schemaProps) { - if (schemaProps.hasOwnProperty(key)) { - const uiSchema = schemaProps[key] as UiSchema; - uiSchema.name = key; - uiSchemas.push(uiSchema); - } - } - }); - return uiSchemas; -} - -/** - * The component displaying the Last Step in scaffolder template form. - * Which represents the summary of the input provided by the end user. - */ -export const ReviewStep = (props: ReviewStepProps) => { - const { - disableButtons, - formData, - handleBack, - handleCreate, - handleReset, - steps, - } = props; - return ( - - - Review and create - ({ schema: mergedSchema })), - ), - )} - /> - - - - - - - ); -}; diff --git a/plugins/scaffolder/src/legacy/MultistepJsonForm/schema.test.ts b/plugins/scaffolder/src/legacy/MultistepJsonForm/schema.test.ts deleted file mode 100644 index af5bb7fac5..0000000000 --- a/plugins/scaffolder/src/legacy/MultistepJsonForm/schema.test.ts +++ /dev/null @@ -1,535 +0,0 @@ -/* - * 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 { transformSchemaToProps } from './schema'; - -describe('transformSchemaToProps', () => { - it('transforms deep schema', () => { - const inputSchema = { - type: 'object', - 'ui:welp': 'warp', - properties: { - field1: { - type: 'string', - 'ui:derp': 'herp', - }, - field2: { - type: 'object', - properties: { - fieldX: { - type: 'string', - 'ui:derp': 'xerp', - }, - }, - }, - }, - }; - const expectedSchema = { - type: 'object', - properties: { - field1: { - type: 'string', - }, - field2: { - type: 'object', - properties: { - fieldX: { - type: 'string', - }, - }, - }, - }, - }; - const expectedUiSchema = { - 'ui:welp': 'warp', - field1: { - 'ui:derp': 'herp', - }, - field2: { - fieldX: { - 'ui:derp': 'xerp', - }, - }, - }; - - expect(transformSchemaToProps(inputSchema)).toEqual({ - schema: expectedSchema, - uiSchema: expectedUiSchema, - }); - }); - - it('transforms schema with anyOf fields', () => { - const inputSchema = { - type: 'object', - anyOf: [ - { - properties: { - field3: { - type: 'string', - default: 'Value 1', - 'ui:readonly': true, - }, - }, - }, - { - properties: { - field3: { - type: 'string', - default: 'Value 2', - 'ui:readonly': true, - }, - }, - }, - ], - oneOf: [ - { - properties: { - field4: { - type: 'string', - default: 'Value 1', - 'ui:readonly': true, - }, - }, - }, - ], - allOf: [ - { - properties: { - field5: { - type: 'string', - default: 'Value 1', - 'ui:readonly': true, - }, - }, - }, - ], - properties: { - field1: { - type: 'object', - anyOf: [ - { - properties: { - field3: { - type: 'string', - default: 'Value 1', - 'ui:readonly': true, - }, - }, - }, - { - properties: { - field3: { - type: 'string', - default: 'Value 2', - 'ui:readonly': true, - }, - }, - }, - ], - oneOf: [ - { - properties: { - field4: { - type: 'string', - default: 'Value 1', - 'ui:readonly': true, - }, - }, - }, - ], - allOf: [ - { - properties: { - field5: { - type: 'string', - default: 'Value 1', - 'ui:readonly': true, - }, - }, - }, - ], - }, - field2: { - type: 'string', - 'ui:derp': 'xerp', - }, - }, - }; - const expectedSchema = { - type: 'object', - anyOf: [ - { - properties: { - field3: { - type: 'string', - default: 'Value 1', - }, - }, - }, - { - properties: { - field3: { - type: 'string', - default: 'Value 2', - }, - }, - }, - ], - oneOf: [ - { - properties: { - field4: { - type: 'string', - default: 'Value 1', - }, - }, - }, - ], - allOf: [ - { - properties: { - field5: { - type: 'string', - default: 'Value 1', - }, - }, - }, - ], - properties: { - field1: { - type: 'object', - anyOf: [ - { - properties: { - field3: { - type: 'string', - default: 'Value 1', - }, - }, - }, - { - properties: { - field3: { - type: 'string', - default: 'Value 2', - }, - }, - }, - ], - oneOf: [ - { - properties: { - field4: { - type: 'string', - default: 'Value 1', - }, - }, - }, - ], - allOf: [ - { - properties: { - field5: { - type: 'string', - default: 'Value 1', - }, - }, - }, - ], - }, - field2: { - type: 'string', - }, - }, - }; - const expectedUiSchema = { - field3: { - 'ui:readonly': true, - }, - field4: { - 'ui:readonly': true, - }, - field5: { - 'ui:readonly': true, - }, - field1: { - field3: { - 'ui:readonly': true, - }, - field4: { - 'ui:readonly': true, - }, - field5: { - 'ui:readonly': true, - }, - }, - field2: { - 'ui:derp': 'xerp', - }, - }; - - expect(transformSchemaToProps(inputSchema)).toEqual({ - schema: expectedSchema, - uiSchema: expectedUiSchema, - }); - }); - - it('transforms schema with dependencies', () => { - const inputSchema = { - type: 'object', - properties: { - name: { - type: 'string', - }, - credit_card: { - type: 'number', - }, - }, - required: ['name'], - dependencies: { - credit_card: { - properties: { - billing_address: { - type: 'string', - 'ui:widget': 'textarea', - }, - }, - required: ['billing_address'], - }, - }, - }; - const expectedSchema = { - type: 'object', - properties: { - name: { - type: 'string', - }, - credit_card: { - type: 'number', - }, - }, - required: ['name'], - dependencies: { - credit_card: { - properties: { - billing_address: { - type: 'string', - }, - }, - required: ['billing_address'], - }, - }, - }; - const expectedUiSchema = { - billing_address: { - 'ui:widget': 'textarea', - }, - credit_card: {}, - name: {}, - }; - - expect(transformSchemaToProps(inputSchema)).toEqual({ - schema: expectedSchema, - uiSchema: expectedUiSchema, - }); - }); - - it('transforms schema with complex dependencies', () => { - const inputSchema = { - type: 'object', - properties: { - conditional: { - title: 'Person', - type: 'object', - properties: { - 'Do you have any pets?': { - type: 'string', - enum: ['No', 'Yes: One', 'Yes: More than one'], - default: 'No', - 'ui:widget': 'radio', - }, - }, - required: ['Do you have any pets?'], - dependencies: { - 'Do you have any pets?': { - oneOf: [ - { - properties: { - 'Do you have any pets?': { - enum: ['No'], - }, - }, - }, - { - properties: { - 'Do you have any pets?': { - enum: ['Yes: One'], - }, - 'How old is your pet?': { - type: 'number', - }, - }, - required: ['How old is your pet?'], - }, - { - properties: { - 'Do you have any pets?': { - enum: ['Yes: More than one'], - }, - 'Do you want to get rid of any?': { - type: 'boolean', - }, - }, - required: ['Do you want to get rid of any?'], - }, - ], - }, - }, - }, - }, - }; - const expectedSchema = { - type: 'object', - properties: { - conditional: { - title: 'Person', - type: 'object', - properties: { - 'Do you have any pets?': { - type: 'string', - enum: ['No', 'Yes: One', 'Yes: More than one'], - default: 'No', - }, - }, - required: ['Do you have any pets?'], - dependencies: { - 'Do you have any pets?': { - oneOf: [ - { - properties: { - 'Do you have any pets?': { - enum: ['No'], - }, - }, - }, - { - properties: { - 'Do you have any pets?': { - enum: ['Yes: One'], - }, - 'How old is your pet?': { - type: 'number', - }, - }, - required: ['How old is your pet?'], - }, - { - properties: { - 'Do you have any pets?': { - enum: ['Yes: More than one'], - }, - 'Do you want to get rid of any?': { - type: 'boolean', - }, - }, - required: ['Do you want to get rid of any?'], - }, - ], - }, - }, - }, - }, - }; - const expectedUiSchema = { - conditional: { - 'Do you have any pets?': { - 'ui:widget': 'radio', - }, - 'Do you want to get rid of any?': {}, - 'How old is your pet?': {}, - }, - }; - - expect(transformSchemaToProps(inputSchema)).toEqual({ - schema: expectedSchema, - uiSchema: expectedUiSchema, - }); - }); - - it('transforms schema with array items', () => { - const inputSchema = { - type: 'object', - properties: { - person: { - type: 'array', - items: { - type: 'object', - properties: { - name: { - type: 'string', - }, - address: { - type: 'string', - 'ui:widget': 'textarea', - }, - }, - }, - }, - accountNumber: { - type: 'number', - }, - }, - }; - const expectedSchema = { - type: 'object', - properties: { - person: { - type: 'array', - items: { - type: 'object', - properties: { - name: { - type: 'string', - }, - address: { - type: 'string', - }, - }, - }, - }, - accountNumber: { - type: 'number', - }, - }, - }; - const expectedUiSchema = { - accountNumber: {}, - person: { - items: { - name: {}, - address: { - 'ui:widget': 'textarea', - }, - }, - }, - }; - - expect(transformSchemaToProps(inputSchema)).toEqual({ - schema: expectedSchema, - uiSchema: expectedUiSchema, - }); - }); -}); diff --git a/plugins/scaffolder/src/legacy/MultistepJsonForm/schema.ts b/plugins/scaffolder/src/legacy/MultistepJsonForm/schema.ts deleted file mode 100644 index e6221cb8d5..0000000000 --- a/plugins/scaffolder/src/legacy/MultistepJsonForm/schema.ts +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { JsonObject } from '@backstage/types'; -import { UiSchema } from '@rjsf/utils'; -import type { LayoutOptions } from '@backstage/plugin-scaffolder-react'; -import { FormProps } from '@rjsf/core'; - -function isObject(value: unknown): value is JsonObject { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -function extractUiSchema(schema: JsonObject, uiSchema: JsonObject) { - if (!isObject(schema)) { - return; - } - - const { properties, items, anyOf, oneOf, allOf, dependencies } = schema; - - for (const propName in schema) { - if (!schema.hasOwnProperty(propName)) { - continue; - } - - if (propName.startsWith('ui:')) { - uiSchema[propName] = schema[propName]; - delete schema[propName]; - } - } - - if (isObject(properties)) { - for (const propName in properties) { - if (!properties.hasOwnProperty(propName)) { - continue; - } - - const schemaNode = properties[propName]; - if (!isObject(schemaNode)) { - continue; - } - const innerUiSchema = {}; - uiSchema[propName] = uiSchema[propName] || innerUiSchema; - extractUiSchema(schemaNode, innerUiSchema); - } - } - - if (isObject(items)) { - const innerUiSchema = {}; - uiSchema.items = innerUiSchema; - extractUiSchema(items, innerUiSchema); - } - - if (Array.isArray(anyOf)) { - for (const schemaNode of anyOf) { - if (!isObject(schemaNode)) { - continue; - } - extractUiSchema(schemaNode, uiSchema); - } - } - - if (Array.isArray(oneOf)) { - for (const schemaNode of oneOf) { - if (!isObject(schemaNode)) { - continue; - } - extractUiSchema(schemaNode, uiSchema); - } - } - - if (Array.isArray(allOf)) { - for (const schemaNode of allOf) { - if (!isObject(schemaNode)) { - continue; - } - extractUiSchema(schemaNode, uiSchema); - } - } - - if (isObject(dependencies)) { - for (const depName of Object.keys(dependencies)) { - const schemaNode = dependencies[depName]; - if (!isObject(schemaNode)) { - continue; - } - extractUiSchema(schemaNode, uiSchema); - } - } -} - -export function transformSchemaToProps( - inputSchema: JsonObject, - layouts: LayoutOptions[] = [], -): { - schema: FormProps['schema']; - uiSchema: FormProps['uiSchema']; -} { - const customLayoutName = inputSchema['ui:ObjectFieldTemplate']; - inputSchema.type = inputSchema.type || 'object'; - const schema = JSON.parse(JSON.stringify(inputSchema)); - delete schema.title; // Rendered separately - const uiSchema: UiSchema = {}; - extractUiSchema(schema, uiSchema); - - if (customLayoutName) { - const Layout = layouts.find( - layout => layout.name === customLayoutName, - )?.component; - - if (Layout) { - uiSchema['ui:ObjectFieldTemplate'] = Layout; - } - } - - return { schema, uiSchema }; -} diff --git a/plugins/scaffolder/src/legacy/Router.tsx b/plugins/scaffolder/src/legacy/Router.tsx deleted file mode 100644 index ac183b9470..0000000000 --- a/plugins/scaffolder/src/legacy/Router.tsx +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { ComponentType, useEffect, PropsWithChildren } from 'react'; -import { Navigate, Route, Routes, useOutlet } from 'react-router-dom'; -import { Entity } from '@backstage/catalog-model'; -import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { ScaffolderPage } from './ScaffolderPage'; -import { TemplatePage } from './TemplatePage'; -import { TaskPage } from './TaskPage'; -import { ActionsPage } from '../components/ActionsPage'; -import { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from '../extensions/default'; -import { useRouteRef, useRouteRefParams } from '@backstage/core-plugin-api'; -import { LegacyFieldExtensionOptions } from '@backstage/plugin-scaffolder-react/alpha'; -import { - ReviewStepProps, - SecretsContextProvider, - useCustomFieldExtensions, - useCustomLayouts, -} from '@backstage/plugin-scaffolder-react'; -import { ListTasksPage } from '../components/ListTasksPage'; -import { - actionsRouteRef, - editRouteRef, - legacySelectedTemplateRouteRef, - scaffolderListTaskRouteRef, - scaffolderTaskRouteRef, - selectedTemplateRouteRef, -} from '../routes'; -import { TemplateEditorPage } from './TemplateEditorPage'; - -/** - * The props for the entrypoint `ScaffolderPage` component the plugin. - * @alpha - */ -export type LegacyRouterProps = { - components?: { - ReviewStepComponent?: ComponentType; - TemplateCardComponent?: - | ComponentType<{ template: TemplateEntityV1beta3 }> - | undefined; - TaskPageComponent?: ComponentType>; - }; - groups?: Array<{ - title?: React.ReactNode; - filter: (entity: Entity) => boolean; - }>; - templateFilter?: (entity: TemplateEntityV1beta3) => boolean; - defaultPreviewTemplate?: string; - headerOptions?: { - pageTitleOverride?: string; - title?: string; - subtitle?: string; - }; - /** - * Options for the context menu on the scaffolder page. - */ - contextMenu?: { - /** Whether to show a link to the template editor */ - editor?: boolean; - /** Whether to show a link to the actions documentation */ - actions?: boolean; - }; -}; - -/** - * The legacy router - * - * @alpha - */ -export const LegacyRouter = (props: LegacyRouterProps) => { - const { - groups, - templateFilter, - components = {}, - defaultPreviewTemplate, - } = props; - - const { ReviewStepComponent, TemplateCardComponent, TaskPageComponent } = - components; - - const outlet = useOutlet(); - const TaskPageElement = TaskPageComponent ?? TaskPage; - - const customFieldExtensions = - useCustomFieldExtensions(outlet); - - const fieldExtensions = [ - ...customFieldExtensions, - ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter( - ({ name }) => - !customFieldExtensions.some( - customFieldExtension => customFieldExtension.name === name, - ), - ), - ] as LegacyFieldExtensionOptions[]; - - const customLayouts = useCustomLayouts(outlet); - - /** - * This component can be deleted once the older routes have been deprecated. - */ - const RedirectingComponent = () => { - const { templateName } = useRouteRefParams(legacySelectedTemplateRouteRef); - const newLink = useRouteRef(selectedTemplateRouteRef); - useEffect( - () => - // eslint-disable-next-line no-console - console.warn( - 'The route /template/:templateName is deprecated, please use the new /template/:namespace/:templateName route instead', - ), - [], - ); - return ; - }; - - return ( - - - } - /> - } - /> - - -
    - } - /> - } - /> - } /> - } /> - - - - } - /> - - } /> - - ); -}; diff --git a/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPage.tsx deleted file mode 100644 index 54122eddf0..0000000000 --- a/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPage.tsx +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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 { - Content, - ContentHeader, - CreateButton, - Header, - Page, - SupportButton, -} from '@backstage/core-components'; -import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { useRouteRef } from '@backstage/core-plugin-api'; -import { - CatalogFilterLayout, - EntityKindPicker, - EntityListProvider, - EntitySearchBar, - EntityTagPicker, - UserListPicker, -} from '@backstage/plugin-catalog-react'; -import React, { ComponentType } from 'react'; -import { TemplateList } from '../TemplateList'; -import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha'; -import { usePermission } from '@backstage/plugin-permission-react'; -import { ScaffolderPageContextMenu } from './ScaffolderPageContextMenu'; -import { registerComponentRouteRef } from '../../routes'; -import { TemplateTypePicker } from '../../components'; - -export type ScaffolderPageProps = { - TemplateCardComponent?: - | ComponentType<{ template: TemplateEntityV1beta3 }> - | undefined; - groups?: Array<{ - title?: React.ReactNode; - filter: (entity: TemplateEntityV1beta3) => boolean; - }>; - templateFilter?: (entity: TemplateEntityV1beta3) => boolean; - contextMenu?: { - editor?: boolean; - actions?: boolean; - tasks?: boolean; - }; - headerOptions?: { - pageTitleOverride?: string; - title?: string; - subtitle?: string; - }; -}; - -export const ScaffolderPageContents = ({ - TemplateCardComponent, - groups, - templateFilter, - contextMenu, - headerOptions, -}: ScaffolderPageProps) => { - const registerComponentLink = useRouteRef(registerComponentRouteRef); - const otherTemplatesGroup = { - title: groups ? 'Other Templates' : 'Templates', - filter: (entity: TemplateEntityV1beta3) => { - const filtered = (groups ?? []).map(group => group.filter(entity)); - return !filtered.some(result => result === true); - }, - }; - - const { allowed } = usePermission({ - permission: catalogEntityCreatePermission, - }); - - return ( - -
    - -
    - - - {allowed && ( - - )} - - Create new software components using standard templates. Different - templates create different kinds of components (services, websites, - documentation, ...). - - - - - - - - - {groups && - groups.map((group, index) => ( - - ))} - - - - -
    - ); -}; - -export const ScaffolderPage = ({ - TemplateCardComponent, - groups, - templateFilter, - contextMenu, - headerOptions, -}: ScaffolderPageProps) => ( - - - -); diff --git a/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPageContextMenu.test.tsx b/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPageContextMenu.test.tsx deleted file mode 100644 index 66513591d0..0000000000 --- a/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPageContextMenu.test.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 userEvent from '@testing-library/user-event'; -import { renderInTestApp } from '@backstage/test-utils'; -import { screen } from '@testing-library/react'; -import React from 'react'; -import { ScaffolderPageContextMenu } from './ScaffolderPageContextMenu'; -import { rootRouteRef } from '../../routes'; - -describe('ScaffolderPageContextMenu', () => { - it('does not render anything if fully disabled', async () => { - await renderInTestApp( -
    - -
    , - { mountedRoutes: { '/': rootRouteRef } }, - ); - - expect(screen.getByTestId('container')).toBeEmptyDOMElement(); - }); - - it('renders the editor option', async () => { - await renderInTestApp( -
    - -
    , - { - mountedRoutes: { '/': rootRouteRef }, - }, - ); - - await userEvent.click(screen.getByTestId('container').firstElementChild!); - - expect(screen.getByText('Template Editor')).toBeInTheDocument(); - expect(screen.queryByText('Installed Actions')).not.toBeInTheDocument(); - }); - - it('renders the actions option', async () => { - await renderInTestApp( -
    - -
    , - { - mountedRoutes: { '/': rootRouteRef }, - }, - ); - - await userEvent.click(screen.getByTestId('container').firstElementChild!); - - expect(screen.queryByText('Template Editor')).not.toBeInTheDocument(); - expect(screen.getByText('Installed Actions')).toBeInTheDocument(); - }); - - it('renders all options', async () => { - await renderInTestApp( -
    - -
    , - { - mountedRoutes: { '/': rootRouteRef }, - }, - ); - - await userEvent.click(screen.getByTestId('container').firstElementChild!); - - expect(screen.getByText('Template Editor')).toBeInTheDocument(); - expect(screen.getByText('Installed Actions')).toBeInTheDocument(); - }); -}); diff --git a/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPageContextMenu.tsx b/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPageContextMenu.tsx deleted file mode 100644 index f6151c227b..0000000000 --- a/plugins/scaffolder/src/legacy/ScaffolderPage/ScaffolderPageContextMenu.tsx +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 { useRouteRef } from '@backstage/core-plugin-api'; -import IconButton from '@material-ui/core/IconButton'; -import ListItemIcon from '@material-ui/core/ListItemIcon'; -import ListItemText from '@material-ui/core/ListItemText'; -import MenuItem from '@material-ui/core/MenuItem'; -import MenuList from '@material-ui/core/MenuList'; -import Popover from '@material-ui/core/Popover'; -import { makeStyles } from '@material-ui/core/styles'; -import Description from '@material-ui/icons/Description'; -import Edit from '@material-ui/icons/Edit'; -import List from '@material-ui/icons/List'; -import MoreVert from '@material-ui/icons/MoreVert'; -import React, { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { - actionsRouteRef, - editRouteRef, - scaffolderListTaskRouteRef, -} from '../../routes'; - -const useStyles = makeStyles(theme => ({ - button: { - color: theme.page.fontColor, - }, -})); - -export type ScaffolderPageContextMenuProps = { - editor?: boolean; - actions?: boolean; - tasks?: boolean; -}; - -export function ScaffolderPageContextMenu( - props: ScaffolderPageContextMenuProps, -) { - const classes = useStyles(); - const [anchorEl, setAnchorEl] = useState(); - const editLink = useRouteRef(editRouteRef); - const actionsLink = useRouteRef(actionsRouteRef); - const tasksLink = useRouteRef(scaffolderListTaskRouteRef); - - const navigate = useNavigate(); - - const showEditor = props.editor !== false; - const showActions = props.actions !== false; - const showTasks = props.tasks !== false; - - if (!showEditor && !showActions) { - return null; - } - - const onOpen = (event: React.SyntheticEvent) => { - setAnchorEl(event.currentTarget); - }; - - const onClose = () => { - setAnchorEl(undefined); - }; - - return ( - <> - - - - - - {showEditor && ( - navigate(editLink())}> - - - - - - )} - {showActions && ( - navigate(actionsLink())}> - - - - - - )} - {showTasks && ( - navigate(tasksLink())}> - - - - - - )} - - - - ); -} diff --git a/plugins/scaffolder/src/legacy/TaskPage/TaskErrors.tsx b/plugins/scaffolder/src/legacy/TaskPage/TaskErrors.tsx deleted file mode 100644 index 4f8ce974c8..0000000000 --- a/plugins/scaffolder/src/legacy/TaskPage/TaskErrors.tsx +++ /dev/null @@ -1,39 +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. - */ -import { Box } from '@material-ui/core'; -import React, { useEffect, useRef } from 'react'; -import { DismissableBanner } from '@backstage/core-components'; - -type TaskErrorsProps = { - error?: Error; -}; - -export const TaskErrors = ({ error }: TaskErrorsProps) => { - const id = useRef(''); - - useEffect(() => { - id.current = String(Math.random()); - }, [error]); - return error ? ( - - - - ) : null; -}; diff --git a/plugins/scaffolder/src/legacy/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/legacy/TaskPage/TaskPage.tsx deleted file mode 100644 index affb9a31b7..0000000000 --- a/plugins/scaffolder/src/legacy/TaskPage/TaskPage.tsx +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { parseEntityRef } from '@backstage/catalog-model'; -import { - Content, - ErrorPage, - Header, - LogViewer, - Page, - Progress, -} from '@backstage/core-components'; -import { - useApi, - useRouteRef, - useRouteRefParams, -} from '@backstage/core-plugin-api'; -import { - Button, - CircularProgress, - Paper, - StepButton, - StepIconProps, -} from '@material-ui/core'; -import Grid from '@material-ui/core/Grid'; -import Step from '@material-ui/core/Step'; -import StepLabel from '@material-ui/core/StepLabel'; -import Stepper from '@material-ui/core/Stepper'; -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; -import Typography from '@material-ui/core/Typography'; -import Cancel from '@material-ui/icons/Cancel'; -import Check from '@material-ui/icons/Check'; -import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; -import classNames from 'classnames'; -import { DateTime, Interval } from 'luxon'; -import qs from 'qs'; -import React, { memo, useEffect, useMemo, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import useInterval from 'react-use/lib/useInterval'; -import { - ScaffolderTaskStatus, - ScaffolderTaskOutput, - useTaskEventStream, -} from '@backstage/plugin-scaffolder-react'; -import { TaskErrors } from './TaskErrors'; -import { TaskPageLinks } from './TaskPageLinks'; -import { - rootRouteRef, - scaffolderTaskRouteRef, - selectedTemplateRouteRef, -} from '../../routes'; -import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; -import humanizeDuration from 'humanize-duration'; - -const useStyles = makeStyles((theme: Theme) => - createStyles({ - root: { - width: '100%', - }, - button: { - marginBottom: theme.spacing(2), - marginLeft: theme.spacing(2), - }, - actionsContainer: { - marginBottom: theme.spacing(2), - }, - resetContainer: { - padding: theme.spacing(3), - }, - labelWrapper: { - display: 'flex', - flex: 1, - flexDirection: 'row', - justifyContent: 'space-between', - }, - stepWrapper: { - width: '100%', - }, - }), -); - -type TaskStep = { - id: string; - name: string; - status: ScaffolderTaskStatus; - startedAt?: string; - endedAt?: string; -}; - -const StepTimeTicker = ({ step }: { step: TaskStep }) => { - const [time, setTime] = useState(''); - - useInterval(() => { - if (!step.startedAt) { - setTime(''); - return; - } - - const end = step.endedAt - ? DateTime.fromISO(step.endedAt) - : DateTime.local(); - - const startedAt = DateTime.fromISO(step.startedAt); - const formatted = Interval.fromDateTimes(startedAt, end) - .toDuration() - .valueOf(); - - setTime(humanizeDuration(formatted, { round: true })); - }, 1000); - - return {time}; -}; - -const useStepIconStyles = makeStyles(theme => - createStyles({ - root: { - color: theme.palette.text.disabled, - display: 'flex', - height: 22, - alignItems: 'center', - }, - completed: { - color: theme.palette.status.ok, - }, - error: { - color: theme.palette.status.error, - }, - }), -); - -function TaskStepIconComponent(props: StepIconProps) { - const classes = useStepIconStyles(); - const { active, completed, error } = props; - - const getMiddle = () => { - if (active) { - return ; - } - if (completed) { - return ; - } - if (error) { - return ; - } - return ; - }; - - return ( -
    - {getMiddle()} -
    - ); -} - -export const TaskStatusStepper = memo( - (props: { - steps: TaskStep[]; - currentStepId: string | undefined; - onUserStepChange: (id: string) => void; - classes?: { - root?: string; - }; - }) => { - const { steps, currentStepId, onUserStepChange } = props; - const classes = useStyles(props); - - return ( -
    - s.id === currentStepId)} - orientation="vertical" - nonLinear - > - {steps.map((step, index) => { - const isCancelled = step.status === 'cancelled'; - const isActive = step.status === 'processing'; - const isCompleted = step.status === 'completed'; - const isFailed = step.status === 'failed'; - const isSkipped = step.status === 'skipped'; - - return ( - - onUserStepChange(step.id)}> - -
    - {step.name} - {isSkipped ? ( - Skipped - ) : ( - - )} -
    -
    -
    -
    - ); - })} -
    -
    - ); - }, -); - -const hasLinks = ({ links = [] }: ScaffolderTaskOutput): boolean => - links.length > 0; - -/** - * TaskPageProps for constructing a TaskPage - * @param loadingText - Optional loading text shown before a task begins executing. - * @public - * @deprecated - this is a useless type that is no longer used. - */ -export type TaskPageProps = { - loadingText?: string; -}; - -/** - * TaskPage for showing the status of the taskId provided as a param - * - * @alpha - */ -export const TaskPage = (props: TaskPageProps) => { - const { loadingText } = props; - const classes = useStyles(); - const navigate = useNavigate(); - const rootPath = useRouteRef(rootRouteRef); - const scaffolderApi = useApi(scaffolderApiRef); - const templateRoute = useRouteRef(selectedTemplateRouteRef); - const [userSelectedStepId, setUserSelectedStepId] = useState< - string | undefined - >(undefined); - const [clickedToCancel, setClickedToCancel] = useState(false); - const [lastActiveStepId, setLastActiveStepId] = useState( - undefined, - ); - const { taskId } = useRouteRefParams(scaffolderTaskRouteRef); - const taskStream = useTaskEventStream(taskId); - const completed = taskStream.completed; - const taskCancelled = taskStream.cancelled; - const steps = useMemo( - () => - taskStream.task?.spec.steps.map(step => ({ - ...step, - ...taskStream?.steps?.[step.id], - })) ?? [], - [taskStream], - ); - - useEffect(() => { - const mostRecentFailedOrActiveStep = steps.find(step => - ['failed', 'processing'].includes(step.status), - ); - if (completed && !mostRecentFailedOrActiveStep) { - setLastActiveStepId(steps[steps.length - 1]?.id); - return; - } - - setLastActiveStepId(mostRecentFailedOrActiveStep?.id); - }, [steps, completed]); - - const currentStepId = userSelectedStepId ?? lastActiveStepId; - - const logAsString = useMemo(() => { - if (!currentStepId) { - return loadingText ? loadingText : 'Loading...'; - } - const log = taskStream.stepLogs[currentStepId]; - - if (!log?.length) { - return 'Waiting for logs...'; - } - return log.join('\n'); - }, [taskStream.stepLogs, currentStepId, loadingText]); - - const taskNotFound = - taskStream.completed && !taskStream.loading && !taskStream.task; - - const { output } = taskStream; - - const handleStartOver = () => { - if (!taskStream.task || !taskStream.task?.spec.templateInfo?.entityRef) { - navigate(rootPath()); - return; - } - - const formData = taskStream.task!.spec.parameters; - - const { name, namespace } = parseEntityRef( - taskStream.task!.spec.templateInfo?.entityRef, - ); - - navigate( - `${templateRoute({ templateName: name, namespace })}?${qs.stringify({ - formData: JSON.stringify(formData), - })}`, - ); - }; - - const handleCancel = async () => { - setClickedToCancel(true); - await scaffolderApi.cancelTask(taskId); - }; - - return ( - -
    - - {taskNotFound ? ( - - ) : ( -
    - - - - - {output && hasLinks(output) && ( - - )} - - - - - - {!currentStepId && } - -
    - - -
    -
    -
    -
    - )} -
    - - ); -}; diff --git a/plugins/scaffolder/src/legacy/TaskPage/TaskPageLinks.test.tsx b/plugins/scaffolder/src/legacy/TaskPage/TaskPageLinks.test.tsx deleted file mode 100644 index 2f5d015f8d..0000000000 --- a/plugins/scaffolder/src/legacy/TaskPage/TaskPageLinks.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 { entityRouteRef } from '@backstage/plugin-catalog-react'; -import { renderInTestApp } from '@backstage/test-utils'; -import React from 'react'; -import { TaskPageLinks } from './TaskPageLinks'; - -describe('TaskPageLinks', () => { - beforeEach(() => {}); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('renders further links', async () => { - const output = { - links: [ - { url: 'https://first.url', title: 'Cool link 1' }, - { url: 'https://second.url', title: 'Cool link 2' }, - { entityRef: 'Component:default/my-app', title: 'Open in catalog' }, - { title: 'Skipped' }, - ], - }; - const { findByText, queryByText } = await renderInTestApp( - , - { - mountedRoutes: { - '/catalog/:namespace/:kind/:name/*': entityRouteRef, - }, - }, - ); - - let element = await findByText('Cool link 1'); - - expect(element).toBeInTheDocument(); - expect(element).toHaveAttribute('href', 'https://first.url'); - - element = await findByText('Cool link 2'); - - expect(element).toBeInTheDocument(); - expect(element).toHaveAttribute('href', 'https://second.url'); - - element = await findByText('Open in catalog'); - - expect(element).toBeInTheDocument(); - expect(element).toHaveAttribute( - 'href', - '/catalog/default/Component/my-app', - ); - - expect(queryByText('Skipped')).not.toBeInTheDocument(); - }); -}); diff --git a/plugins/scaffolder/src/legacy/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/legacy/TemplateCard/TemplateCard.tsx deleted file mode 100644 index 80f719bd36..0000000000 --- a/plugins/scaffolder/src/legacy/TemplateCard/TemplateCard.tsx +++ /dev/null @@ -1,316 +0,0 @@ -/* - * 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 { - DEFAULT_NAMESPACE, - Entity, - EntityLink, - parseEntityRef, - RELATION_OWNED_BY, - stringifyEntityRef, -} from '@backstage/catalog-model'; -import { - ItemCardHeader, - Link, - LinkButton, - MarkdownContent, -} from '@backstage/core-components'; -import { - IconComponent, - useApi, - useApp, - useRouteRef, -} from '@backstage/core-plugin-api'; -import { - ScmIntegrationIcon, - scmIntegrationsApiRef, -} from '@backstage/integration-react'; -import { - EntityRefLinks, - FavoriteEntity, - getEntityRelations, - getEntitySourceLocation, -} from '@backstage/plugin-catalog-react'; -import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { - Box, - Card, - CardActions, - CardContent, - CardMedia, - Chip, - IconButton, - makeStyles, - Theme, - Tooltip, - Typography, - useTheme, -} from '@material-ui/core'; -import LanguageIcon from '@material-ui/icons/Language'; -import WarningIcon from '@material-ui/icons/Warning'; -import React from 'react'; -import { selectedTemplateRouteRef, viewTechDocRouteRef } from '../../routes'; - -const useStyles = makeStyles< - Theme, - { fontColor: string; backgroundImage: string } ->(theme => ({ - cardHeader: { - position: 'relative', - }, - title: { - backgroundImage: ({ backgroundImage }) => backgroundImage, - color: ({ fontColor }) => fontColor, - }, - box: { - overflow: 'hidden', - textOverflow: 'ellipsis', - display: '-webkit-box', - '-webkit-line-clamp': 10, - '-webkit-box-orient': 'vertical', - }, - label: { - color: theme.palette.text.secondary, - textTransform: 'uppercase', - fontSize: '0.65rem', - fontWeight: 'bold', - letterSpacing: 0.5, - lineHeight: 1, - paddingBottom: '0.2rem', - }, - linksLabel: { - padding: '0 16px', - }, - description: { - '& p': { - margin: '0px', - }, - }, - leftButton: { - marginRight: 'auto', - }, - starButton: { - position: 'absolute', - top: theme.spacing(0.5), - right: theme.spacing(0.5), - padding: '0.25rem', - color: ({ fontColor }) => fontColor, - }, -})); - -const MuiIcon = ({ icon: Icon }: { icon: IconComponent }) => ; - -const useDeprecationStyles = makeStyles(theme => ({ - deprecationIcon: { - position: 'absolute', - top: theme.spacing(0.5), - right: theme.spacing(3.5), - padding: '0.25rem', - }, - link: { - color: theme.palette.warning.light, - }, -})); - -export type TemplateCardProps = { - template: TemplateEntityV1beta3; - deprecated?: boolean; -}; - -type TemplateProps = { - description: string; - tags: string[]; - title: string; - type: string; - name: string; - links: EntityLink[]; -}; - -const getTemplateCardProps = ( - template: TemplateEntityV1beta3, -): TemplateProps & { key: string } => { - return { - key: template.metadata.uid!, - name: template.metadata.name, - title: `${(template.metadata.title || template.metadata.name) ?? ''}`, - type: template.spec.type ?? '', - description: template.metadata.description ?? '-', - tags: (template.metadata?.tags as string[]) ?? [], - links: template.metadata.links ?? [], - }; -}; - -const DeprecationWarning = () => { - const styles = useDeprecationStyles(); - - const Title = ( - - This template uses a syntax that has been deprecated, and should be - migrated to a newer syntax. Click for more info. - - ); - - return ( -
    - - - - - -
    - ); -}; - -export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { - const app = useApp(); - const backstageTheme = useTheme(); - const templateRoute = useRouteRef(selectedTemplateRouteRef); - const templateProps = getTemplateCardProps(template); - const ownedByRelations = getEntityRelations( - template as Entity, - RELATION_OWNED_BY, - ); - const themeId = backstageTheme.getPageTheme({ themeId: templateProps.type }) - ? templateProps.type - : 'other'; - const theme = backstageTheme.getPageTheme({ themeId }); - const classes = useStyles({ - fontColor: theme.fontColor, - backgroundImage: theme.backgroundImage, - }); - const { name, namespace } = parseEntityRef(stringifyEntityRef(template)); - const href = templateRoute({ templateName: name, namespace }); - - // TechDocs Link - const viewTechDoc = useRouteRef(viewTechDocRouteRef); - const viewTechDocsAnnotation = - template.metadata.annotations?.['backstage.io/techdocs-ref']; - const viewTechDocsLink = - !!viewTechDocsAnnotation && - !!viewTechDoc && - viewTechDoc({ - namespace: template.metadata.namespace || DEFAULT_NAMESPACE, - kind: template.kind, - name: template.metadata.name, - }); - - const iconResolver = (key?: string): IconComponent => - key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon; - - const scmIntegrationsApi = useApi(scmIntegrationsApiRef); - const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi); - - return ( - - - - {deprecated && } - - - - - - Description - - - - - - Owner - - - - - - Tags - - {templateProps.tags?.map(tag => ( - - ))} - - - - Links - - -
    - {sourceLocation && ( - - - - - - )} - {viewTechDocsLink && ( - - - - - - )} - {templateProps.links?.map((link, i) => ( - - - - - - ))} -
    - - Choose - -
    -
    - ); -}; diff --git a/plugins/scaffolder/src/legacy/TemplateCard/index.ts b/plugins/scaffolder/src/legacy/TemplateCard/index.ts deleted file mode 100644 index 291a06ed69..0000000000 --- a/plugins/scaffolder/src/legacy/TemplateCard/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { TemplateCard } from './TemplateCard'; -export type { TemplateCardProps } from './TemplateCard'; diff --git a/plugins/scaffolder/src/legacy/TemplateEditorPage/CustomFieldExplorer.tsx b/plugins/scaffolder/src/legacy/TemplateEditorPage/CustomFieldExplorer.tsx deleted file mode 100644 index 9107ac67eb..0000000000 --- a/plugins/scaffolder/src/legacy/TemplateEditorPage/CustomFieldExplorer.tsx +++ /dev/null @@ -1,202 +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. - */ -import { StreamLanguage } from '@codemirror/language'; -import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; -import { - Button, - Card, - CardContent, - CardHeader, - FormControl, - IconButton, - InputLabel, - makeStyles, - MenuItem, - Select, -} from '@material-ui/core'; -import CloseIcon from '@material-ui/icons/Close'; -import CodeMirror from '@uiw/react-codemirror'; -import React, { useCallback, useMemo, useState } from 'react'; -import yaml from 'yaml'; -import { Form } from '@backstage/plugin-scaffolder-react/alpha'; -import { TemplateEditorForm } from './TemplateEditorForm'; -import validator from '@rjsf/validator-ajv8'; -import { LegacyFieldExtensionOptions } from '@backstage/plugin-scaffolder-react/alpha'; - -const useStyles = makeStyles(theme => ({ - root: { - gridArea: 'pageContent', - display: 'grid', - gridTemplateAreas: ` - "controls controls" - "fieldForm preview" - `, - gridTemplateRows: 'auto 1fr', - gridTemplateColumns: '1fr 1fr', - }, - controls: { - gridArea: 'controls', - display: 'flex', - flexFlow: 'row nowrap', - alignItems: 'center', - margin: theme.spacing(1), - }, - fieldForm: { - gridArea: 'fieldForm', - }, - preview: { - gridArea: 'preview', - }, -})); - -export const CustomFieldExplorer = ({ - customFieldExtensions = [], - onClose, -}: { - customFieldExtensions?: LegacyFieldExtensionOptions[]; - onClose?: () => void; -}) => { - const classes = useStyles(); - const fieldOptions = customFieldExtensions.filter(field => !!field.schema); - const [selectedField, setSelectedField] = useState(fieldOptions[0]); - const [fieldFormState, setFieldFormState] = useState({}); - const [refreshKey, setRefreshKey] = useState(Date.now()); - const [formState, setFormState] = useState({}); - const sampleFieldTemplate = useMemo( - () => - yaml.stringify({ - parameters: [ - { - title: `${selectedField.name} Example`, - properties: { - [selectedField.name]: { - type: selectedField.schema?.returnValue?.type, - 'ui:field': selectedField.name, - 'ui:options': fieldFormState, - }, - }, - }, - ], - }), - [fieldFormState, selectedField], - ); - - const fieldComponents = useMemo(() => { - return Object.fromEntries( - customFieldExtensions.map(({ name, component }) => [name, component]), - ); - }, [customFieldExtensions]); - - const handleSelectionChange = useCallback( - (selection: LegacyFieldExtensionOptions) => { - setSelectedField(selection); - setFieldFormState({}); - setFormState({}); - }, - [setFieldFormState, setSelectedField, setFormState], - ); - - const handleFieldConfigChange = useCallback( - (state: {}) => { - setFieldFormState(state); - // Force TemplateEditorForm to re-render since some fields - // may not be responsive to ui:option changes - setRefreshKey(Date.now()); - }, - [setFieldFormState, setRefreshKey], - ); - - return ( -
    -
    - - - Choose Custom Field Extension - - - - - - - -
    -
    - - - -
    handleFieldConfigChange(e.formData)} - validator={validator} - schema={selectedField.schema?.uiOptions || {}} - > - -
    -
    -
    -
    -
    - - - - - - - null} - /> -
    -
    - ); -}; diff --git a/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateEditor.tsx b/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateEditor.tsx deleted file mode 100644 index f6e7b38d7d..0000000000 --- a/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateEditor.tsx +++ /dev/null @@ -1,92 +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. - */ -import { makeStyles } from '@material-ui/core'; -import React, { useState } from 'react'; -import { LegacyFieldExtensionOptions } from '@backstage/plugin-scaffolder-react/alpha'; -import { TemplateDirectoryAccess } from '../../lib/filesystem'; -import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; -import { DirectoryEditorProvider } from '../../next/TemplateEditorPage/DirectoryEditorContext'; -import { DryRunProvider } from '../../next/TemplateEditorPage/DryRunContext'; -import { TemplateEditorBrowser } from '../../next/TemplateEditorPage/TemplateEditorBrowser'; -import { TemplateEditorTextArea } from '../../next/TemplateEditorPage/TemplateEditorTextArea'; -import { TemplateEditorForm } from './TemplateEditorForm'; -import { DryRunResults } from '../../next/TemplateEditorPage/DryRunResults'; - -const useStyles = makeStyles({ - // Reset and fix sizing to make sure scrolling behaves correctly - root: { - gridArea: 'pageContent', - - display: 'grid', - gridTemplateAreas: ` - "browser editor preview" - "results results results" - `, - gridTemplateColumns: '1fr 3fr 2fr', - gridTemplateRows: '1fr auto', - }, - browser: { - gridArea: 'browser', - overflow: 'auto', - }, - editor: { - gridArea: 'editor', - overflow: 'auto', - }, - preview: { - gridArea: 'preview', - overflow: 'auto', - }, - results: { - gridArea: 'results', - }, -}); - -export const TemplateEditor = (props: { - directory: TemplateDirectoryAccess; - fieldExtensions?: LegacyFieldExtensionOptions[]; - layouts?: LayoutOptions[]; - onClose?: () => void; -}) => { - const classes = useStyles(); - - const [errorText, setErrorText] = useState(); - - return ( - - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    - ); -}; diff --git a/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateEditorForm.tsx b/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateEditorForm.tsx deleted file mode 100644 index df05efc921..0000000000 --- a/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateEditorForm.tsx +++ /dev/null @@ -1,261 +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. - */ -import { useApiHolder } from '@backstage/core-plugin-api'; -import { JsonObject, JsonValue } from '@backstage/types'; -import { makeStyles } from '@material-ui/core/styles'; -import React, { Component, ReactNode, useMemo, useState } from 'react'; -import useDebounce from 'react-use/lib/useDebounce'; -import yaml from 'yaml'; -import { - LayoutOptions, - TemplateParameterSchema, -} from '@backstage/plugin-scaffolder-react'; - -import { useDryRun } from '../../next/TemplateEditorPage/DryRunContext'; -import { useDirectoryEditor } from '../../next/TemplateEditorPage/DirectoryEditorContext'; -import { MultistepJsonForm } from '../MultistepJsonForm'; -import { createValidator } from '../TemplatePage'; -import { LegacyFieldExtensionOptions } from '@backstage/plugin-scaffolder-react/alpha'; - -const useStyles = makeStyles({ - containerWrapper: { - position: 'relative', - width: '100%', - height: '100%', - }, - container: { - position: 'absolute', - top: 0, - bottom: 0, - left: 0, - right: 0, - overflow: 'auto', - }, -}); - -interface ErrorBoundaryProps { - invalidator: unknown; - setErrorText(errorText: string | undefined): void; - children: ReactNode; -} - -interface ErrorBoundaryState { - shouldRender: boolean; -} - -class ErrorBoundary extends Component { - state = { - shouldRender: true, - }; - - componentDidUpdate(prevProps: { invalidator: unknown }) { - if (prevProps.invalidator !== this.props.invalidator) { - this.setState({ shouldRender: true }); - } - } - - componentDidCatch(error: Error) { - this.props.setErrorText(error.message); - this.setState({ shouldRender: false }); - } - - render() { - return this.state.shouldRender ? this.props.children : null; - } -} - -interface TemplateEditorFormProps { - content?: string; - /** Setting this to true will cause the content to be parsed as if it is the template entity spec */ - contentIsSpec?: boolean; - data: JsonObject; - onUpdate: (data: JsonObject) => void; - setErrorText: (errorText?: string) => void; - - onDryRun?: (data: JsonObject) => Promise; - fieldExtensions?: LegacyFieldExtensionOptions[]; - layouts?: LayoutOptions[]; -} - -function isJsonObject(value: JsonValue | undefined): value is JsonObject { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -/** Shows the a template form that is parsed from the provided content */ -export function TemplateEditorForm(props: TemplateEditorFormProps) { - const { - content, - contentIsSpec, - data, - onUpdate, - onDryRun, - setErrorText, - fieldExtensions = [], - layouts = [], - } = props; - const classes = useStyles(); - const apiHolder = useApiHolder(); - - const [steps, setSteps] = useState(); - - const fields = useMemo(() => { - return Object.fromEntries( - fieldExtensions.map(({ name, component }) => [name, component]), - ); - }, [fieldExtensions]); - - useDebounce( - () => { - try { - if (!content) { - setSteps(undefined); - return; - } - const parsed: JsonValue = yaml - .parseAllDocuments(content) - .filter(c => c) - .map(c => c.toJSON())[0]; - - if (!isJsonObject(parsed)) { - setSteps(undefined); - return; - } - - let rootObj = parsed; - if (!contentIsSpec) { - const isTemplate = - String(parsed.kind).toLocaleLowerCase('en-US') === 'template'; - if (!isTemplate) { - setSteps(undefined); - return; - } - - rootObj = isJsonObject(parsed.spec) ? parsed.spec : {}; - } - - const { parameters } = rootObj; - - if (!Array.isArray(parameters)) { - setErrorText('Template parameters must be an array'); - setSteps(undefined); - return; - } - - const fieldValidators = Object.fromEntries( - fieldExtensions.map(({ name, validation }) => [name, validation]), - ); - - setErrorText(); - setSteps( - parameters.flatMap(param => - isJsonObject(param) - ? [ - { - title: String(param.title), - schema: param, - validate: createValidator(param, fieldValidators, { - apiHolder, - }), - }, - ] - : [], - ), - ); - } catch (e) { - setErrorText(e.message); - } - }, - 250, - [contentIsSpec, content, apiHolder], - ); - - if (!steps) { - return null; - } - - return ( -
    -
    - - onUpdate(e.formData)} - onReset={() => onUpdate({})} - finishButtonLabel={onDryRun && 'Try It'} - onFinish={onDryRun && (() => onDryRun(data))} - layouts={layouts} - /> - -
    -
    - ); -} - -/** A version of the TemplateEditorForm that is connected to the DirectoryEditor and DryRun contexts */ -export function TemplateEditorFormDirectoryEditorDryRun( - props: Pick< - TemplateEditorFormProps, - 'setErrorText' | 'fieldExtensions' | 'layouts' - >, -) { - const { setErrorText, fieldExtensions = [], layouts } = props; - const dryRun = useDryRun(); - - const directoryEditor = useDirectoryEditor(); - const { selectedFile } = directoryEditor; - - const [data, setData] = useState({}); - - const handleDryRun = async () => { - if (!selectedFile) { - return; - } - - try { - await dryRun.execute({ - templateContent: selectedFile.content, - values: data, - files: directoryEditor.files, - }); - setErrorText(); - } catch (e) { - setErrorText(String(e.cause || e)); - throw e; - } - }; - - const content = - selectedFile && selectedFile.path.match(/\.ya?ml$/) - ? selectedFile.content - : undefined; - - return ( - - ); -} - -TemplateEditorForm.DirectoryEditorDryRun = - TemplateEditorFormDirectoryEditorDryRun; diff --git a/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateEditorPage.tsx deleted file mode 100644 index 127c73dcf5..0000000000 --- a/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateEditorPage.tsx +++ /dev/null @@ -1,105 +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. - */ -import React, { useState } from 'react'; -import { Content, Header, Page } from '@backstage/core-components'; -import { - TemplateDirectoryAccess, - WebFileSystemAccess, -} from '../../lib/filesystem'; -import { type LayoutOptions } from '@backstage/plugin-scaffolder-react'; -import { LegacyFieldExtensionOptions } from '@backstage/plugin-scaffolder-react/alpha'; -import { TemplateEditorIntro } from '../../next/TemplateEditorPage/TemplateEditorIntro'; -import { TemplateEditor } from './TemplateEditor'; -import { TemplateFormPreviewer } from './TemplateFormPreviewer'; -import { CustomFieldExplorer } from './CustomFieldExplorer'; - -type Selection = - | { - type: 'local'; - directory: TemplateDirectoryAccess; - } - | { - type: 'form'; - } - | { - type: 'field-explorer'; - }; - -interface TemplateEditorPageProps { - defaultPreviewTemplate?: string; - customFieldExtensions?: LegacyFieldExtensionOptions[]; - layouts?: LayoutOptions[]; -} - -export function TemplateEditorPage(props: TemplateEditorPageProps) { - const [selection, setSelection] = useState(); - - let content: JSX.Element | null = null; - if (selection?.type === 'local') { - content = ( - setSelection(undefined)} - layouts={props.layouts} - /> - ); - } else if (selection?.type === 'form') { - content = ( - setSelection(undefined)} - layouts={props.layouts} - /> - ); - } else if (selection?.type === 'field-explorer') { - content = ( - setSelection(undefined)} - /> - ); - } else { - content = ( - - { - if (option === 'local') { - WebFileSystemAccess.requestDirectoryAccess() - .then(directory => setSelection({ type: 'local', directory })) - .catch(() => {}); - } else if (option === 'form') { - setSelection({ type: 'form' }); - } else if (option === 'field-explorer') { - setSelection({ type: 'field-explorer' }); - } - }} - /> - - ); - } - - return ( - -
    - {content} - - ); -} diff --git a/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateFormPreviewer.tsx deleted file mode 100644 index 9e92d12d93..0000000000 --- a/plugins/scaffolder/src/legacy/TemplateEditorPage/TemplateFormPreviewer.tsx +++ /dev/null @@ -1,221 +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. - */ -import { Entity } from '@backstage/catalog-model'; -import { alertApiRef, useApi } from '@backstage/core-plugin-api'; -import { - catalogApiRef, - humanizeEntityRef, -} from '@backstage/plugin-catalog-react'; -import { - FormControl, - IconButton, - InputLabel, - LinearProgress, - makeStyles, - MenuItem, - Select, -} from '@material-ui/core'; -import CloseIcon from '@material-ui/icons/Close'; -import React, { useCallback, useState } from 'react'; -import useAsync from 'react-use/lib/useAsync'; -import yaml from 'yaml'; -import { LayoutOptions } from '@backstage/plugin-scaffolder-react'; -import { LegacyFieldExtensionOptions } from '@backstage/plugin-scaffolder-react/alpha'; -import { TemplateEditorTextArea } from '../../next/TemplateEditorPage/TemplateEditorTextArea'; -import { TemplateEditorForm } from './TemplateEditorForm'; - -const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI -parameters: - - title: Fill in some steps - required: - - name - properties: - name: - title: Name - type: string - description: Unique name of the component - owner: - title: Owner - type: string - description: Owner of the component - ui:field: OwnerPicker - ui:options: - catalogFilter: - kind: Group - - title: Choose a location - required: - - repoUrl - properties: - repoUrl: - title: Repository Location - type: string - ui:field: RepoUrlPicker - ui:options: - allowedHosts: - - github.com -steps: - - id: fetch-base - name: Fetch Base - action: fetch:template - input: - url: ./template - values: - name: \${{parameters.name}} -`; - -type TemplateOption = { - label: string; - value: Entity; -}; - -const useStyles = makeStyles(theme => ({ - root: { - gridArea: 'pageContent', - display: 'grid', - gridTemplateAreas: ` - "controls controls" - "textArea preview" - `, - gridTemplateRows: 'auto 1fr', - gridTemplateColumns: '1fr 1fr', - }, - controls: { - gridArea: 'controls', - display: 'flex', - flexFlow: 'row nowrap', - alignItems: 'center', - margin: theme.spacing(1), - }, - textArea: { - gridArea: 'textArea', - }, - preview: { - gridArea: 'preview', - }, -})); - -export const TemplateFormPreviewer = ({ - defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML, - customFieldExtensions = [], - onClose, - layouts = [], -}: { - defaultPreviewTemplate?: string; - customFieldExtensions?: LegacyFieldExtensionOptions[]; - onClose?: () => void; - layouts?: LayoutOptions[]; -}) => { - const classes = useStyles(); - const alertApi = useApi(alertApiRef); - const catalogApi = useApi(catalogApiRef); - const [selectedTemplate, setSelectedTemplate] = useState(''); - const [errorText, setErrorText] = useState(); - const [templateOptions, setTemplateOptions] = useState([]); - const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate); - const [formState, setFormState] = useState({}); - - const { loading } = useAsync( - () => - catalogApi - .getEntities({ - filter: { kind: 'template' }, - fields: [ - 'kind', - 'metadata.namespace', - 'metadata.name', - 'metadata.title', - 'spec.parameters', - 'spec.steps', - 'spec.output', - ], - }) - .then(({ items }) => - setTemplateOptions( - items.map(template => ({ - label: - template.metadata.title ?? - humanizeEntityRef(template, { defaultKind: 'template' }), - value: template, - })), - ), - ) - .catch(e => - alertApi.post({ - message: `Error loading exisiting templates: ${e.message}`, - severity: 'error', - }), - ), - [catalogApi], - ); - - const handleSelectChange = useCallback( - // TODO(Rugvip): Afaik this should be Entity, but didn't want to make runtime changes while fixing types - (selected: any) => { - setSelectedTemplate(selected); - setTemplateYaml(yaml.stringify(selected.spec)); - }, - [setTemplateYaml], - ); - - return ( - <> - {loading && } -
    -
    - - - Load Existing Template - - - - - - - -
    -
    - -
    -
    - -
    -
    - - ); -}; diff --git a/plugins/scaffolder/src/legacy/TemplateList/TemplateList.test.tsx b/plugins/scaffolder/src/legacy/TemplateList/TemplateList.test.tsx deleted file mode 100644 index c65613d2f5..0000000000 --- a/plugins/scaffolder/src/legacy/TemplateList/TemplateList.test.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 { screen } from '@testing-library/react'; -import React from 'react'; -import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { TemplateList } from './TemplateList'; -import { rootRouteRef } from '../../routes'; -import { - ScmIntegrationsApi, - scmIntegrationsApiRef, -} from '@backstage/integration-react'; -import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; - -jest.mock('@backstage/plugin-catalog-react', () => ({ - useEntityList: jest.fn().mockReturnValue({ - loading: false, - entities: [ - { - apiVersion: 'scaffolder.backstage.io/v1beta3', - kind: 'Template', - metadata: { - name: 't1', - }, - spec: {}, - }, - { - apiVersion: 'scaffolder.backstage.io/v1beta3', - kind: 'Template', - metadata: { - name: 't2', - }, - spec: {}, - }, - ], - }), - getEntityRelations: jest.fn().mockImplementation(() => []), - getEntitySourceLocation: jest.fn().mockImplementation(() => ({})), -})); - -describe('TemplateList', () => { - const mockIntegrationsApi: Partial = { - byHost: () => ({ type: 'github' }), - }; - - it('should filter out templates based on provided filter condition', async () => { - const TemplateCardComponent = ({ - template, - }: { - template: TemplateEntityV1beta3; - }) => ( -
    {template.metadata.name}
    - ); - - await renderInTestApp( - -
    - e.metadata.name === 't1'} - TemplateCardComponent={TemplateCardComponent} - /> -
    -
    , - { mountedRoutes: { '/': rootRouteRef } }, - ); - - expect(() => screen.getByTestId('t2')).toThrow(); - expect(screen.getByTestId('t1')).toBeDefined(); - }); -}); diff --git a/plugins/scaffolder/src/legacy/TemplateList/TemplateList.tsx b/plugins/scaffolder/src/legacy/TemplateList/TemplateList.tsx deleted file mode 100644 index c925cdf6ef..0000000000 --- a/plugins/scaffolder/src/legacy/TemplateList/TemplateList.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { ComponentType } from 'react'; -import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; -import { - isTemplateEntityV1beta3, - TemplateEntityV1beta3, -} from '@backstage/plugin-scaffolder-common'; -import { - Content, - ContentHeader, - ItemCardGrid, - Link, - Progress, - WarningPanel, -} from '@backstage/core-components'; -import { useEntityList } from '@backstage/plugin-catalog-react'; -import { Typography } from '@material-ui/core'; -import { TemplateCard } from '../TemplateCard'; - -/** - * @internal - */ -export type TemplateListProps = { - TemplateCardComponent?: - | ComponentType<{ template: TemplateEntityV1beta3 }> - | undefined; - group?: { - title?: React.ReactNode; - filter: (entity: TemplateEntityV1beta3) => boolean; - }; - templateFilter?: (entity: TemplateEntityV1beta3) => boolean; -}; - -/** - * @internal - */ -export const TemplateList = ({ - TemplateCardComponent, - group, - templateFilter, -}: TemplateListProps) => { - const { loading, error, entities } = useEntityList(); - const Card = TemplateCardComponent || TemplateCard; - const templateEntities = entities.filter(isTemplateEntityV1beta3); - const maybeFilteredEntities = ( - group ? templateEntities.filter(group.filter) : templateEntities - ).filter(e => (templateFilter ? templateFilter(e) : true)); - - const titleComponent: React.ReactNode = (() => { - if (group && group.title) { - if (typeof group.title === 'string') { - return ; - } - return group.title; - } - - return ; - })(); - - if (group && maybeFilteredEntities.length === 0) { - return null; - } - return ( - <> - {loading && } - - {error && ( - - {error.message} - - )} - - {!error && !loading && !entities.length && ( - - No templates found that match your filter. Learn more about{' '} - - adding templates - - . - - )} - - - {titleComponent} - - {maybeFilteredEntities && - maybeFilteredEntities?.length > 0 && - maybeFilteredEntities.map((template: Entity) => ( - - ))} - - - - ); -}; diff --git a/plugins/scaffolder/src/legacy/TemplateList/index.ts b/plugins/scaffolder/src/legacy/TemplateList/index.ts deleted file mode 100644 index 0b62f9cbb1..0000000000 --- a/plugins/scaffolder/src/legacy/TemplateList/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { TemplateList } from './TemplateList'; -export type { TemplateListProps } from './TemplateList'; diff --git a/plugins/scaffolder/src/legacy/TemplatePage/TemplatePage.test.tsx b/plugins/scaffolder/src/legacy/TemplatePage/TemplatePage.test.tsx deleted file mode 100644 index 344929fa43..0000000000 --- a/plugins/scaffolder/src/legacy/TemplatePage/TemplatePage.test.tsx +++ /dev/null @@ -1,366 +0,0 @@ -/* - * 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 { - MockAnalyticsApi, - renderInTestApp, - TestApiRegistry, -} from '@backstage/test-utils'; -import { act, fireEvent, screen, within } from '@testing-library/react'; -import React from 'react'; -import { Route, Routes } from 'react-router-dom'; -import { - scaffolderApiRef, - ScaffolderApi, - SecretsContextProvider, -} from '@backstage/plugin-scaffolder-react'; -import { TemplatePage } from './TemplatePage'; -import { - featureFlagsApiRef, - FeatureFlagsApi, - analyticsApiRef, -} from '@backstage/core-plugin-api'; -import { ApiProvider } from '@backstage/core-app-api'; -import { errorApiRef } from '@backstage/core-plugin-api'; -import { rootRouteRef } from '../../routes'; - -jest.mock('react-router-dom', () => { - return { - ...(jest.requireActual('react-router-dom') as any), - useParams: () => ({ - templateName: 'test', - }), - }; -}); - -const scaffolderApiMock: jest.Mocked = { - cancelTask: jest.fn(), - scaffold: jest.fn(), - getTemplateParameterSchema: jest.fn(), - getIntegrationsList: jest.fn(), - getTask: jest.fn(), - streamLogs: jest.fn(), - listActions: jest.fn(), - listTasks: jest.fn(), -}; - -const featureFlagsApiMock: jest.Mocked = { - isActive: jest.fn(), - registerFlag: jest.fn(), - getRegisteredFlags: jest.fn(), - save: jest.fn(), -}; - -const errorApiMock = { post: jest.fn(), error$: jest.fn() }; - -const analyticsMock = new MockAnalyticsApi(); - -const schemaMockValue = { - title: 'my-schema', - steps: [ - { - title: 'Fill in some steps', - schema: { - title: 'Fill in some steps', - 'backstage:featureFlag': 'experimental-feature', - properties: { - name: { - title: 'Name', - type: 'string', - 'backstage:featureFlag': 'should-show-some-stuff-first-option', - }, - description: { - title: 'Description', - type: 'string', - description: 'A description for the component', - }, - owner: { - title: 'Owner', - type: 'string', - description: 'Owner of the component', - }, - }, - type: 'object', - }, - }, - { - title: 'Send data', - schema: { - title: 'Send data', - properties: { - user: { - title: 'User', - type: 'string', - }, - }, - type: 'object', - }, - }, - ], -}; - -const apis = TestApiRegistry.from( - [scaffolderApiRef, scaffolderApiMock], - [errorApiRef, errorApiMock], - [featureFlagsApiRef, featureFlagsApiMock], - [analyticsApiRef, analyticsMock], -); - -describe('TemplatePage', () => { - beforeEach(() => jest.resetAllMocks()); - - it('renders correctly', async () => { - scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue({ - title: 'React SSR Template', - steps: [], - }); - const rendered = await renderInTestApp( - - - - - , - { - mountedRoutes: { - '/create': rootRouteRef, - }, - }, - ); - - expect(rendered.getByText('Create a New Component')).toBeInTheDocument(); - expect(rendered.getByText('React SSR Template')).toBeInTheDocument(); - }); - - it('renders spinner while loading', async () => { - let resolve: Function; - const promise = new Promise(res => { - resolve = res; - }); - scaffolderApiMock.getTemplateParameterSchema.mockReturnValueOnce(promise); - const rendered = await renderInTestApp( - - - - - , - { - mountedRoutes: { - '/create': rootRouteRef, - }, - }, - ); - - expect(rendered.getByText('Create a New Component')).toBeInTheDocument(); - expect(rendered.getByTestId('loading-progress')).toBeInTheDocument(); - - await act(async () => { - resolve!({ - title: 'React SSR Template', - steps: [], - }); - }); - }); - - it('captures expected analytics events', async () => { - scaffolderApiMock.scaffold.mockResolvedValue({ taskId: 'xyz' }); - scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue({ - title: 'schema-4-analytics', - steps: [ - { - title: 'Fill in some steps', - schema: { - properties: { - name: { - title: 'Name', - type: 'string', - }, - }, - required: ['name'], - }, - }, - ], - }); - const { findByLabelText, findByText } = await renderInTestApp( - - - - - , - { - mountedRoutes: { - '/create': rootRouteRef, - }, - }, - ); - - // Fill out the name field - expect(await findByText('Fill in some steps')).toBeInTheDocument(); - fireEvent.change(await findByLabelText('Name', { exact: false }), { - target: { value: 'expected-name' }, - }); - - // Go to the final page - fireEvent.click(await findByText('Next step')); - expect(await findByText('Reset')).toBeInTheDocument(); - - // Create the software - await act(async () => { - fireEvent.click(await findByText('Create')); - }); - - // The "Next Step" button should have fired an event - expect(analyticsMock.getEvents()[0]).toMatchObject({ - action: 'click', - subject: 'Next Step (1)', - context: { entityRef: 'template:default/test' }, - }); - - // And the "Create" button should have fired an event - expect(analyticsMock.getEvents()[1]).toMatchObject({ - action: 'create', - subject: 'expected-name', - context: { entityRef: 'template:default/test' }, - }); - }); - - it('navigates away if no template was loaded', async () => { - scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue( - undefined as any, - ); - - const rendered = await renderInTestApp( - - - - - - } - /> - This is root} /> - - , - { - routeEntries: ['/create'], - mountedRoutes: { '/create': rootRouteRef }, - }, - ); - - expect( - rendered.queryByText('Create a New Component'), - ).not.toBeInTheDocument(); - expect(rendered.getByText('This is root')).toBeInTheDocument(); - }); - - it('display template with oneOf', async () => { - scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue({ - title: 'my-schema', - steps: [ - { - title: 'Fill in some steps', - schema: { - oneOf: [ - { - title: 'First', - properties: { - name: { - title: 'Name', - type: 'string', - }, - }, - required: ['name'], - }, - { - title: 'Second', - properties: { - something: { - title: 'Something', - type: 'string', - }, - }, - required: ['something'], - }, - ], - }, - }, - ], - }); - - const { findByText, findByLabelText, findAllByRole, findByRole } = - await renderInTestApp( - - - - - , - { - mountedRoutes: { - '/create/actions': rootRouteRef, - }, - }, - ); - - expect(await findByText('Fill in some steps')).toBeInTheDocument(); - - // Fill the first option - fireEvent.change(await findByLabelText('Name', { exact: false }), { - target: { value: 'my-name' }, - }); - - // Switch to second option - fireEvent.mouseDown((await findAllByRole('button'))[0]); - const listbox = within(await findByRole('listbox')); - fireEvent.click(listbox.getByText(/Second/i)); - - // Fill the second option - fireEvent.change(await findByLabelText('Something', { exact: false }), { - target: { value: 'my-something' }, - }); - - // Go to the final page - fireEvent.click(await findByText('Next step')); - expect(await findByText('Reset')).toBeInTheDocument(); - }); - - it('should display a section or property based on a feature flag', async () => { - featureFlagsApiMock.isActive.mockImplementation(flag => { - return flag === 'experimental-feature'; - }); - scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue( - schemaMockValue, - ); - - await renderInTestApp( - - - - - , - { - mountedRoutes: { - '/create/actions': rootRouteRef, - }, - }, - ); - - expect(screen.queryByText('Name')).not.toBeInTheDocument(); - expect(screen.getByText('Description')).toBeInTheDocument(); - expect(screen.getByText('Owner')).toBeInTheDocument(); - expect(screen.getByText('Send data')).toBeInTheDocument(); - }); -}); diff --git a/plugins/scaffolder/src/legacy/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/legacy/TemplatePage/TemplatePage.tsx deleted file mode 100644 index ffb754bae3..0000000000 --- a/plugins/scaffolder/src/legacy/TemplatePage/TemplatePage.tsx +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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 { LinearProgress } from '@material-ui/core'; -import { IChangeEvent } from '@rjsf/core'; -import qs from 'qs'; -import React, { ComponentType, useCallback, useState } from 'react'; -import { Navigate, useNavigate } from 'react-router-dom'; -import useAsync from 'react-use/lib/useAsync'; -import { - type LayoutOptions, - scaffolderApiRef, - useTemplateSecrets, - ReviewStepProps, -} from '@backstage/plugin-scaffolder-react'; -import { MultistepJsonForm } from '../MultistepJsonForm'; -import { createValidator } from './createValidator'; - -import { Content, Header, InfoCard, Page } from '@backstage/core-components'; -import { - AnalyticsContext, - errorApiRef, - useApi, - useApiHolder, - useRouteRef, - useRouteRefParams, -} from '@backstage/core-plugin-api'; -import { stringifyEntityRef } from '@backstage/catalog-model'; -import { - rootRouteRef, - scaffolderTaskRouteRef, - selectedTemplateRouteRef, -} from '../../routes'; -import { LegacyFieldExtensionOptions } from '@backstage/plugin-scaffolder-react/alpha'; - -const useTemplateParameterSchema = (templateRef: string) => { - const scaffolderApi = useApi(scaffolderApiRef); - const { value, loading, error } = useAsync( - () => scaffolderApi.getTemplateParameterSchema(templateRef), - [scaffolderApi, templateRef], - ); - return { schema: value, loading, error }; -}; - -type Props = { - ReviewStepComponent?: ComponentType; - customFieldExtensions?: LegacyFieldExtensionOptions[]; - layouts?: LayoutOptions[]; - headerOptions?: { - pageTitleOverride?: string; - title?: string; - subtitle?: string; - }; -}; - -export const TemplatePage = ({ - ReviewStepComponent, - customFieldExtensions = [], - layouts = [], - headerOptions, -}: Props) => { - const apiHolder = useApiHolder(); - const secretsContext = useTemplateSecrets(); - const errorApi = useApi(errorApiRef); - const scaffolderApi = useApi(scaffolderApiRef); - const { templateName, namespace } = useRouteRefParams( - selectedTemplateRouteRef, - ); - const templateRef = stringifyEntityRef({ - name: templateName, - kind: 'template', - namespace, - }); - const navigate = useNavigate(); - const scaffolderTaskRoute = useRouteRef(scaffolderTaskRouteRef); - const rootRoute = useRouteRef(rootRouteRef); - const { schema, loading, error } = useTemplateParameterSchema(templateRef); - const [formState, setFormState] = useState>(() => { - const query = qs.parse(window.location.search, { - ignoreQueryPrefix: true, - }); - - try { - return JSON.parse(query.formData as string); - } catch (e) { - return query.formData ?? {}; - } - }); - const handleFormReset = () => setFormState({}); - const handleChange = useCallback( - (e: IChangeEvent) => setFormState(e.formData), - [setFormState], - ); - - const handleCreate = async () => { - const { taskId } = await scaffolderApi.scaffold({ - templateRef, - values: formState, - secrets: secretsContext?.secrets, - }); - - const formParams = qs.stringify( - { formData: formState }, - { addQueryPrefix: true }, - ); - const newUrl = `${window.location.pathname}${formParams}`; - // We use direct history manipulation since useSearchParams and - // useNavigate in react-router-dom cause unnecessary extra rerenders. - // Also make sure to replace the state rather than pushing to avoid - // extra back/forward slots. - window.history?.replaceState(null, document.title, newUrl); - - navigate(scaffolderTaskRoute({ taskId })); - }; - - if (error) { - errorApi.post(new Error(`Failed to load template, ${error}`)); - return ; - } - if (!loading && !schema) { - errorApi.post(new Error('Template was not found.')); - return ; - } - - const customFieldComponents = Object.fromEntries( - customFieldExtensions.map(({ name, component }) => [name, component]), - ); - - const customFieldValidators = Object.fromEntries( - customFieldExtensions.map(({ name, validation }) => [name, validation]), - ); - - return ( - - -
    - - {loading && } - {schema && ( - - { - return { - ...step, - validate: createValidator( - step.schema, - customFieldValidators, - { apiHolder }, - ), - }; - })} - /> - - )} - - - - ); -}; diff --git a/plugins/scaffolder/src/legacy/TemplatePage/createValidator.test.ts b/plugins/scaffolder/src/legacy/TemplatePage/createValidator.test.ts deleted file mode 100644 index d6a5cd7499..0000000000 --- a/plugins/scaffolder/src/legacy/TemplatePage/createValidator.test.ts +++ /dev/null @@ -1,200 +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. - */ - -import { createValidator } from './createValidator'; -import { LegacyCustomFieldValidator } from '@backstage/plugin-scaffolder-react/alpha'; -import { ApiHolder } from '@backstage/core-plugin-api'; -import { FieldValidation, FormValidation } from '@rjsf/utils'; - -type CustomLinkType = { - url: string; - title: string; - icon: string; -}; - -describe('createValidator', () => { - const validators: Record< - string, - undefined | LegacyCustomFieldValidator - > = { - CustomPicker: ( - value: unknown, - fieldValidation: FieldValidation, - _context: { apiHolder: ApiHolder }, - ) => { - if (!value || !(value as { value?: unknown }).value) { - fieldValidation.addError('Error !'); - } - }, - CustomLink: ( - values: unknown, - fieldValidation: FieldValidation, - _context: { apiHolder: ApiHolder }, - ) => { - const input = values as CustomLinkType[]; - for (const item of input) { - const validGitlabUrlRegex = - /gitlab\.(?:stg\.)?spotify\.com\?owner=.*&repo=.*/; - - if (!item || !validGitlabUrlRegex.test(item.url)) { - fieldValidation.addError( - `Make sure to put in a valid gitlab clone url.`, - ); - } - } - }, - TagPicker: ( - values: unknown, - fieldValidation: FieldValidation, - _context: { apiHolder: ApiHolder }, - ) => { - const input = values as string[]; - for (const item of input) { - if (!/^[a-z0-9-]+$/.test(item)) { - fieldValidation.addError( - 'A tag name can only contain lowercase letters, numeric characters or dashes', - ); - } - } - }, - }; - - const apiHolderMock: jest.Mocked = { - get: jest.fn().mockImplementation(() => { - return null; - }), - }; - - const context = { - apiHolder: apiHolderMock, - }; - - it('should call validator for object property from a custom field extension', () => { - /* GIVEN */ - const rootSchema = { - title: 'Title', - properties: { - p1: { - title: 'PropertyOn', - type: 'object', - 'ui:field': 'CustomPicker', - }, - }, - }; - const validator = createValidator(rootSchema, validators, context); - - const formData = { - p1: {}, - }; - const errors = { - addError: jest.fn(), - p1: { - addError: jest.fn(), - } as unknown as FormValidation, - } as unknown as FormValidation; - - /* WHEN */ - const result = validator(formData, errors); - - /* THEN */ - expect(result).not.toBeNull(); - expect(result.p1?.addError).toHaveBeenCalledTimes(1); - }); - - it('should call validator for array property from a custom field extension', () => { - /* GIVEN */ - const rootSchema = { - title: 'My form', - properties: { - tags: { - title: 'Tags', - type: 'array', - items: { - type: 'string', - 'ui:field': 'TagPicker', - }, - }, - }, - }; - const validator = createValidator(rootSchema, validators, context); - - const formData = { - tags: ['invalid-tag$$'], - }; - const errors = { - addError: jest.fn(), - tags: { - addError: jest.fn(), - } as unknown as FormValidation, - } as unknown as FormValidation; - - /* WHEN */ - const result = validator(formData, errors); - - /* THEN */ - expect(result).not.toBeNull(); - expect(result.tags?.addError).toHaveBeenCalledTimes(1); - }); - - it('should call validator for array object property from a custom field extension', () => { - /* GIVEN */ - const rootSchema = { - title: 'My links', - properties: { - links: { - title: 'Links', - type: 'array', - items: { - type: 'object', - required: ['url', 'title', 'icon'], - properties: { - url: { - title: 'url', - description: 'url', - type: 'object', - 'ui:field': 'CustomLink', - }, - }, - }, - }, - }, - }; - const validator = createValidator(rootSchema, validators, context); - - const formData = { - links: [ - { - url: 'http://gitlab.spotify.nl/owener=me&repo=test', - icon: 'subject', - title: 'My repository for testing features', - } as CustomLinkType, - ], - }; - const errors = { - addError: jest.fn(), - links: { - addError: jest.fn(), - } as unknown as FormValidation, - } as unknown as FormValidation; - - /* WHEN */ - const result = validator(formData, errors); - - /* THEN */ - expect(result).not.toBeNull(); - expect(result.links?.addError).toHaveBeenCalledTimes(1); - }); -}); diff --git a/plugins/scaffolder/src/legacy/TemplatePage/createValidator.ts b/plugins/scaffolder/src/legacy/TemplatePage/createValidator.ts deleted file mode 100644 index ae0b834fe9..0000000000 --- a/plugins/scaffolder/src/legacy/TemplatePage/createValidator.ts +++ /dev/null @@ -1,103 +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. - */ - -import { LegacyCustomFieldValidator } from '@backstage/plugin-scaffolder-react/alpha'; -import { FieldValidation, FormValidation } from '@rjsf/utils'; -import { JsonObject, JsonValue } from '@backstage/types'; -import { ApiHolder } from '@backstage/core-plugin-api'; - -function isObject(obj: unknown): obj is JsonObject { - return typeof obj === 'object' && obj !== null && !Array.isArray(obj); -} - -function isArray(obj: unknown): obj is JsonObject { - return typeof obj === 'object' && obj !== null && Array.isArray(obj); -} - -export const createValidator = ( - rootSchema: JsonObject, - validators: Record>, - context: { - apiHolder: ApiHolder; - }, -) => { - function validate( - schema: JsonObject, - formData: JsonObject, - errors: FormValidation, - ) { - const schemaProps = schema.properties; - const customObject = schema.type === 'object' && schemaProps === undefined; - - if (!isObject(schemaProps) && !customObject) { - return; - } - - if (schemaProps) { - for (const [key, propData] of Object.entries(formData)) { - const propValidation = errors[key]; - - const doValidate = (item: JsonValue | undefined) => { - if (item && isObject(item)) { - const fieldName = item['ui:field'] as string; - if (fieldName && typeof validators[fieldName] === 'function') { - validators[fieldName]!( - propData as JsonObject[], - propValidation as FieldValidation, - context, - ); - } - } - }; - - const propSchemaProps = schemaProps[key]; - if (isObject(propData) && isObject(propSchemaProps)) { - validate( - propSchemaProps, - propData as JsonObject, - propValidation as FormValidation, - ); - } else if (isArray(propData)) { - if (isObject(propSchemaProps)) { - const { items } = propSchemaProps; - if (isObject(items)) { - if (items.type === 'object') { - const properties = (items?.properties ?? []) as JsonObject[]; - for (const [, value] of Object.entries(properties)) { - doValidate(value); - } - } else { - doValidate(items); - } - } - } - } else { - doValidate(propSchemaProps); - } - } - } else if (customObject) { - const fieldName = schema['ui:field'] as string; - if (fieldName && typeof validators[fieldName] === 'function') { - validators[fieldName]!(formData, errors, context); - } - } - } - - return (formData: JsonObject, errors: FormValidation) => { - validate(rootSchema, formData, errors); - return errors; - }; -}; diff --git a/plugins/scaffolder/src/legacy/TemplatePage/index.ts b/plugins/scaffolder/src/legacy/TemplatePage/index.ts deleted file mode 100644 index d3ae24504b..0000000000 --- a/plugins/scaffolder/src/legacy/TemplatePage/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { TemplatePage } from './TemplatePage'; -export { createValidator } from './createValidator'; diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx index a6c06ea20a..2a4326c432 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/CustomFieldExplorer.tsx @@ -157,6 +157,9 @@ export const CustomFieldExplorer = ({ onSubmit={e => handleFieldConfigChange(e.formData)} validator={validator} schema={selectedField.schema?.uiOptions || {}} + experimental_defaultFormStateBehavior={{ + allOf: 'populateDefaults', + }} >