diff --git a/.changeset/blue-pumas-cheer.md b/.changeset/blue-pumas-cheer.md new file mode 100644 index 0000000000..f6cbf180ff --- /dev/null +++ b/.changeset/blue-pumas-cheer.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Added the Kubernetes plugin to `create-app` diff --git a/.changeset/breezy-jeans-tie.md b/.changeset/breezy-jeans-tie.md new file mode 100644 index 0000000000..4601389a4c --- /dev/null +++ b/.changeset/breezy-jeans-tie.md @@ -0,0 +1,18 @@ +--- +'@backstage/plugin-auth-backend-module-cloudflare-access-provider': patch +'@backstage/plugin-auth-backend-module-vmware-cloud-provider': patch +'@backstage/plugin-auth-backend-module-atlassian-provider': patch +'@backstage/plugin-auth-backend-module-bitbucket-provider': patch +'@backstage/plugin-auth-backend-module-microsoft-provider': patch +'@backstage/plugin-auth-backend-module-onelogin-provider': patch +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +'@backstage/plugin-auth-backend-module-gcp-iap-provider': patch +'@backstage/plugin-auth-backend-module-github-provider': patch +'@backstage/plugin-auth-backend-module-gitlab-provider': patch +'@backstage/plugin-auth-backend-module-google-provider': patch +'@backstage/plugin-auth-backend-module-oauth2-provider': patch +'@backstage/plugin-auth-backend-module-oidc-provider': patch +'@backstage/plugin-auth-backend-module-okta-provider': patch +--- + +Add `signIn` to authentication provider configuration schema diff --git a/.changeset/bright-panthers-leave.md b/.changeset/bright-panthers-leave.md deleted file mode 100644 index 35d9c0f869..0000000000 --- a/.changeset/bright-panthers-leave.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch -'@backstage/backend-defaults': patch ---- - -Added a new Root Health Service which adds new endpoints for health checks. diff --git a/.changeset/bright-trainers-brake.md b/.changeset/bright-trainers-brake.md new file mode 100644 index 0000000000..2299d83a32 --- /dev/null +++ b/.changeset/bright-trainers-brake.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Add frontend-dynamic-container role to eslint config factory diff --git a/.changeset/calm-jeans-ring.md b/.changeset/calm-jeans-ring.md deleted file mode 100644 index 3d1efcf7bd..0000000000 --- a/.changeset/calm-jeans-ring.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': minor ---- - -Use virtualization with `EntityPicker` as done earlier with `MultiEntityPicker` to fix performance issues with large data sets. `VirtualizedListbox` extracted into reusable component. diff --git a/.changeset/chilly-roses-trade.md b/.changeset/chilly-roses-trade.md deleted file mode 100644 index 73fc3ff766..0000000000 --- a/.changeset/chilly-roses-trade.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Deprecate legacy status check factory, handler and types. diff --git a/.changeset/chilly-trains-sleep.md b/.changeset/chilly-trains-sleep.md new file mode 100644 index 0000000000..cde7600c8b --- /dev/null +++ b/.changeset/chilly-trains-sleep.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-events-backend-module-aws-sqs': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/backend-common': patch +--- + +Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. diff --git a/.changeset/clever-waves-judge.md b/.changeset/clever-waves-judge.md deleted file mode 100644 index 585ad01bbe..0000000000 --- a/.changeset/clever-waves-judge.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-techdocs-react': patch -'@backstage/plugin-techdocs': patch ---- - -Fixed a bug with the TechDocsReaderPageProvider not re-rendering when setShadowDom is called, meaning that the useShadowDom hooks were inconsistent. This issue caused the TextSize addon changes not to reapply during navigation. diff --git a/.changeset/cool-insects-remember.md b/.changeset/cool-insects-remember.md new file mode 100644 index 0000000000..ab33015377 --- /dev/null +++ b/.changeset/cool-insects-remember.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-notifications': patch +--- + +Add examples for notification:send scaffolder action & improve related tests diff --git a/.changeset/create-app-1719320674.md b/.changeset/create-app-1719320674.md deleted file mode 100644 index b50d431d4b..0000000000 --- a/.changeset/create-app-1719320674.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Bumped create-app version. diff --git a/.changeset/cuddly-zebras-crash.md b/.changeset/cuddly-zebras-crash.md new file mode 100644 index 0000000000..473ac4053f --- /dev/null +++ b/.changeset/cuddly-zebras-crash.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Use ES2022 in CLI bundler diff --git a/.changeset/curvy-teachers-smell.md b/.changeset/curvy-teachers-smell.md deleted file mode 100644 index 3f032f2177..0000000000 --- a/.changeset/curvy-teachers-smell.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-ldap': patch -'@backstage/backend-plugin-api': patch -'@backstage/backend-defaults': patch -'@backstage/backend-tasks': patch ---- - -Fix bug where ISO durations could no longer be used for schedules diff --git a/.changeset/dry-squids-tap.md b/.changeset/dry-squids-tap.md new file mode 100644 index 0000000000..613ec4b7cb --- /dev/null +++ b/.changeset/dry-squids-tap.md @@ -0,0 +1,31 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +Introduce a new way to encapsulate extension kinds that replaces the extension creator pattern with `createExtensionBlueprint` + +This allows the creation of extension instances with the following pattern: + +```tsx +// create the extension blueprint which is used to create instances +const EntityCardBlueprint = createExtensionBlueprint({ + kind: 'entity-card', + attachTo: { id: 'test', input: 'default' }, + output: { + element: coreExtensionData.reactElement, + }, + factory(params: { text: string }) { + return { + element:

{params.text}

, + }; + }, +}); + +// create an instance of the extension blueprint with params +const testExtension = EntityCardBlueprint.make({ + name: 'foo', + params: { + text: 'Hello World', + }, +}); +``` diff --git a/.changeset/dull-ghosts-double.md b/.changeset/dull-ghosts-double.md new file mode 100644 index 0000000000..0bc5a09791 --- /dev/null +++ b/.changeset/dull-ghosts-double.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-search-react': patch +'@backstage/plugin-home': patch +--- + +Updated alpha definitions of extension data references. diff --git a/.changeset/early-trees-dance.md b/.changeset/early-trees-dance.md new file mode 100644 index 0000000000..55b2c08f89 --- /dev/null +++ b/.changeset/early-trees-dance.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': patch +--- + +The `ExtensionBoundary` now by default infers whether it's routable from whether it outputs a route path. diff --git a/.changeset/eighty-games-wink.md b/.changeset/eighty-games-wink.md deleted file mode 100644 index e87dc56b5f..0000000000 --- a/.changeset/eighty-games-wink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Fixed bug where `` component with empty string as placeholder gave an error +- 99d672d: Modified the `Select` component to take in a `data-testid` parameter ensuring backwards compatibility with default value corresponding to previously hardcoded `data-testid` of "select". +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/version-bridge@1.0.8 + +## @backstage/create-app@0.5.17 + +### Patch Changes + +- 780d994: Added `MyGroupsSidebarItem` to the sidebar in the `create-app` template +- f03d12a: Bumped create-app version. +- e90a2cd: Added the Catalog logs module to the `create-app` template +- 3ac2a6a: Added the Postgres Search Engine to the `create-app` template +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/dev-utils@1.0.35 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-app-api@1.14.0 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/app-defaults@1.5.8 + - @backstage/integration-react@1.1.29 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## @backstage/frontend-app-api@0.7.3 + +### Patch Changes + +- d3c39fc: Allow for the disabling of external routes through config, which was rendered impossible after the introduction of default targets. + + ```yaml + app: + routes: + bindings: + # This has the effect of removing the button for registering new + # catalog entities in the scaffolder template list view + scaffolder.registerComponent: false + ``` + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-app-api@1.14.0 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-plugin-api@0.6.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-test-utils@0.1.10 + +### Patch Changes + +- 95a3a0b: Rename frontend and backend `setupRequestMockHandlers` methods to `registerMswTestHooks`. +- Updated dependencies + - @backstage/frontend-app-api@0.7.3 + - @backstage/test-utils@1.5.8 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/types@1.1.1 + +## @backstage/integration-react@1.1.29 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.13.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/repo-tools@0.9.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/cli-node@0.2.7 + - @backstage/config-loader@1.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.16 + +### Patch Changes + +- c964a3d: Import discovery from backend-defaults instead of backend-common +- Updated dependencies + - @backstage/backend-defaults@0.4.0 + - @backstage/plugin-techdocs-node@1.12.8 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## @backstage/test-utils@1.5.8 + +### Patch Changes + +- 95a3a0b: Rename frontend and backend `setupRequestMockHandlers` methods to `registerMswTestHooks`. +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/core-app-api@1.14.0 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + +## @backstage/plugin-api-docs@0.11.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-catalog@1.21.1 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-app-backend@0.3.71 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-app-node@0.1.22 + - @backstage/config-loader@1.8.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-app-node@0.1.22 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/config-loader@1.8.1 + +## @backstage/plugin-app-visualizer@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-auth-backend@0.22.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.2.3 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.14 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.5 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.5 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.5 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.17 + - @backstage/plugin-auth-backend-module-github-provider@0.1.19 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.19 + - @backstage/plugin-auth-backend-module-google-provider@0.1.19 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.17 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.2.3 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.15 + - @backstage/plugin-auth-backend-module-oidc-provider@0.2.3 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.15 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.1.3 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.2.3 + +### Patch Changes + +- b9832ae: Fix several issues with the Atlassian auth provider (type definition, profile url, profile transformation, scopes) +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-auth-backend@0.22.9 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-auth-backend@0.22.9 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config@1.2.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-auth-node@0.4.17 + +### Patch Changes + +- 55c1a72: Fix issues with Atlassian OAuth provider: retrieve the email and photo that were not in arrays but rather in single props. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-react@0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-bitbucket-cloud-common@0.2.21 + +### Patch Changes + +- b5deed0: Add support for `autocomplete` handler to provide autocomplete options for `RepoUrlPicker` +- Updated dependencies + - @backstage/integration@1.13.0 + +## @backstage/plugin-catalog@1.21.1 + +### Patch Changes + +- 06c0956: Support i18n for catalog and catalog-react plugins +- d133eaa: Added small notes to AboutCard to discourage customizability PRs +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-scaffolder-common@1.5.4 + - @backstage/plugin-search-common@1.2.13 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/integration-react@1.1.29 + - @backstage/plugin-search-react@1.7.13 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-aws@0.3.17 + +### Patch Changes + +- 4afa050: Export `defaultEksClusterEntityTransformer` to allow library consumers to layer additional changes on top of the default transformer. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + +## @backstage/plugin-catalog-backend-module-azure@0.1.42 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-tasks@0.5.27 + - @backstage/backend-openapi-utils@0.1.15 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-bitbucket-cloud-common@0.2.21 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.36 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.39 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-github@0.6.5 + +### Patch Changes + +- 9112efc: Adds support for `repository` events. + + The provider adds a subscription to the topic `github.repository`. + + Hereby, it supports events of type `repository` with actions + + - `archived` + - `deleted` + - `edited` + - `renamed` + - `transferred` + - `unarchived` + + Actions skipped as they don't require entity changes: + + - `created` + - `privatized` + - `publicized` + + If the config option `validateLocationsExist` is enabled, an API request + is necessary and will be executed. + This affects the actions `renamed`, `transferred`, and `unarchive` + of event type `repository`. + + Catalog entities related to the `GithubEntityProvider` instance will be adjusted + according to action and its meaning for them. + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-catalog-backend@1.24.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-catalog-backend-module-github@0.6.5 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.21 + +### Patch Changes + +- 8db30ad: The Gitlab configuration supports an additional optional boolean key `catalog.providers.gitlab..restrictUsersToGroup`. Setting this to `true` will make Backstage only import users from the group defined in the `group` key, instead of all users in the organisation (self-hosted) or of the root group (SaaS). It will default to false, keeping the original implementation intact, when not explicitly set. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.21 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-catalog-node@1.12.4 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.27 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-catalog-backend@1.24.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-logs@0.0.1 + +### Patch Changes + +- 97caf55: Creates a new module to make logging catalog errors simple. This module subscribes to catalog events and logs them. + + See [Backstage documentation](https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors) for details on how to install + and configure the plugin. + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-catalog-backend@1.24.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.30 + +### Patch Changes + +- f7bdcea: Adds a dynamic provider for the plugin-catalog-backend-module-msgraph. Configuration is now runtime configurable through the ProviderConfigTransformer. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.40 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-backend@1.24.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-scaffolder-common@1.5.4 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.3 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-common@1.0.25 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-search-common@1.2.13 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-catalog-graph@0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.12.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/integration-react@1.1.29 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-node@1.12.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-react@1.12.2 + +### Patch Changes + +- 06c0956: Support i18n for catalog and catalog-react plugins +- 2030962: Make EntityOwnerPicker display metadata.title or spec.profile.displayName for mode=only-owners instead of metadata.name +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/core-components@0.14.9 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/integration-react@1.1.29 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + +## @backstage/plugin-config-schema@0.1.57 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-devtools@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-devtools-common@0.1.11 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-devtools-backend@0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/config-loader@1.8.1 + - @backstage/plugin-devtools-common@0.1.11 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-devtools-common@0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-events-backend@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-events-node@0.3.8 + - @backstage/config@1.2.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-events-node@0.3.8 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-events-backend-module-azure@0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-events-node@0.3.8 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-events-node@0.3.8 + +## @backstage/plugin-events-backend-module-gerrit@0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-events-node@0.3.8 + +## @backstage/plugin-events-backend-module-github@0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/config@1.2.0 + +## @backstage/plugin-events-backend-module-gitlab@0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/config@1.2.0 + +## @backstage/plugin-events-backend-test-utils@0.1.32 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.8 + +## @backstage/plugin-events-node@0.3.8 + +### Patch Changes + +- b05e1e1: Service factories exported by this package have been updated to use the new service factory format that doesn't use a callback. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + +## @backstage/plugin-home@0.7.7 + +### Patch Changes + +- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`. + Updated dependency `@rjsf/core` to `5.18.5`. + Updated dependency `@rjsf/material-ui` to `5.18.5`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.5`. +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-app-api@1.14.0 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-home-react@0.1.15 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## @backstage/plugin-home-react@0.1.15 + +### Patch Changes + +- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`. + Updated dependency `@rjsf/core` to `5.18.5`. + Updated dependency `@rjsf/material-ui` to `5.18.5`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.5`. +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-kubernetes@0.11.12 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-kubernetes-react@0.4.1 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-kubernetes-backend@0.18.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-kubernetes-node@0.1.16 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-cluster@0.0.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-kubernetes-react@0.4.1 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-kubernetes-common@0.8.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-node@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-react@0.4.1 + +### Patch Changes + +- e3cb6ab: Add a namespace label to RolloutDrawer +- 2414d86: Fix the `HTML` markup of the `FixDialog` component, `ul` and `li` are not allowed inside a `p` tag. +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-notifications@0.2.3 + +### Patch Changes + +- 3bf0697: The toolbar on the Notifications page is hidden when there are no listed notifications. +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.4 + +## @backstage/plugin-notifications-backend@0.3.3 + +### Patch Changes + +- d7b8ca5: Added an option to filter notifications by topic +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.3 + - @backstage/plugin-signals-node@0.1.8 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-notifications-backend-module-email@0.1.3 + +### Patch Changes + +- 4e4ef2b: Move notification processor filter parsing to common package +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## @backstage/plugin-notifications-common@0.0.5 + +### Patch Changes + +- 4e4ef2b: Move notification processor filter parsing to common package +- Updated dependencies + - @backstage/config@1.2.0 + +## @backstage/plugin-notifications-node@0.2.3 + +### Patch Changes + +- 4e4ef2b: Move notification processor filter parsing to common package +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.8 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-org@0.6.27 + +### Patch Changes + +- 5132d28: The `useGetEntities` hook could result in requests to `/api/catalog/entities` where the headers exceed the default maximum Node.js header size of 16KB. The hook logic has been adjusted to batch the requests. + +- c307ef4: Added `relationType` property to EntityMembersListCard component that allows for display users related to a group via some other relationship aside from `memberOf`. + + Also, as a side effect, the `relationsType` property has been deprecated in favor of a more accurately named `relationAggregation` property. + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-org-react@0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-permission-backend@0.5.46 + +### Patch Changes + +- ed10fd2: The `PermissionPolicy` interface has been updated to align with the recent changes to the Backstage auth system. The second argument to the `handle` method is now of the new `PolicyQueryUser` type. This type maintains the old fields from the `BackstageIdentityResponse`, which are now all deprecated. Instead, two new fields have been added, which allows access to the same information: + + - `credentials` - A `BackstageCredentials` object, which is useful for making requests to other services on behalf of the user as part of evaluating the policy. This replaces the deprecated `token` field. See the [Auth Service documentation](https://backstage.io/docs/backend-system/core-services/auth#creating-request-tokens) for information about how to create a token using these credentials. + - `info` - A `BackstageUserInfo` object, which contains the same information as the deprecated `identity`, except for the `type` field that was redundant. + + Most existing policies can be updated by replacing the `BackstageIdentityResponse` type with `PolicyQueryUser`, which is exported from `@backstage/plugin-permission-node`, as well as replacing any occurrences of `user?.identity` with `user?.info`. + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.19 + +### Patch Changes + +- ed10fd2: Internal refactor to use new `PolicyQueryUser` type. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + +## @backstage/plugin-permission-react@0.4.24 + +### Patch Changes + +- 28b2cfb: Improve API Reference documentation +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-proxy-backend@0.5.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.14 + +### Patch Changes + +- 661b354: Fixed a bug where the `RepoUrlPicker` would still require the `owner` field for `azure` +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.12 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.12 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.12 + +### Patch Changes + +- b5deed0: Add support for `autocomplete` handler to provide autocomplete options for `RepoUrlPicker` +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/plugin-bitbucket-cloud-common@0.2.21 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.12 + +### Patch Changes + +- 6a4ad4e: Instead of using hardcoded `targetBranch` now fetch the default branch from Bitbucket repository. + This prevents from errors when no `targetBranch` is provided and the default repository branch is different from `master`, for example: `main`. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.46 + +### Patch Changes + +- 0ac124b: Updated configuration instructions +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.4 + +### Patch Changes + +- 0ac124b: Updated configuration instructions +- 2fb0eb8: Added support for passing `variables` to `gitlab:pipeline:trigger` +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.3 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.39 + +### Patch Changes + +- 0ac124b: Updated configuration instructions +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.30 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/plugin-scaffolder-node-test-utils@0.1.9 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-common@1.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node@0.4.8 + +### Patch Changes + +- 661b354: Fixed a bug where the `RepoUrlPicker` would still require the `owner` field for `azure` +- b5deed0: Add support for `autocomplete` extension point to provide additional `autocomplete` handlers +- 0b52438: Serialization of the scaffolder workspace into GCP bucket +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-common@1.5.4 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.9 + +### Patch Changes + +- 54429b5: Filename and imports correction for mockActionContext.ts +- Updated dependencies + - @backstage/backend-test-utils@0.4.4 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/types@1.1.1 + +## @backstage/plugin-search@1.4.14 + +### Patch Changes + +- 1117aba: Update deps in search api extension to include fetch api +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-search-common@1.2.13 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/plugin-search-react@1.7.13 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-search-backend@1.5.14 + +### Patch Changes + +- 343f656: The `AuthorizedSearchEngine` will now ignore the deprecated `token` option, and treat it as an unauthorized request. This will not have any effect in practice, since credentials are always provided by the router. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-defaults@0.4.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/backend-openapi-utils@0.1.15 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/plugin-search-common@1.2.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-search-backend-module-catalog@0.1.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-search-common@1.2.13 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-search-backend-module-elasticsearch@1.5.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/plugin-search-common@1.2.13 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + +## @backstage/plugin-search-backend-module-explore@0.1.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/plugin-search-common@1.2.13 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-pg@0.5.32 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-app-api@0.8.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/plugin-search-common@1.2.13 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/plugin-search-common@1.2.13 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-techdocs@0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-techdocs-node@1.12.8 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-search-common@1.2.13 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-node@1.2.27 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-search-common@1.2.13 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-search-common@1.2.13 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-search-react@1.7.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-search-common@1.2.13 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-signals@0.0.8 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.4 + +## @backstage/plugin-signals-backend@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-signals-node@0.1.8 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-signals-node@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-techdocs@1.10.7 + +### Patch Changes + +- 8fc2622: Fixed an issue that was causing techdocs pages unnecessarily re-render on navigate. +- 6fa652c: Improve default sorting of docs table +- 605b691: Allow for searching TechDocs by entity title +- 60caa92: Fix double scrollbar bug in reader +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.6 + - @backstage/core-components@0.14.9 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-search-common@1.2.13 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/integration-react@1.1.29 + - @backstage/plugin-auth-react@0.1.4 + - @backstage/plugin-search-react@1.7.13 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.35 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.7 + - @backstage/plugin-techdocs-react@1.2.6 + - @backstage/core-app-api@1.14.0 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-catalog@1.21.1 + - @backstage/test-utils@1.5.8 + - @backstage/integration-react@1.1.29 + - @backstage/plugin-search-react@1.7.13 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-techdocs-backend@1.10.9 + +### Patch Changes + +- 9ecf5fd: Adds extension point for publishers to the techdocs backend +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-techdocs-node@1.12.8 + - @backstage/integration@1.13.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.27 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-react@1.2.6 + - @backstage/core-components@0.14.9 + - @backstage/integration@1.13.0 + - @backstage/integration-react@1.1.29 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-techdocs-node@1.12.8 + +### Patch Changes + +- 9ecf5fd: Adds extension point for publishers to the techdocs backend +- 4c4d077: Bumps default version of techdocs docker image to latest +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-search-common@1.2.13 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + +## @backstage/plugin-techdocs-react@1.2.6 + +### Patch Changes + +- 8ac9ce5: Resolved the issue where changes in TechDoc add-ons, including the TextSize add-on, were not reapplying during navigation +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-user-settings@0.8.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-app-api@1.14.0 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.4 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-signals-node@0.1.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.99 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.23.0 + - @backstage/plugin-techdocs@1.10.7 + - @backstage/plugin-techdocs-react@1.2.6 + - @backstage/core-components@0.14.9 + - @backstage/cli@0.26.11 + - @backstage/core-app-api@1.14.0 + - @backstage/frontend-app-api@0.7.3 + - @backstage/plugin-org@0.6.27 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-catalog@1.21.1 + - @backstage/plugin-notifications@0.2.3 + - @backstage/plugin-home@0.7.7 + - @backstage/plugin-scaffolder-react@1.10.0 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-search@1.4.14 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-search-common@1.2.13 + - @backstage/app-defaults@1.5.8 + - @backstage/integration-react@1.1.29 + - @backstage/plugin-api-docs@0.11.7 + - @backstage/plugin-auth-react@0.1.4 + - @backstage/plugin-catalog-graph@0.4.7 + - @backstage/plugin-catalog-import@0.12.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6 + - @backstage/plugin-devtools@0.1.16 + - @backstage/plugin-kubernetes@0.11.12 + - @backstage/plugin-kubernetes-cluster@0.0.13 + - @backstage/plugin-search-react@1.7.13 + - @backstage/plugin-signals@0.0.8 + - @backstage/plugin-user-settings@0.8.9 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## example-app-next@0.0.13 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.23.0 + - @backstage/plugin-techdocs@1.10.7 + - @backstage/plugin-techdocs-react@1.2.6 + - @backstage/core-components@0.14.9 + - @backstage/cli@0.26.11 + - @backstage/core-app-api@1.14.0 + - @backstage/frontend-app-api@0.7.3 + - @backstage/plugin-org@0.6.27 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-catalog@1.21.1 + - @backstage/plugin-notifications@0.2.3 + - @backstage/plugin-home@0.7.7 + - @backstage/plugin-scaffolder-react@1.10.0 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-search@1.4.14 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-search-common@1.2.13 + - @backstage/app-defaults@1.5.8 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/integration-react@1.1.29 + - @backstage/plugin-api-docs@0.11.7 + - @backstage/plugin-app-visualizer@0.1.8 + - @backstage/plugin-auth-react@0.1.4 + - @backstage/plugin-catalog-graph@0.4.7 + - @backstage/plugin-catalog-import@0.12.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6 + - @backstage/plugin-kubernetes@0.11.12 + - @backstage/plugin-kubernetes-cluster@0.0.13 + - @backstage/plugin-search-react@1.7.13 + - @backstage/plugin-signals@0.0.8 + - @backstage/plugin-user-settings@0.8.9 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## app-next-example-plugin@0.0.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/frontend-plugin-api@0.6.7 + +## example-backend@0.0.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-defaults@0.4.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.19 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-scaffolder-backend@1.23.0 + - @backstage/plugin-scaffolder-backend-module-github@0.4.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-backend@0.5.46 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-techdocs-backend@1.10.9 + - @backstage/plugin-notifications-backend@0.3.3 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-search-backend@1.5.14 + - @backstage/plugin-catalog-backend@1.24.0 + - @backstage/plugin-app-backend@0.3.71 + - @backstage/plugin-auth-backend@0.22.9 + - @backstage/plugin-auth-backend-module-github-provider@0.1.19 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.8 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.5 + - @backstage/plugin-catalog-backend-module-openapi@0.1.40 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.9 + - @backstage/plugin-devtools-backend@0.3.8 + - @backstage/plugin-kubernetes-backend@0.18.3 + - @backstage/plugin-proxy-backend@0.5.3 + - @backstage/plugin-search-backend-module-catalog@0.1.28 + - @backstage/plugin-search-backend-module-explore@0.1.28 + - @backstage/plugin-search-backend-module-techdocs@0.1.27 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/plugin-signals-backend@0.1.8 + - @backstage/catalog-model@1.5.0 + +## example-backend-legacy@0.2.100 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-scaffolder-backend@1.23.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-backend@0.5.46 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-techdocs-backend@1.10.9 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.4 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.39 + - @backstage/integration@1.13.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-search-backend@1.5.14 + - @backstage/plugin-catalog-backend@1.24.0 + - @backstage/plugin-app-backend@0.3.71 + - @backstage/plugin-auth-backend@0.22.9 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.9 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-devtools-backend@0.3.8 + - @backstage/plugin-events-backend@0.3.9 + - @backstage/plugin-kubernetes-backend@0.18.3 + - @backstage/plugin-proxy-backend@0.5.3 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.23 + - @backstage/plugin-search-backend-module-catalog@0.1.28 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.3 + - @backstage/plugin-search-backend-module-explore@0.1.28 + - @backstage/plugin-search-backend-module-pg@0.5.32 + - @backstage/plugin-search-backend-module-techdocs@0.1.27 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/plugin-signals-backend@0.1.8 + - @backstage/plugin-signals-node@0.1.8 + - example-app@0.2.99 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## e2e-test@0.2.18 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.17 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.98 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.7 + - @backstage/plugin-techdocs-react@1.2.6 + - @backstage/core-components@0.14.9 + - @backstage/cli@0.26.11 + - @backstage/core-app-api@1.14.0 + - @backstage/plugin-catalog@1.21.1 + - @backstage/test-utils@1.5.8 + - @backstage/app-defaults@1.5.8 + - @backstage/integration-react@1.1.29 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## @internal/plugin-todo-list@1.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-plugin-api@1.9.3 + +## @internal/plugin-todo-list-backend@1.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/errors@1.2.4 + +## @internal/plugin-todo-list-common@1.0.20 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 diff --git a/docs/releases/v1.29.0-next.1-changelog.md b/docs/releases/v1.29.0-next.1-changelog.md new file mode 100644 index 0000000000..34cd038f22 --- /dev/null +++ b/docs/releases/v1.29.0-next.1-changelog.md @@ -0,0 +1,1952 @@ +# Release v1.29.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.29.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.29.0-next.1) + +## @backstage/plugin-catalog-backend@1.24.0-next.1 + +### Minor Changes + +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-openapi-utils@0.1.15-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.28-next.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.7.0-next.1 + +### Minor Changes + +- cb32ca7: **BREAKING**: `readLdapOrg` and the `LdapProviderConfig` type now always accept arrays of user and group configs, not just single items. + + Added support for single ldap catalog provider to provide list and undefined user and group bindings next to standard single one. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + +## @backstage/plugin-scaffolder-react@1.10.0-next.1 + +### Minor Changes + +- 354e68c: Improve validation error display text in scaffolder + +### Patch Changes + +- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`. + Updated dependency `@rjsf/core` to `5.18.5`. + Updated dependency `@rjsf/material-ui` to `5.18.5`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.5`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-permission-react@0.4.23 + - @backstage/plugin-scaffolder-common@1.5.3 + +## @backstage/app-defaults@1.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.23 + +## @backstage/backend-app-api@0.7.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.6 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-permission-node@0.7.33-next.1 + +## @backstage/backend-common@0.23.3-next.1 + +### Patch Changes + +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 +- Updated dependencies + - @backstage/backend-dev-utils@0.1.4 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/backend-defaults@0.3.4-next.1 + +### Patch Changes + +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-app-api@0.7.10-next.1 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-permission-node@0.7.33-next.1 + +## @backstage/backend-dynamic-feature-service@0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-app-api@0.7.10-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.6 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-events-backend@0.3.9-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-search-common@1.2.12 + +## @backstage/backend-openapi-utils@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/errors@1.2.4 + +## @backstage/backend-plugin-api@0.6.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-permission-common@0.7.14 + +## @backstage/backend-tasks@0.5.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.4.4-next.1 + +### Patch Changes + +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 +- Updated dependencies + - @backstage/backend-defaults@0.3.4-next.1 + - @backstage/backend-app-api@0.7.10-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/cli@0.26.11-next.1 + +### Patch Changes + +- 4baac0c: The `backendPlugin` and `backendModule` factory now includes a step for automatically adding the new backend plugin/module to the `index.ts` file of the backend. +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.6 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.13.0-next.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/core-app-api@1.13.1-next.1 + +### Patch Changes + +- db2e2d5: Updated config schema to support app.routes.bindings +- Updated dependencies + - @backstage/core-plugin-api@1.9.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/create-app@0.5.17-next.1 + +### Patch Changes + +- 780d994: Added `MyGroupsSidebarItem` to the sidebar in the `create-app` template +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/dev-utils@1.0.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/app-defaults@1.5.8-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-model@1.5.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/theme@0.5.6 + +## @backstage/frontend-app-api@0.7.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-test-utils@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.7.3-next.1 + - @backstage/test-utils@1.5.8-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/types@1.1.1 + +## @backstage/repo-tools@0.9.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.6 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.8-next.1 + - @backstage/backend-defaults@0.3.4-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## @backstage/test-utils@1.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/core-plugin-api@1.9.3 + - @backstage/config@1.2.0 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-react@0.4.23 + +## @backstage/plugin-api-docs@0.11.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/plugin-catalog@1.21.1-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-model@1.5.0 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-permission-react@0.4.23 + +## @backstage/plugin-app-backend@0.3.71-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-app-node@0.1.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config-loader@1.8.1 + +## @backstage/plugin-auth-backend@0.22.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.2.3-next.1 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.14-next.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.5-next.1 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.5-next.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.5-next.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.17-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.19-next.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.19-next.1 + - @backstage/plugin-auth-backend-module-google-provider@0.1.19-next.1 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.17-next.1 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.2.3-next.1 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.15-next.1 + - @backstage/plugin-auth-backend-module-oidc-provider@0.2.3-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.15-next.1 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.1.3-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-backend@0.22.9-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-backend@0.22.9-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-auth-node@0.4.17-next.1 + +## @backstage/plugin-auth-node@0.4.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog@1.21.1-next.1 + +### Patch Changes + +- 06c0956: Support i18n for catalog and catalog-react plugins +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-permission-react@0.4.23 + - @backstage/plugin-scaffolder-common@1.5.3 + - @backstage/plugin-search-common@1.2.12 + +## @backstage/plugin-catalog-backend-module-aws@0.3.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-kubernetes-common@0.8.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/config@1.2.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.1.15-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.4-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.21-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-catalog-node@1.12.4-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-kubernetes-common@0.8.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-catalog-node@1.12.4-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.6.5-next.1 + +### Patch Changes + +- 9112efc: Adds support for `repository` events. + + The provider adds a subscription to the topic `github.repository`. + + Hereby, it supports events of type `repository` with actions + + - `archived` + - `deleted` + - `edited` + - `renamed` + - `transferred` + - `unarchived` + + Actions skipped as they don't require entity changes: + + - `created` + - `privatized` + - `publicized` + + If the config option `validateLocationsExist` is enabled, an API request + is necessary and will be executed. + This affects the actions `renamed`, `transferred`, and `unarchive` + of event type `repository`. + + Catalog entities related to the `GithubEntityProvider` instance will be adjusted + according to action and its meaning for them. + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.6.5-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.21-next.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-permission-common@0.7.14 + +## @backstage/plugin-catalog-backend-module-logs@0.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-scaffolder-common@1.5.3 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.2 + - @backstage/plugin-permission-common@0.7.14 + +## @backstage/plugin-catalog-graph@0.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.12.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-catalog-common@1.0.24 + +## @backstage/plugin-catalog-node@1.12.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + +## @backstage/plugin-catalog-react@1.12.2-next.1 + +### Patch Changes + +- 06c0956: Support i18n for catalog and catalog-react plugins +- Updated dependencies + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-react@0.4.23 + +## @backstage/plugin-devtools-backend@0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.10 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + +## @backstage/plugin-events-backend@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-events-backend-module-azure@0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-events-backend-module-github@0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-events-backend-module-gitlab@0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-events-backend-test-utils@0.1.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-events-node@0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + +## @backstage/plugin-home@0.7.7-next.1 + +### Patch Changes + +- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`. + Updated dependency `@rjsf/core` to `5.18.5`. + Updated dependency `@rjsf/material-ui` to `5.18.5`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.5`. +- Updated dependencies + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/plugin-home-react@0.1.15-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/theme@0.5.6 + +## @backstage/plugin-home-react@0.1.15-next.1 + +### Patch Changes + +- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`. + Updated dependency `@rjsf/core` to `5.18.5`. + Updated dependency `@rjsf/material-ui` to `5.18.5`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.5`. +- Updated dependencies + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-kubernetes@0.11.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-react@0.4.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-kubernetes-common@0.8.0 + +## @backstage/plugin-kubernetes-backend@0.18.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-kubernetes-common@0.8.0 + - @backstage/plugin-kubernetes-node@0.1.16-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + +## @backstage/plugin-kubernetes-cluster@0.0.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-react@0.4.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-kubernetes-common@0.8.0 + +## @backstage/plugin-kubernetes-node@0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.0 + +## @backstage/plugin-notifications@0.2.3-next.1 + +### Patch Changes + +- 3bf0697: The toolbar on the Notifications page is hidden when there are no listed notifications. +- Updated dependencies + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.4 + - @backstage/plugin-signals-react@0.0.4 + +## @backstage/plugin-notifications-backend@0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-notifications-common@0.0.4 + - @backstage/plugin-notifications-node@0.2.3-next.1 + - @backstage/plugin-signals-node@0.1.8-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.4 + - @backstage/plugin-notifications-node@0.2.3-next.1 + +## @backstage/plugin-notifications-node@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-notifications-common@0.0.4 + - @backstage/plugin-signals-node@0.1.8-next.1 + +## @backstage/plugin-org@0.6.27-next.1 + +### Patch Changes + +- 5132d28: The `useGetEntities` hook could result in requests to `/api/catalog/entities` where the headers exceed the default maximum Node.js header size of 16KB. The hook logic has been adjusted to batch the requests. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-model@1.5.0 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/plugin-catalog-common@1.0.24 + +## @backstage/plugin-org-react@0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-permission-backend@0.5.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + +## @backstage/plugin-permission-node@0.7.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-permission-common@0.7.14 + +## @backstage/plugin-proxy-backend@0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder@1.22.1-next.1 + +### Patch Changes + +- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`. + Updated dependency `@rjsf/core` to `5.18.5`. + Updated dependency `@rjsf/material-ui` to `5.18.5`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.5`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/plugin-scaffolder-react@1.10.0-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-permission-react@0.4.23 + - @backstage/plugin-scaffolder-common@1.5.3 + +## @backstage/plugin-scaffolder-backend@1.23.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.4.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.4-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.21-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20-next.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.14-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.12-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.12-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.12-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.14-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.12-next.1 + - @backstage/plugin-scaffolder-common@1.5.3 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.12-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.12-next.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.21-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.1.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.4.0-next.1 + +### Patch Changes + +- 141f366: Added action to enable GitHub Pages on a repo + +- dfaa28d: Adds `requireLastPushApproval` input property to configure Branch Protection Settings in `github:publish` action + + Adds `requireLastPushApproval` input property to configure Branch Protection Settings in `github:repo:push` action + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.4-next.1 + +### Patch Changes + +- 2fb0eb8: Added support for passing `variables` to `gitlab:pipeline:trigger` +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-notifications-common@0.0.4 + - @backstage/plugin-notifications-node@0.2.3-next.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.1.9-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-scaffolder-node@0.4.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.3 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.9-next.1 + +### Patch Changes + +- 54429b5: Filename and imports correction for mockActionContext.ts +- Updated dependencies + - @backstage/backend-test-utils@0.4.4-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + +## @backstage/plugin-search@1.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.12 + +## @backstage/plugin-search-backend@1.5.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.3.4-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-openapi-utils@0.1.15-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-search-common@1.2.12 + +## @backstage/plugin-search-backend-module-catalog@0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-search-common@1.2.12 + +## @backstage/plugin-search-backend-module-elasticsearch@1.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-search-common@1.2.12 + +## @backstage/plugin-search-backend-module-explore@0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-search-common@1.2.12 + +## @backstage/plugin-search-backend-module-pg@0.5.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-app-api@0.7.10-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-search-common@1.2.12 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-search-common@1.2.12 + +## @backstage/plugin-search-backend-module-techdocs@0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.8-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-search-common@1.2.12 + +## @backstage/plugin-search-backend-node@1.2.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-search-common@1.2.12 + +## @backstage/plugin-signals-backend@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-signals-node@0.1.8-next.1 + +## @backstage/plugin-signals-node@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + +## @backstage/plugin-techdocs@1.10.7-next.1 + +### Patch Changes + +- 60caa92: Fix double scrollbar bug in reader +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.4-next.0 + - @backstage/plugin-search-common@1.2.12 + - @backstage/plugin-techdocs-react@1.2.6-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/plugin-catalog@1.21.1-next.1 + - @backstage/plugin-techdocs@1.10.7-next.1 + - @backstage/core-plugin-api@1.9.3 + - @backstage/test-utils@1.5.8-next.1 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-techdocs-react@1.2.6-next.0 + +## @backstage/plugin-techdocs-backend@1.10.9-next.1 + +### Patch Changes + +- 9ecf5fd: Adds extension point for publishers to the techdocs backend +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.8-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.27-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-permission-common@0.7.14 + +## @backstage/plugin-techdocs-node@1.12.8-next.1 + +### Patch Changes + +- 9ecf5fd: Adds extension point for publishers to the techdocs backend +- 4c4d077: Bumps default version of techdocs docker image to latest +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.12 + +## @backstage/plugin-user-settings@0.8.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.4 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-signals-node@0.1.8-next.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.99-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.6.27-next.1 + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/plugin-catalog@1.21.1-next.1 + - @backstage/plugin-techdocs@1.10.7-next.1 + - @backstage/plugin-notifications@0.2.3-next.1 + - @backstage/plugin-home@0.7.7-next.1 + - @backstage/plugin-scaffolder-react@1.10.0-next.1 + - @backstage/plugin-scaffolder@1.22.1-next.1 + - @backstage/cli@0.26.11-next.1 + - @backstage/app-defaults@1.5.8-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/frontend-app-api@0.7.3-next.1 + - @backstage/plugin-api-docs@0.11.7-next.1 + - @backstage/plugin-catalog-graph@0.4.7-next.1 + - @backstage/plugin-catalog-import@0.12.1-next.1 + - @backstage/plugin-search@1.4.13-next.1 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/plugin-signals@0.0.8-next.0 + - @backstage/plugin-user-settings@0.8.9-next.1 + - @backstage/plugin-kubernetes@0.11.12-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.13-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.4-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.0 + - @backstage/plugin-devtools@0.1.16-next.0 + - @backstage/plugin-permission-react@0.4.23 + - @backstage/plugin-search-common@1.2.12 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.0 + - @backstage/plugin-techdocs-react@1.2.6-next.0 + +## example-app-next@0.0.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.6.27-next.1 + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/plugin-catalog@1.21.1-next.1 + - @backstage/plugin-techdocs@1.10.7-next.1 + - @backstage/plugin-notifications@0.2.3-next.1 + - @backstage/plugin-home@0.7.7-next.1 + - @backstage/plugin-scaffolder-react@1.10.0-next.1 + - @backstage/plugin-scaffolder@1.22.1-next.1 + - @backstage/cli@0.26.11-next.1 + - @backstage/app-defaults@1.5.8-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/frontend-app-api@0.7.3-next.1 + - @backstage/plugin-api-docs@0.11.7-next.1 + - @backstage/plugin-catalog-graph@0.4.7-next.1 + - @backstage/plugin-catalog-import@0.12.1-next.1 + - @backstage/plugin-search@1.4.13-next.1 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/plugin-signals@0.0.8-next.0 + - @backstage/plugin-user-settings@0.8.9-next.1 + - @backstage/plugin-kubernetes@0.11.12-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.13-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-app-visualizer@0.1.8-next.0 + - @backstage/plugin-auth-react@0.1.4-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.0 + - @backstage/plugin-permission-react@0.4.23 + - @backstage/plugin-search-common@1.2.12 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.0 + - @backstage/plugin-techdocs-react@1.2.6-next.0 + +## example-backend@0.0.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@1.10.9-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.4.0-next.1 + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-defaults@0.3.4-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.27-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-app-backend@0.3.71-next.1 + - @backstage/plugin-auth-backend@0.22.9-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.19-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.8-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.5-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.40-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.9-next.1 + - @backstage/plugin-devtools-backend@0.3.8-next.1 + - @backstage/plugin-kubernetes-backend@0.18.3-next.1 + - @backstage/plugin-notifications-backend@0.3.3-next.1 + - @backstage/plugin-permission-backend@0.5.46-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.19-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + - @backstage/plugin-proxy-backend@0.5.3-next.1 + - @backstage/plugin-scaffolder-backend@1.23.0-next.1 + - @backstage/plugin-search-backend@1.5.14-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.28-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.28-next.1 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-signals-backend@0.1.8-next.1 + +## example-backend-legacy@0.2.100-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@1.10.9-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.4-next.1 + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.27-next.1 + - example-app@0.2.99-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-app-backend@0.3.71-next.1 + - @backstage/plugin-auth-backend@0.22.9-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.9-next.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-devtools-backend@0.3.8-next.1 + - @backstage/plugin-events-backend@0.3.9-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-kubernetes-backend@0.18.3-next.1 + - @backstage/plugin-permission-backend@0.5.46-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + - @backstage/plugin-proxy-backend@0.5.3-next.1 + - @backstage/plugin-scaffolder-backend@1.23.0-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.23-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.39-next.1 + - @backstage/plugin-search-backend@1.5.14-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.28-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.3-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.28-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.32-next.1 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-signals-backend@0.1.8-next.1 + - @backstage/plugin-signals-node@0.1.8-next.1 + +## e2e-test@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.17-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.98-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/plugin-catalog@1.21.1-next.1 + - @backstage/plugin-techdocs@1.10.7-next.1 + - @backstage/cli@0.26.11-next.1 + - @backstage/app-defaults@1.5.8-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/test-utils@1.5.8-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-react@1.2.6-next.0 + +## @internal/plugin-todo-list-backend@1.0.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 diff --git a/docs/releases/v1.29.0-next.2-changelog.md b/docs/releases/v1.29.0-next.2-changelog.md new file mode 100644 index 0000000000..f6269407cf --- /dev/null +++ b/docs/releases/v1.29.0-next.2-changelog.md @@ -0,0 +1,446 @@ +# Release v1.29.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.29.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.29.0-next.2) + +## @backstage/app-defaults@1.5.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## @backstage/core-compat-api@0.2.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.7-next.1 + +## @backstage/core-components@0.14.9-next.1 + +### Patch Changes + +- 99d672d: Modified the `Select` component to take in a `data-testid` parameter ensuring backwards compatibility with default value corresponding to previously hardcoded `data-testid` of "select". + +## @backstage/create-app@0.5.17-next.2 + +### Patch Changes + +- e90a2cd: Added the Catalog logs module to the `create-app` template + +## @backstage/dev-utils@1.0.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/app-defaults@1.5.8-next.2 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-catalog-react@1.12.2-next.2 + +## @backstage/frontend-app-api@0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + +## @backstage/frontend-plugin-api@0.6.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## @backstage/frontend-test-utils@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.7.3-next.2 + - @backstage/frontend-plugin-api@0.6.7-next.1 + +## @backstage/plugin-api-docs@0.11.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/plugin-catalog@1.21.1-next.2 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## @backstage/plugin-app-visualizer@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + +## @backstage/plugin-auth-react@0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## @backstage/plugin-catalog@1.21.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-search-react@1.7.13-next.1 + - @backstage/core-compat-api@0.2.7-next.1 + +## @backstage/plugin-catalog-graph@0.4.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## @backstage/plugin-catalog-import@0.12.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## @backstage/plugin-catalog-react@1.12.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/integration-react@1.1.29-next.0 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## @backstage/plugin-config-schema@0.1.57-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## @backstage/plugin-devtools@0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/core-compat-api@0.2.7-next.1 + +## @backstage/plugin-home@0.7.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-home-react@0.1.15-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## @backstage/plugin-home-react@0.1.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## @backstage/plugin-kubernetes@0.11.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-kubernetes-react@0.4.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + +## @backstage/plugin-kubernetes-cluster@0.0.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-kubernetes-react@0.4.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + +## @backstage/plugin-kubernetes-react@0.4.1-next.1 + +### Patch Changes + +- e3cb6ab: Add a namespace label to RolloutDrawer +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## @backstage/plugin-notifications@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## @backstage/plugin-org@0.6.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## @backstage/plugin-org-react@0.1.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + +## @backstage/plugin-scaffolder@1.22.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-scaffolder-react@1.10.0-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## @backstage/plugin-scaffolder-backend@1.23.0-next.2 + +### Patch Changes + +- ff1bb4c: Added a documentation how to use checkpoints + +## @backstage/plugin-scaffolder-react@1.10.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + +## @backstage/plugin-search@1.4.14-next.2 + +### Patch Changes + +- 1117aba: Update deps in search api extension to include fetch api +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-search-react@1.7.13-next.1 + - @backstage/core-compat-api@0.2.7-next.1 + +## @backstage/plugin-search-react@1.7.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + +## @backstage/plugin-signals@0.0.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## @backstage/plugin-techdocs@1.10.7-next.2 + +### Patch Changes + +- 6fa652c: Improve default sorting of docs table +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-auth-react@0.1.4-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-search-react@1.7.13-next.1 + - @backstage/plugin-techdocs-react@1.2.6-next.1 + - @backstage/core-compat-api@0.2.7-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.35-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.7-next.2 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-catalog@1.21.1-next.2 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-search-react@1.7.13-next.1 + - @backstage/plugin-techdocs-react@1.2.6-next.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-techdocs-react@1.2.6-next.1 + +## @backstage/plugin-techdocs-react@1.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## @backstage/plugin-user-settings@0.8.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## example-app@0.2.99-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.7-next.2 + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-search@1.4.14-next.2 + - @backstage/app-defaults@1.5.8-next.2 + - @backstage/cli@0.26.11-next.1 + - @backstage/frontend-app-api@0.7.3-next.2 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-api-docs@0.11.7-next.2 + - @backstage/plugin-auth-react@0.1.4-next.1 + - @backstage/plugin-catalog@1.21.1-next.2 + - @backstage/plugin-catalog-graph@0.4.7-next.2 + - @backstage/plugin-catalog-import@0.12.1-next.2 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.1 + - @backstage/plugin-devtools@0.1.16-next.1 + - @backstage/plugin-home@0.7.7-next.2 + - @backstage/plugin-kubernetes@0.11.12-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.13-next.2 + - @backstage/plugin-notifications@0.2.3-next.2 + - @backstage/plugin-org@0.6.27-next.2 + - @backstage/plugin-scaffolder@1.22.1-next.2 + - @backstage/plugin-scaffolder-react@1.10.0-next.2 + - @backstage/plugin-search-react@1.7.13-next.1 + - @backstage/plugin-signals@0.0.8-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.1 + - @backstage/plugin-techdocs-react@1.2.6-next.1 + - @backstage/plugin-user-settings@0.8.9-next.2 + +## example-app-next@0.0.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.7-next.2 + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-search@1.4.14-next.2 + - @backstage/app-defaults@1.5.8-next.2 + - @backstage/cli@0.26.11-next.1 + - @backstage/frontend-app-api@0.7.3-next.2 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-api-docs@0.11.7-next.2 + - @backstage/plugin-app-visualizer@0.1.8-next.1 + - @backstage/plugin-auth-react@0.1.4-next.1 + - @backstage/plugin-catalog@1.21.1-next.2 + - @backstage/plugin-catalog-graph@0.4.7-next.2 + - @backstage/plugin-catalog-import@0.12.1-next.2 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.1 + - @backstage/plugin-home@0.7.7-next.2 + - @backstage/plugin-kubernetes@0.11.12-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.13-next.2 + - @backstage/plugin-notifications@0.2.3-next.2 + - @backstage/plugin-org@0.6.27-next.2 + - @backstage/plugin-scaffolder@1.22.1-next.2 + - @backstage/plugin-scaffolder-react@1.10.0-next.2 + - @backstage/plugin-search-react@1.7.13-next.1 + - @backstage/plugin-signals@0.0.8-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.1 + - @backstage/plugin-techdocs-react@1.2.6-next.1 + - @backstage/plugin-user-settings@0.8.9-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## app-next-example-plugin@0.0.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + +## example-backend@0.0.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.23.0-next.2 + +## example-backend-legacy@0.2.100-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.23.0-next.2 + - example-app@0.2.99-next.2 + +## e2e-test@0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.17-next.2 + +## techdocs-cli-embedded-app@0.2.98-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.7-next.2 + - @backstage/core-components@0.14.9-next.1 + - @backstage/app-defaults@1.5.8-next.2 + - @backstage/cli@0.26.11-next.1 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-catalog@1.21.1-next.2 + - @backstage/plugin-techdocs-react@1.2.6-next.1 + +## @internal/plugin-todo-list@1.0.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 diff --git a/docs/releases/v1.29.0.md b/docs/releases/v1.29.0.md new file mode 100644 index 0000000000..5f7ed1db19 --- /dev/null +++ b/docs/releases/v1.29.0.md @@ -0,0 +1,117 @@ +--- +id: v1.29.0 +title: v1.29.0 +description: Backstage Release v1.29.0 +--- + +These are the release notes for the v1.29.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 + +### BREAKING: Backend System deprecations and removals + +As part of the [work towards a stable 1.0 release of the new backend system](https://github.com/backstage/backstage/issues/24493), there are several new deprecations and breaking changes in the backend system packages: + +**Breaking**: + +- The deprecated `token` option has been removed from `PermissionsService`, and the request options are now required and must contain a `credentials` object. +- The deprecated `getPath` option has been removed from `httpRouterServiceFactory`, the plugin paths are now always `/api/`. +- It is no longer possible to pass service factory callbacks to the `defaultServiceFactories` option of `createSpecializedBackend`. + +**Deprecations**: + +- The ability to define options for service factories through `createServiceFactory` has been deprecated. See the [service architecture documentation](https://backstage.io/docs/backend-system/architecture/services) for more information on how to define customizable services. +- The ability to install backend features in callback form, i.e. `() => BackendFeature`, has been deprecated. This also includes other usages such as `startTestBackend`, and dynamically imported backend features. No manual changes should be needed for this change, as all backend feature creators have been updated to return `BackendFeature` instances directly. +- The `ServiceFactoryTest.get` method has been renamed to `ServiceFactoryTest.getSubject`, and is now deprecated. +- The following types have been renamed to use an `*Options` suffix instead: `ServiceRefConfig`, `RootServiceFactoryConfig`, `PluginServiceFactoryConfig`. +- Deprecated all exports related to the legacy status checker in `@backstage/backend-common`. +- The `isDockerDisabledForTests` function exported by `@backstage/backend-test-utils` has been deprecated. + +### Backend Health Service + +A new health service as been added to the new backend system. This service provides health check endpoints for the backend, and replaces `createStatusCheckRouter` from `@backstage/backend-common`. + +The service helps implement the new `/.backstage/health/v1/readiness` and `/.backstage/health/v1/liveness` endpoints, which provide health checks for the entire backend instance. + +You can read more about this new service and how to customize it in the [Root Health Service documentation](https://backstage.io/docs/backend-system/core-services/root-health). + +### New Catalog Logs module + +This new `@backstage/plugin-catalog-backend-module-logs` module is a minimal module that will log any error events that are published by the catalog. This module is useful for making sure that catalog errors are visible in the logs, but you may want to replace it with a more customized solution if the resulting logs are too verbose. + +### Updates to the `@backstage/create-app` template + +New backstage projects created with `@backstage/create-app` will now include the Catalog Logs module for logging catalog error events, as well as support for the Postgres Search Engine. + +### Permission Policy deprecations + +The `PermissionPolicy` interface has been updated to align with the recent changes to the Backstage auth system. The second argument to the `handle` method is now of the new `PolicyQueryUser` type. This type maintains the old fields from the `BackstageIdentityResponse`, which are now all deprecated. Instead, two new fields have been added, which allows access to the same information: + +- `credentials` - A `BackstageCredentials` object, which is useful for making requests to other services on behalf of the user as part of evaluating the policy. This replaces the deprecated `token` field. See the [Auth Service documentation](https://backstage.io/docs/backend-system/core-services/auth#creating-request-tokens) for information about how to create a token using these credentials. +- `info` - A `BackstageUserInfo` object, which contains the same information as the deprecated `identity`, except for the `type` field that was redundant. + +Most existing policies can be updated by replacing the `BackstageIdentityResponse` type with `PolicyQueryUser`, which is exported from `@backstage/plugin-permission-node`, as well as replacing any occurrences of `user?.identity` with `user?.info`. + +### Renaming the `setupRequestMockHandlers` test utility + +The `setupRequestMockHandlers` utility function exported by `@backstage/test-utils` and `@backstage/backend-test-utils` has been renamed to `registerMswTestHooks`. This is done to better reflect the context and the purpose of the function. The old name is deprecated and will be removed in a future release. + +### Catalog GitHub module support for `repository` events + +The GitHub provider module and `GithubEntityProvider` for the Catalog now supports event driven ingestion of repositories by subscribing to `repository` events from GitHub. This includes the actions `archived`, `deleted`, `edited`, `renamed`, `transferred`, and `unarchived`. This is in addition to the existing support for `push` events, which you can read more about in the integration documentation for [GitHub Discovery](https://backstage.io/docs/integrations/github/discovery#events-support). + +Contributed by [@pjungermann](https://github.com/pjungermann) in [#25360](https://github.com/backstage/backstage/pull/25360) + +### Catalog i18n support + +The Catalog plugin as well as the Catalog React library now support internationalization (i18n). This means that you can customize the messaging in the catalog, as well as add translations. You can read more about this in the [i18n documentation](https://backstage.io/docs/plugins/internationalization/). + +Contributed by [@mario-mui](https://github.com/mario-mui) in [#23392](https://github.com/backstage/backstage/pull/23392) + +### Route Binding configuration improvements + +It is now possible to explicitly remove default route bindings, for cases where you don't want a plugin route to be bound to any target at all: + +```yaml +app: + routes: + bindings: + # This has the effect of removing the button for registering new + # catalog entities in the scaffolder template list view + scaffolder.registerComponent: false +``` + +### Scaffolder Fields performance improvements + +The `EntityPicker` and `MultiEntityPicker` fields have been updated to improve performance with large catalogs. Contributed by [@kmikko](https://github.com/kmikko) in [#25315](https://github.com/backstage/backstage/pull/25315), [#25380](https://github.com/backstage/backstage/pull/25380) + +### BREAKING: Catalog LDAP Module improvements + +The `@backstage/plugin-catalog-backend-module-ldap` module has been improved to support multiple or no declarations of both user and group configs. + +This change is breaking for `readLdapOrg` and `LdapProviderConfig`, which now both always accept arrays of `users` and `groups` configurations. + +Contributed by [@Jenson3210](https://github.com/Jenson3210) in [#25261](https://github.com/backstage/backstage/pull/25261) + +## 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.29.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.30.0-next.0-changelog.md b/docs/releases/v1.30.0-next.0-changelog.md new file mode 100644 index 0000000000..f3b7f2ef45 --- /dev/null +++ b/docs/releases/v1.30.0-next.0-changelog.md @@ -0,0 +1,2214 @@ +# Release v1.30.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.30.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.30.0-next.0) + +## @backstage/cli@0.27.0-next.0 + +### Minor Changes + +- 32a38e1: **BREAKING**: The lockfile (`yarn.lock`) dependency analysis and mutations have been removed from several commands. + + The `versions:bump` command will no longer attempt to bump and deduplicate dependencies by modifying the lockfile, it will only update `package.json` files. + + The `versions:check` command has been removed, since its only purpose was verification and mutation of the lockfile. We recommend using the `yarn dedupe` command instead, or the `yarn-deduplicate` package if you're using Yarn classic. + + The check that was built into the `package start` command has been removed, it will no longer warn about lockfile mismatches. + + The packages in the Backstage ecosystem handle package duplications much better now than when these CLI features were first introduced, so the need for these features has diminished. By removing them, we drastically reduce the integration between the Backstage CLI and Yarn, making it much easier to add support for other package managers in the future. + +### Patch Changes + +- 7eb08a6: Add frontend-dynamic-container role to eslint config factory +- fbc7819: Use ES2022 in CLI bundler +- e53074f: Updated default backend plugin to use `RootConfigService` instead of `Config`. This also removes the dependency on `@backstage/config` as it's no longer used. +- ee2b0e5: The experimental module federation build now has the ability to force the use of development versions of `react` and `react-dom` by setting the `FORCE_REACT_DEVELOPMENT` flag. +- 239dffc: Remove usage of deprecated functionality from @backstage/config-loader +- 0eedec3: Add support for dynamic plugins via the EXPERIMENTAL_MODULE_FEDERATION environment variable when running `yarn start`. +- dc4fb4f: Fix for `repo build --all` not properly detecting the experimental public entry point. +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/integration@1.14.0-next.0 + +### Minor Changes + +- 78c1329: Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens. + +### Patch Changes + +- c591670: Updated functions for `getHarnessEditContentsUrl`, `getHarnessFileContentsUrl`, `getHarnessArchiveUrl`, `getHarnessLatestCommitUrl` and `parseHarnessUrl` to handle account and org level urls +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-react@1.11.0-next.0 + +### Minor Changes + +- 8839381: Add scaffolder option to display object items in separate rows on review page + +### Patch Changes + +- d0e95a7: Add ability to customise form fields in the UI by exposing `uiSchema` and `formContext` in `FormProps` +- 4670f06: support `ajv-errors` for scaffolder validation to allow for customizing the error messages +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-scaffolder-common@1.5.4 + +## @backstage/app-defaults@1.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.24 + +## @backstage/backend-app-api@0.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## @backstage/backend-common@0.23.4-next.0 + +### Patch Changes + +- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. + +- 7e13b7a: The remaining exports in the package have now been deprecated: + + - `cacheToPluginCacheManager` + - `createLegacyAuthAdapters` + - `LegacyCreateRouter` + - `legacyPlugin` + - `loggerToWinstonLogger` + - `makeLegacyPlugin` + + Users of these export should fully [migrate to the new backend system](https://backstage.io/docs/backend-system/building-backends/migrating). + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/backend-defaults@0.4.2-next.0 + +### Patch Changes + +- 4e79d19: The `createHealthRouter` utility that allows you to create a health check router is now exported via `@backstage/backend-defaults/rootHttpRouter`. +- 78c1329: Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens. +- 8e967da: Fixed the routing of the new health check service, the health endpoints should now properly be available at `/.backstage/health/v1/readiness` and `/.backstage/health/v1/liveness`. +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-app-api@0.8.1-next.0 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## @backstage/backend-dynamic-feature-service@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-app-api@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.23-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-events-backend@0.3.10-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-search-common@1.2.13 + +## @backstage/backend-openapi-utils@0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/errors@1.2.4 + +## @backstage/backend-plugin-api@0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-permission-common@0.8.0 + +## @backstage/backend-tasks@0.5.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.4.5-next.0 + +### Patch Changes + +- 4e79d19: The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service. +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.0 + - @backstage/backend-app-api@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/config-loader@1.8.2-next.0 + +### Patch Changes + +- 493feac: Add boolean `allowMissingDefaultConfig` option to `ConfigSources.default` and + `ConfigSources.defaultForTargets`, which results in omission of a ConfigSource + for the default app-config.yaml configuration file if it's not present. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/core-app-api@1.14.1-next.0 + +### Patch Changes + +- 9a46a81: The request to delete the session cookie when running the app in protected mode is now done with a plain `fetch` rather than `FetchApi`. This fixes a bug where the app would immediately try to sign-in again when removing the cookie during logout. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/core-compat-api@0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/core-components@0.14.10-next.0 + +### Patch Changes + +- 678971a: Move the `Link` component to the `RoutedTabs` instead of the `HeaderTabs` component +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/version-bridge@1.0.8 + +## @backstage/create-app@0.5.18-next.0 + +### Patch Changes + +- c0a705d: Added the Kubernetes plugin to `create-app` +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/dev-utils@1.0.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/app-defaults@1.5.9-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + +## @backstage/frontend-app-api@0.7.4-next.0 + +### Patch Changes + +- 7777b5f: Support icon overriding with the new `IconBundleBlueprint` API. +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-plugin-api@0.6.8-next.0 + +### Patch Changes + +- 4e53ad6: Introduce a new way to encapsulate extension kinds that replaces the extension creator pattern with `createExtensionBlueprint` + + This allows the creation of extension instances with the following pattern: + + ```tsx + // create the extension blueprint which is used to create instances + const EntityCardBlueprint = createExtensionBlueprint({ + kind: 'entity-card', + attachTo: { id: 'test', input: 'default' }, + output: { + element: coreExtensionData.reactElement, + }, + factory(params: { text: string }) { + return { + element:

{params.text}

, + }; + }, + }); + + // create an instance of the extension blueprint with params + const testExtension = EntityCardBlueprint.make({ + name: 'foo', + params: { + text: 'Hello World', + }, + }); + ``` + +- 9b89b82: The `ExtensionBoundary` now by default infers whether it's routable from whether it outputs a route path. + +- 7777b5f: Added a new `IconBundleBlueprint` that lets you create icon bundle extensions that can be installed in an App in order to override or add new app icons. + + ```tsx + import { IconBundleBlueprint } from '@backstage/frontend-plugin-api'; + + const exampleIconBundle = IconBundleBlueprint.make({ + name: 'example-bundle', + params: { + icons: { + user: MyOwnUserIcon, + }, + }, + }); + ``` + +- 31bfc44: Extension data references can now be defined in a way that encapsulates the ID string in the type, in addition to the data type itself. The old way of creating extension data references is deprecated and will be removed in a future release. + + For example, the following code: + + ```ts + export const myExtension = + createExtensionDataRef('my-plugin.my-data'); + ``` + + Should be updated to the following: + + ```ts + export const myExtension = createExtensionDataRef().with({ + id: 'my-plugin.my-data', + }); + ``` + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-test-utils@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/frontend-app-api@0.7.4-next.0 + - @backstage/test-utils@1.5.9-next.0 + - @backstage/types@1.1.1 + +## @backstage/integration-react@1.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/repo-tools@0.9.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/plugin-techdocs-node@1.12.9-next.0 + +## @backstage/test-utils@1.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-react@0.4.24 + +## @backstage/plugin-api-docs@0.11.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog@1.21.2-next.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-permission-react@0.4.24 + +## @backstage/plugin-app-backend@0.3.72-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.23-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-app-node@0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + +## @backstage/plugin-app-visualizer@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-auth-backend@0.22.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4-next.0 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15-next.0 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6-next.0 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6-next.0 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.6-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.20-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.2.4-next.0 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.16-next.0 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-backend@0.22.10-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18-next.0 + +### Patch Changes + +- 39f36a9: Updated the Microsoft authenticator to accurately define required scopes, but to also omit the required and additional scopes when requesting resource scopes. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-backend@0.22.10-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## @backstage/plugin-auth-node@0.4.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-react@0.1.5-next.0 + +### Patch Changes + +- aeac3e9: feat: Hide visibility of CookieAuthRedirect +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + +## @backstage/plugin-catalog@1.21.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/plugin-search-react@1.7.14-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-scaffolder-common@1.5.4 + - @backstage/plugin-search-common@1.2.13 + +## @backstage/plugin-catalog-backend@1.24.1-next.0 + +### Patch Changes + +- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-openapi-utils@0.1.16-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.3.18-next.0 + +### Patch Changes + +- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. +- 90a7340: `AwsOrganizationCloudAccountProcessor` configuration field `roleArn` is deprecated in favor of new field `accountId` +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-kubernetes-common@0.8.1 + +## @backstage/plugin-catalog-backend-module-azure@0.1.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.25 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-openapi-utils@0.1.16-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-kubernetes-common@0.8.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-github@0.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-backend-module-github@0.6.6-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-permission-common@0.8.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.25 + +## @backstage/plugin-catalog-backend-module-logs@0.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.31-next.0 + +### Patch Changes + +- 58dff4d: Added option to ingest groups based on their group membership in Azure Entra ID +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.25 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.25 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-scaffolder-common@1.5.4 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.3 + - @backstage/plugin-permission-common@0.8.0 + +## @backstage/plugin-catalog-graph@0.4.8-next.0 + +### Patch Changes + +- fba7537: Memoize entity graph nodes when applying an `entityFilter` to prevent repeated redraws +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.12.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/plugin-catalog-common@1.0.25 + +## @backstage/plugin-catalog-node@1.12.5-next.0 + +### Patch Changes + +- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## @backstage/plugin-catalog-react@1.12.3-next.0 + +### Patch Changes + +- 31bfc44: Updated alpha definitions of extension data references. +- 9b89b82: Internal refactor to remove unnecessary `routable` prop in the implementation of the `createEntityContentExtension` alpha export. +- bebd569: Fix extra divider displayed on user list picker component +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-react@0.4.24 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-config-schema@0.1.58-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-devtools@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-devtools-common@0.1.11 + - @backstage/plugin-permission-react@0.4.24 + +## @backstage/plugin-devtools-backend@0.3.9-next.0 + +### Patch Changes + +- 32a38e1: Removed unused code for lockfile analysis. +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.11 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## @backstage/plugin-events-backend@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.3.9-next.0 + +### Patch Changes + +- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-events-backend-module-azure@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-events-backend-module-github@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-events-backend-module-gitlab@0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-events-backend-test-utils@0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-events-node@0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + +## @backstage/plugin-home@0.7.8-next.0 + +### Patch Changes + +- 31bfc44: Updated alpha definitions of extension data references. +- fdcc059: Fixed a bug on the WelcomeTitle component where the welcome message wasn't correct when the language was set to Spanish +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-home-react@0.1.16-next.0 + +## @backstage/plugin-home-react@0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-kubernetes@0.11.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/plugin-kubernetes-react@0.4.2-next.0 + +## @backstage/plugin-kubernetes-backend@0.18.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/plugin-kubernetes-node@0.1.17-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/plugin-kubernetes-react@0.4.2-next.0 + +## @backstage/plugin-kubernetes-node@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.1 + +## @backstage/plugin-kubernetes-react@0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.1 + +## @backstage/plugin-notifications@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-react@0.0.4 + +## @backstage/plugin-notifications-backend@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.0 + - @backstage/plugin-signals-node@0.1.9-next.0 + +## @backstage/plugin-notifications-backend-module-email@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.0 + +## @backstage/plugin-notifications-node@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.9-next.0 + +## @backstage/plugin-org@0.6.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.25 + +## @backstage/plugin-org-react@0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-permission-backend@0.5.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## @backstage/plugin-permission-node@0.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-permission-common@0.8.0 + +## @backstage/plugin-proxy-backend@0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder@1.23.1-next.0 + +### Patch Changes + +- bbd9f56: Cleaned up codebase of RepoUrlPicker +- b8600fe: Fix issue with `RepoUrlPicker` not refreshing the credentials for a different host +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/plugin-scaffolder-react@1.11.0-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-scaffolder-common@1.5.4 + +## @backstage/plugin-scaffolder-backend@1.23.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.0 + - @backstage/plugin-scaffolder-common@1.5.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.47-next.0 + +### Patch Changes + +- dae85df: Add examples for `fetch:cookiecutter` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.0 + +### Patch Changes + +- fad1b90: Allow the `createGitlabProjectVariableAction` to use oauth tokens + +- aab708e: Added test cases for gitlab:issue:edit examples + +- ef742dc: Added test cases for gitlab:projectAccessToken:create example + +- 1ba4c2f: Added test cases for gitlab:pipeline:trigger examples + +- a6603e4: Add custom action for merge request: **auto** + + The **Auto** action selects the committed action between _create_ and _update_. + + The **Auto** action fetches files using the **/projects/repository/tree endpoint**. + After fetching, it checks if the file exists locally and in the repository. If it does, it chooses **update**; otherwise, it chooses **create**. + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.6-next.0 + +### Patch Changes + +- 6fc03c7: Add examples for notification:send scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.0 + +### Patch Changes + +- 449def7: Add examples for fetch:rails scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.0 + +## @backstage/plugin-scaffolder-node@0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.4 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-test-utils@0.4.5-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## @backstage/plugin-search@1.4.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/plugin-search-react@1.7.14-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.13 + +## @backstage/plugin-search-backend@1.5.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-defaults@0.4.2-next.0 + - @backstage/backend-openapi-utils@0.1.16-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-search-common@1.2.13 + +## @backstage/plugin-search-backend-module-catalog@0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-search-common@1.2.13 + +## @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-search-common@1.2.13 + +## @backstage/plugin-search-backend-module-explore@0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-search-common@1.2.13 + +## @backstage/plugin-search-backend-module-pg@0.5.33-next.0 + +### Patch Changes + +- 7251567: Removing `@backstage/backend-app-api` dependency +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-search-common@1.2.13 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-search-common@1.2.13 + +## @backstage/plugin-search-backend-module-techdocs@0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-search-common@1.2.13 + - @backstage/plugin-techdocs-node@1.12.9-next.0 + +## @backstage/plugin-search-backend-node@1.2.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-search-common@1.2.13 + +## @backstage/plugin-search-react@1.7.14-next.0 + +### Patch Changes + +- 31bfc44: Updated alpha definitions of extension data references. +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.13 + +## @backstage/plugin-signals@0.0.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.4 + +## @backstage/plugin-signals-backend@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-signals-node@0.1.9-next.0 + +## @backstage/plugin-signals-node@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## @backstage/plugin-techdocs@1.10.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/plugin-search-react@1.7.14-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-search-common@1.2.13 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/plugin-search-react@1.7.14-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/test-utils@1.5.9-next.0 + - @backstage/plugin-catalog@1.21.2-next.0 + - @backstage/plugin-techdocs@1.10.8-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## @backstage/plugin-techdocs-backend@1.10.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.0 + - @backstage/plugin-techdocs-node@1.12.9-next.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## @backstage/plugin-techdocs-node@1.12.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.13 + +## @backstage/plugin-techdocs-react@1.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-user-settings@0.8.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.4 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-signals-node@0.1.9-next.0 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.100-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/plugin-search-react@1.7.14-next.0 + - @backstage/plugin-home@0.7.8-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/plugin-scaffolder-react@1.11.0-next.0 + - @backstage/frontend-app-api@0.7.4-next.0 + - @backstage/plugin-catalog-graph@0.4.8-next.0 + - @backstage/plugin-scaffolder@1.23.1-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/app-defaults@1.5.9-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.8-next.0 + - @backstage/plugin-catalog@1.21.2-next.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-catalog-import@0.12.2-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-devtools@0.1.17-next.0 + - @backstage/plugin-kubernetes@0.11.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.0 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-org@0.6.28-next.0 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-search@1.4.15-next.0 + - @backstage/plugin-search-common@1.2.13 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs@1.10.8-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + - @backstage/plugin-user-settings@0.8.10-next.0 + +## example-app-next@0.0.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.0 + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/plugin-search-react@1.7.14-next.0 + - @backstage/plugin-home@0.7.8-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/plugin-scaffolder-react@1.11.0-next.0 + - @backstage/frontend-app-api@0.7.4-next.0 + - @backstage/plugin-catalog-graph@0.4.8-next.0 + - @backstage/plugin-scaffolder@1.23.1-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/app-defaults@1.5.9-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.8-next.0 + - @backstage/plugin-app-visualizer@0.1.9-next.0 + - @backstage/plugin-catalog@1.21.2-next.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-catalog-import@0.12.2-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-kubernetes@0.11.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.0 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-org@0.6.28-next.0 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-search@1.4.15-next.0 + - @backstage/plugin-search-common@1.2.13 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs@1.10.8-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + - @backstage/plugin-user-settings@0.8.10-next.0 + +## app-next-example-plugin@0.0.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/core-components@0.14.10-next.0 + +## example-backend@0.0.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/plugin-devtools-backend@0.3.9-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-app-backend@0.3.72-next.0 + - @backstage/plugin-auth-backend@0.22.10-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.0 + - @backstage/plugin-kubernetes-backend@0.18.4-next.0 + - @backstage/plugin-notifications-backend@0.3.4-next.0 + - @backstage/plugin-permission-backend@0.5.47-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + - @backstage/plugin-proxy-backend@0.5.4-next.0 + - @backstage/plugin-scaffolder-backend@1.23.1-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.0 + - @backstage/plugin-search-backend@1.5.15-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-signals-backend@0.1.9-next.0 + - @backstage/plugin-techdocs-backend@1.10.10-next.0 + +## example-backend-legacy@0.2.101-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/plugin-devtools-backend@0.3.9-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.33-next.0 + - example-app@0.2.100-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-app-backend@0.3.72-next.0 + - @backstage/plugin-auth-backend@0.22.10-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.0 + - @backstage/plugin-events-backend@0.3.10-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-kubernetes-backend@0.18.4-next.0 + - @backstage/plugin-permission-backend@0.5.47-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + - @backstage/plugin-proxy-backend@0.5.4-next.0 + - @backstage/plugin-scaffolder-backend@1.23.1-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.0 + - @backstage/plugin-search-backend@1.5.15-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-signals-backend@0.1.9-next.0 + - @backstage/plugin-signals-node@0.1.9-next.0 + - @backstage/plugin-techdocs-backend@1.10.10-next.0 + +## e2e-test@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.18-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.99-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/app-defaults@1.5.9-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/test-utils@1.5.9-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog@1.21.2-next.0 + - @backstage/plugin-techdocs@1.10.8-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## @internal/plugin-todo-list@1.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @internal/plugin-todo-list-backend@1.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 diff --git a/docs/releases/v1.6.0-changelog.md b/docs/releases/v1.6.0-changelog.md index 0532243cbf..e9692f4f1b 100644 --- a/docs/releases/v1.6.0-changelog.md +++ b/docs/releases/v1.6.0-changelog.md @@ -248,8 +248,7 @@ 1. First we need to add the @backstage/plugin-azure-devops package to your frontend app: - ```bash - # From your Backstage root directory + ```bash title="From your Backstage root directory" yarn add --cwd packages/app @backstage/plugin-azure-devops ``` @@ -313,8 +312,7 @@ 1. First we need to add the @backstage/plugin-azure-devops package to your frontend app: - ```bash - # From your Backstage root directory + ```bash title="From your Backstage root directory" yarn add --cwd packages/app @backstage/plugin-azure-devops ``` diff --git a/docs/releases/v1.6.0-next.0-changelog.md b/docs/releases/v1.6.0-next.0-changelog.md index 5ff2311b79..fbe9caefde 100644 --- a/docs/releases/v1.6.0-next.0-changelog.md +++ b/docs/releases/v1.6.0-next.0-changelog.md @@ -10,8 +10,7 @@ 1. First we need to add the @backstage/plugin-azure-devops package to your frontend app: - ```bash - # From your Backstage root directory + ```bash title="From your Backstage root directory" yarn add --cwd packages/app @backstage/plugin-azure-devops ``` @@ -69,8 +68,7 @@ 1. First we need to add the @backstage/plugin-azure-devops package to your frontend app: - ```bash - # From your Backstage root directory + ```bash title="From your Backstage root directory" yarn add --cwd packages/app @backstage/plugin-azure-devops ``` diff --git a/docs/tooling/cli/03-commands.md b/docs/tooling/cli/03-commands.md index 234d0d04b7..a6652bd036 100644 --- a/docs/tooling/cli/03-commands.md +++ b/docs/tooling/cli/03-commands.md @@ -26,7 +26,6 @@ repo [command] Command that run across an entire package [command] Lifecycle scripts for individual packages migrate [command] Migration utilities versions:bump [options] Bump Backstage packages to the latest versions -versions:check [options] Check Backstage package versioning clean Delete cache directories [DEPRECATED] build-workspace [packages...] Builds a temporary dist workspace from the provided packages @@ -327,8 +326,7 @@ Options: ## versions\:bump Bump all `@backstage` packages to the latest versions. This checks for updates -in the package registry, and will update entries both in `yarn.lock` and -`package.json` files when necessary. +in the package registry, and will update entries `package.json` files when necessary. ```text Usage: backstage-cli versions:bump [options] @@ -339,23 +337,6 @@ Options: --release Bump to a specific Backstage release line or version (default: "main") ``` -## versions\:check - -Validate `@backstage` dependencies within the repo, making sure that there are -no duplicates of packages that might lead to breakages. - -By supplying the `--fix` flag the command will attempt to fix any conflict that -can be resolved by editing `yarn.lock`, but will not attempt to search for -remote updates or modify any `package.json` files. - -```text -Usage: backstage-cli versions:check [options] - -Options: - --fix Fix any auto-fixable versioning problems - -h, --help display help for command -``` - ## build-workspace Builds a mirror of the workspace using the packaged production version of each diff --git a/docs/tutorials/configuring-plugin-databases.md b/docs/tutorials/configuring-plugin-databases.md index 528d2c6325..db3f126de7 100644 --- a/docs/tutorials/configuring-plugin-databases.md +++ b/docs/tutorials/configuring-plugin-databases.md @@ -36,12 +36,15 @@ Please ensure the appropriate database drivers are installed in your `backend` package. If you intend to use both PostgreSQL and SQLite, you can install both of them. -```bash -# From your Backstage root directory -# install pg if you need PostgreSQL -yarn --cwd packages/backend add pg +Install pg if you need PostgreSQL: -# install SQLite 3 if you intend to set it as the client +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add pg +``` + +Install SQLite 3 if you intend to set it as the client: + +```bash title="From your Backstage root directory" yarn --cwd packages/backend add better-sqlite3 ``` diff --git a/docs/tutorials/enable-public-entry.md b/docs/tutorials/enable-public-entry.md index 6282f66418..25d7382f13 100644 --- a/docs/tutorials/enable-public-entry.md +++ b/docs/tutorials/enable-public-entry.md @@ -100,3 +100,47 @@ With that, Backstage's cli and backend will detect public entry point and serve 5. Finally, as soon as you log in, you will be redirected to the main app home page (inspect the page and see that the protected bundle was served from the app backend after the redirect). That's it! + +## New Frontend System + +If your app uses the new frontend system, you can still use the public entry point feature. The `index-public-experimental.tsx` file does end up looking a bit different in this case: + +```tsx title="in packages/app/src/index-public-experimental.tsx" +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { CookieAuthRedirect } from '@backstage/plugin-auth-react'; +import { createApp } from '@backstage/frontend-app-api'; +import { + coreExtensionData, + createExtension, + createExtensionOverrides, + createSignInPageExtension, +} from '@backstage/frontend-plugin-api'; + +const signInPage = createSignInPageExtension({ + name: 'guest', + loader: async () => props => , +}); + +const authRedirectExtension = createExtension({ + namespace: 'app', + name: 'layout', + attachTo: { id: 'app/root', input: 'children' }, + output: { + element: coreExtensionData.reactElement, + }, + factory: () => ({ + element: , + }), +}); + +const app = createApp({ + features: [ + createExtensionOverrides({ + extensions: [signInPage, authRedirectExtension], + }), + ], +}); + +ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); +``` diff --git a/docs/tutorials/switching-sqlite-postgres.md b/docs/tutorials/switching-sqlite-postgres.md index 2ef481dbf3..aec9ca2ee9 100644 --- a/docs/tutorials/switching-sqlite-postgres.md +++ b/docs/tutorials/switching-sqlite-postgres.md @@ -19,8 +19,7 @@ switch between database backends. First, add PostgreSQL to your `backend` package: -```bash -# From your Backstage root directory +```bash title="From your Backstage root directory" yarn --cwd packages/backend add pg ``` diff --git a/microsite/blog/2022-12-19-backstage-wrapped-2022.mdx b/microsite/blog/2022-12-19-backstage-wrapped-2022.mdx index e92c9f2265..c15b9fe208 100644 --- a/microsite/blog/2022-12-19-backstage-wrapped-2022.mdx +++ b/microsite/blog/2022-12-19-backstage-wrapped-2022.mdx @@ -20,7 +20,7 @@ Last year we celebrated 75 [public adopters](https://github.com/backstage/backst ![Accelerated growth of Public Backstage Adopters, from January 2021–December 2022.](assets/2022-12-19/public-backstage-adopters-2022.png) -Not only are our adopters growing at an exponential rate, but so too are our contributors. Our [Discord server](https://discord.gg/backstage-687207715902193673-687207715902193673) is a perfect example of the worlds of our adopters and contributors melding. On average, at any given hour of the day or night, there are more than 500 people online available to talk and provide support on any Backstage topic. Discord discussions run a full gamut of topics – from technical support questions to best practices for onboarding engineers to Backstage – basically, anything and everything an adopter may be interested to know and learn. At the very beginning of 2022, we “only” had 4k Discord members. Today, we have a growing membership of 8.2k. +Not only are our adopters growing at an exponential rate, but so too are our contributors. Our [Discord server](https://discord.gg/backstage-687207715902193673) is a perfect example of the worlds of our adopters and contributors melding. On average, at any given hour of the day or night, there are more than 500 people online available to talk and provide support on any Backstage topic. Discord discussions run a full gamut of topics – from technical support questions to best practices for onboarding engineers to Backstage – basically, anything and everything an adopter may be interested to know and learn. At the very beginning of 2022, we “only” had 4k Discord members. Today, we have a growing membership of 8.2k. Isn’t it what we can call a great and vibrant community of enthusiasts? diff --git a/microsite/blog/2024-06-27-backstage-engineer-journey.mdx b/microsite/blog/2024-06-27-backstage-engineer-journey.mdx new file mode 100644 index 0000000000..b9bfc20e4e --- /dev/null +++ b/microsite/blog/2024-06-27-backstage-engineer-journey.mdx @@ -0,0 +1,135 @@ +--- +# prettier-ignore +title: "1,186 days with Backstage: A journey through the eyes of a developer" +author: Camila Loiola, Spotify +authorURL: https://github.com/camilaibs +authorImageURL: https://avatars.githubusercontent.com/u/6290749?v=4 +--- + +**TL;DR** There is no shortage of content that discusses the benefits that Backstage brings to organizations (and if you want to read more on that, you can just click [here](https://engineering.atspotify.com/category/backstage/), [here](https://backstage.io/blog), or [here](https://backstage.spotify.com/discover/blog/)). But I want to share what it did for me, a developer here at Spotify. And not just what it did, but how I’ve used Backstage to help me achieve goals at each stage of my journey at Spotify — from my first day as a brand new employee to today, my 1,186th day as a contributor. + +![Backstage Engineer Journey](assets/2024-06-27/backstage-engineer-journey.svg) + +{/* truncate */} + +## A new joiner with a need to get up to speed + +My first six months after joining Spotify, I was eager and determined to get up to speed so that I’d be able to start contributing as soon as possible. I set goals for myself and developed concrete activities to reach those goals: + +### Goal 1: Learn the internal process to develop, test, secure, and publish applications. + +Understanding a company’s practices and technologies is absolutely essential to getting started, no matter what company you join. + +At Spotify, we use a Backstage plugin called [TechDocs](https://backstage.io/docs/features/techdocs/) to document [Golden Paths](https://engineering.atspotify.com/2020/08/how-we-use-golden-paths-to-solve-fragmentation-in-our-software-ecosystem/) — tutorials that teach us the development process for each role at the company — for web, backend, mobile, data science development, and so on. + +With Backstage, I had an understanding of how to put things into production, even before I had my first task. I used the web Golden Path to guide me on setting up my environment; creating and structuring projects; and building, publishing, and monitoring production applications. + +> **Reflection:**
_“Is it easy for newly hired engineers to settle in and find information critical to getting started?”_ + +With Backstage, new hires can quickly and easily understand the nuances of their particular positions and be ready to deliver their first task to production. + +### Goal 2: Find and read the documentation and source code of the products my team maintains. + +Having familiarized myself with the ways of working, it was a good time to understand my specific team’s business domain. This meant spending time reading through documentation and source code to find out the value we provide to our customers. In the past, I would spend time with colleagues for knowledge transfer as opposed to learning on my own and consult my colleagues if needed. + +With the [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/), I could easily browse the components my team owns, find the source repo and documentation. + +> **Reflection:**
_“Are you able to accomplish this without asking a teammate for help?”_ + +Use Backstage to get to know the products that you will be working on, and use your peers' time to ask contextualized clarifying questions instead. + +### Goal 3: Figure out where my team fits within the company and with other teams. + +It was also crucial for me to build a network from the beginning so that I could explore collaboration opportunities. + +At Spotify, we use a Backstage plugin called Band Manager which displays information about each team and allows employees to see not only their own teams but also those within the same product area and company domain. + +> **Reflection:**
_“Are you going to need a one-on-one with your manager to ask this question?”_ + +Backstage helped me find the teams close to mine, and I took the opportunity to develop my network and talk to members from those teams to understand our boundaries and gather ideas to contribute. + +It’s pretty likely that you can relate to one or more of these examples, so use Backstage to support your goals from the very beginning! + +Now let’s move on to the middle of the journey, between six months and two years into life at the company. + +## Two years in — from contributor to leader + +After about two years of contributing to my team’s products, I began to look for opportunities to actually lead initiatives and create a new project. One of my objectives was to ensure I was upholding Spotify’s standards and choosing Spotify-approved technologies. + +My goal has changed from being a contributor to being the driver, i.e., from doing what I was told to do to defining what needed to be done. I “started driving initiatives” and “supporting my peers” in order to position myself as a reference in my technical area. + +### Goal 1: Quickly spin up new projects using the most appropriate technology stack. + +At Spotify, we expect engineers to use technologies approved by our [Tech Radar](https://backstage.io/blog/2020/05/14/tech-radar-plugin/#what-is-tech-radar) and follow the best quality standards. + +To do so, we use a plugin called [Scaffolder](https://backstage.io/docs/reference/plugin-scaffolder) (aka Software Templates), which allows engineers to create templates for setting up new projects. Templates automate actions such as creating repositories, allocating resources, registering in the catalog, configuring deployments, and connecting to other internal systems. + +> **Reflection:**
_“Do you need to create a ticket to get these things done before you even start coding? And is there a lot of time spent looking for boilerplate code to start a new project and connecting it to other internal systems?"_ + +Backstage enabled me to quickly create and publish a new backend that can deploy code to production with just a few clicks, so I could focus on building solutions for the business. I could use Backstage to easily build the right thing. Instead of spending time scaffolding projects, I could spend my time writing code. + +### Goal 2: Create products that have a healthy operational state and are easy to evolve. + +Keeping track of vulnerabilities and letting my team know about technical debts were other parts of my routine. + +With the [Soundcheck plugin](https://backstage.spotify.com/marketplace/spotify/plugin/soundcheck/), developers can easily configure checks to verify component health, such as reliability and other development and operational standards. We also have an internal Vulnerabilities plugin that displays fragilities in our components, so we can continuously prevent security issues. + +> **Reflection:**
_“Do you know which components owned by your team follows best practices and standards and which ones are lagging behind?”_ + +Backstage helped me gather and better understand our technical debts, enabling me to effectively prioritize during our team’s planning, visualize which entities were compliant to our team’s best practices, and identify which ones needed improvement. + +### Goal 3: Respond effectively to incidents within our components or their dependencies. + +If you’ve been with a company for around two years, you might have had to manage incidents in one way or another — this was certainly the case for me. A typical problem was to determine if the issue is with the service that I own or a downstream dependency. + +Spotify has adopted [PagerDuty](https://backstage.spotify.com/marketplace/pagerduty/plugin/pagerduty/) to manage on-call rotation and to track incident resolutions. We integrated the PagerDuty plugin in Backstage, so we see who is on call and if there is an incident for a particular component. + +> **Reflection:**
_“When a downstream dependency is failing, do you know who to contact?”_ + +In the event that one of our components starts failing, I check whether any of the systems we rely on is experiencing an incident, and Backstage again makes it easy for me to see the dependencies between systems and the status of those dependencies. + +## Today and beyond! + +With three-plus years of experience at Spotify, I’m taking on more and more responsibility by embracing challenges outside my team’s scope, coordinating cross-team collaboration, attending and facilitating courses for personal development and growth, and mentoring my peers. + +Backstage has allowed me to drive my pursuit of growth in a multitude of ways. + +### Goal 1: Stay in the flow and optimize my time management practices. + +At this point, finding time to focus — with all that I have on my plate — while also making time to learn new concepts is both fundamental and challenging for me. + +Spotify encourages its developers to manage their time in order to stay in the flow and to turn to the tools that help them prioritize effectively and focus on their work. + +> **Reflection:**
_“How do you surface the most important notifications and avoid non-emergency distractions?”_ + +Backstage has been instrumental in helping me stay in the flow by surfacing the most critical notifications and call to actions related to cost spikes, security vulnerabilities in my homepage. + +### Goal 2: Platformize our solutions and reuse code to boost my productivity. + +Reinventing the wheel is a common problem in software development and Spotify is no different. + +Autonomous teams tend to prioritize speed over reuse and develop the same solutions in different places (1) as it unblocks the delivery for their team and (2) there is no easy way to discover if a particular functionality has been developed in another part of the company and if it can be reused for their requirement. + +> **Reflection:**
_“Do you find yourself wondering if someone else already did it before starting a task?”_ + +With the [Backstage Search plugin](https://backstage.io/docs/features/search/), I can discover solutions to reuse, find inspiration, and also share what I’ve built for other developers to discover and use. + +### Goal 3: Grow my career and take steps toward the next level. + +I began looking for opportunities to gain new experiences and responsibilities while expanding my knowledge. + +With Spotify’s [Skill Exchange plugin](https://backstage.spotify.com/marketplace/spotify/plugin/skill-exchange/) for Backstage, engineers temporarily embed with a different team, allowing them to partake in work outside their day-to-day. The plugin also helps engineers connect with mentees or mentors that align with their skills profiles. + +> **Reflection:**
_“Are you able to find opportunities that match your current skill set?”_ + +With Backstage, I've applied for an embed to experience working with another team and eventually make a permanent move to this new team, calling it my new home. + +I am passionate about teaching and eager to share my knowledge to help others. Through Skill Exchange, I found a mentee, a colleague transitioning into engineering, and I am helping them take their first steps. + +## What’s next? + +Backstage is highly customizable, and with its extensive plugin ecosystem, engineers can use it strategically to achieve their goals. + +No matter what point in your journey you are, you can find value with [Backstage](http://backstage.io). It was, after all, made by engineers for engineers. 💚 + +Special thanks to [Stanley Nicholl](https://github.com/stanley-nicholl), [Patrik Oldsberg](https://github.com/Rugvip), [Vincenzo Scamporlino](https://github.com/vinzscam), [Mihai Tabara](https://github.com/MihaiTabara), [Emma Indal](https://github.com/emmaindal), [Raghunandan Balachandran](https://github.com/soapraj) for all of their support. diff --git a/microsite/blog/assets/2024-06-27/backstage-engineer-journey.svg b/microsite/blog/assets/2024-06-27/backstage-engineer-journey.svg new file mode 100644 index 0000000000..1e302206a5 --- /dev/null +++ b/microsite/blog/assets/2024-06-27/backstage-engineer-journey.svg @@ -0,0 +1,362 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/microsite/data/plugins/dora-metrics.yaml b/microsite/data/plugins/dora-metrics.yaml deleted file mode 100644 index 3f59902f46..0000000000 --- a/microsite/data/plugins/dora-metrics.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: DORA Metrics -author: Okay -authorUrl: https://www.okayhq.com -category: Metrics -description: Embed dashboards (like DORA metrics) in your team or service pages from any dev tools (including Github, Gitlab, Jira, Argo, CircleCI, Buildkite, Pagerduty, Rollbar, Sentry, etc). -documentation: https://github.com/OkayHQ/backstage-plugin -iconUrl: /img/okay.png -npmPackageName: '@okayhq/backstage-plugin' -addedDate: '2022-06-16' diff --git a/microsite/data/plugins/harness-chaos.yaml b/microsite/data/plugins/harness-chaos.yaml index ade6f9e566..e089b3d86b 100644 --- a/microsite/data/plugins/harness-chaos.yaml +++ b/microsite/data/plugins/harness-chaos.yaml @@ -1,4 +1,4 @@ -title: Harness Service Reliability Management +title: Harness Chaos Engineering author: harness.io authorUrl: https://github.com/harness/backstage-plugins category: Chaos Engineering diff --git a/microsite/data/plugins/harness-iacm.yaml b/microsite/data/plugins/harness-iacm.yaml new file mode 100644 index 0000000000..4463bab371 --- /dev/null +++ b/microsite/data/plugins/harness-iacm.yaml @@ -0,0 +1,13 @@ +--- +title: Harness Infrastructure as Code Management +author: harness.io +authorUrl: https://github.com/harness/backstage-plugins +category: Infrastructure +description: This plugin provides an overview of all the resources provisioned through information on active workspaces in your account +documentation: https://github.com/harness/backstage-plugins/tree/main/plugins/harness-iacm#harness-iacm-plugin +iconUrl: https://static.harness.io/ng-static/images/favicon.png +npmPackageName: '@harnessio/backstage-plugin-harness-iacm' +tags: + - infrastrucure-as-code + - resource-management +addedDate: '2024-07-03' diff --git a/microsite/data/plugins/relations.yaml b/microsite/data/plugins/relations.yaml new file mode 100644 index 0000000000..61ce052453 --- /dev/null +++ b/microsite/data/plugins/relations.yaml @@ -0,0 +1,10 @@ +--- +title: Relations +author: dweber019 +authorUrl: https://github.com/dweber019 +category: Catalog +description: This plugin enables you to configure additional relations between entities, which aren't supported out of box. +documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/relations +iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/relations-backend/docs/pluginIcon.png +npmPackageName: '@dweber019/backstage-plugin-relations' +addedDate: '2024-05-15' diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index b1a6533ab9..67f6de4962 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -241,7 +241,7 @@ const config: Config = { position: 'left', }, { - to: 'docs/releases/v1.28.0', + to: 'docs/releases/v1.29.0', label: 'Releases', position: 'left', }, diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 075025afbb..d13ebc71c6 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -1,6 +1,7 @@ { "releases": { "Release Notes": [ + "releases/v1.29.0", "releases/v1.28.0", "releases/v1.27.0", "releases/v1.26.0", @@ -429,6 +430,7 @@ "backend-system/core-services/permissions", "backend-system/core-services/plugin-metadata", "backend-system/core-services/root-config", + "backend-system/core-services/root-health", "backend-system/core-services/root-http-router", "backend-system/core-services/root-lifecycle", "backend-system/core-services/root-logger", diff --git a/microsite/src/pages/community/index.tsx b/microsite/src/pages/community/index.tsx index c331638c78..a32f5edd13 100644 --- a/microsite/src/pages/community/index.tsx +++ b/microsite/src/pages/community/index.tsx @@ -96,6 +96,11 @@ const Community = () => { url: 'https://tanzu.vmware.com/developer-portal', logo: 'img/partner-logo-tanzubybroadcom.png', }, + { + name: 'StatusNeo', + url: 'https://statusneo.com/backstage', + logo: 'img/partner-logo-statusneo.png', + }, ]; //#endregion diff --git a/microsite/static/img/okay.png b/microsite/static/img/okay.png deleted file mode 100644 index 357219d9d2..0000000000 Binary files a/microsite/static/img/okay.png and /dev/null differ diff --git a/microsite/static/img/partner-logo-statusneo.png b/microsite/static/img/partner-logo-statusneo.png new file mode 100644 index 0000000000..61a676a220 Binary files /dev/null and b/microsite/static/img/partner-logo-statusneo.png differ diff --git a/mkdocs.yml b/mkdocs.yml index b9f6d41d46..f163005377 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -158,7 +158,7 @@ nav: - Azure EasyAuth: 'auth/microsoft/azure-easyauth.md' - Bitbucket: 'auth/bitbucket/provider.md' - Bitbucket Server: 'auth/bitbucketServer/provider.md' - - Cloudflare Access: 'auth/cloudflare/access.md' + - Cloudflare Access: 'auth/cloudflare/provider.md' - GitHub: 'auth/github/provider.md' - GitLab: 'auth/gitlab/provider.md' - Google: 'auth/google/provider.md' diff --git a/package.json b/package.json index 0946671f12..73d0ca5b19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.29.0-next.0", + "version": "1.30.0-next.0", "private": true, "repository": { "type": "git", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 40ee32565f..a045f9a4f9 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/app-defaults +## 1.5.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.24 + +## 1.5.8 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-app-api@1.14.0 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## 1.5.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## 1.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.23 + ## 1.5.7-next.0 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 5dc9e3d617..2389c2cddc 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.5.7-next.0", + "version": "1.5.9-next.0", "description": "Provides the default wiring of a Backstage App", "backstage": { "role": "web-library" diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md index e6788c8566..f05b81642f 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,29 @@ # app-next-example-plugin +## 0.0.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/core-components@0.14.10-next.0 + +## 0.0.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/frontend-plugin-api@0.6.7 + +## 0.0.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + ## 0.0.13-next.0 ### Patch Changes diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index 9d2e76c418..d397883063 100644 --- a/packages/app-next-example-plugin/package.json +++ b/packages/app-next-example-plugin/package.json @@ -1,6 +1,6 @@ { "name": "app-next-example-plugin", - "version": "0.0.13-next.0", + "version": "0.0.14-next.0", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 979284c459..794484d2ce 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,169 @@ # example-app-next +## 0.0.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.0 + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/plugin-search-react@1.7.14-next.0 + - @backstage/plugin-home@0.7.8-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/plugin-scaffolder-react@1.11.0-next.0 + - @backstage/frontend-app-api@0.7.4-next.0 + - @backstage/plugin-catalog-graph@0.4.8-next.0 + - @backstage/plugin-scaffolder@1.23.1-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/app-defaults@1.5.9-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.8-next.0 + - @backstage/plugin-app-visualizer@0.1.9-next.0 + - @backstage/plugin-catalog@1.21.2-next.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-catalog-import@0.12.2-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-kubernetes@0.11.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.0 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-org@0.6.28-next.0 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-search@1.4.15-next.0 + - @backstage/plugin-search-common@1.2.13 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs@1.10.8-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + - @backstage/plugin-user-settings@0.8.10-next.0 + +## 0.0.13 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.23.0 + - @backstage/plugin-techdocs@1.10.7 + - @backstage/plugin-techdocs-react@1.2.6 + - @backstage/core-components@0.14.9 + - @backstage/cli@0.26.11 + - @backstage/core-app-api@1.14.0 + - @backstage/frontend-app-api@0.7.3 + - @backstage/plugin-org@0.6.27 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-catalog@1.21.1 + - @backstage/plugin-notifications@0.2.3 + - @backstage/plugin-home@0.7.7 + - @backstage/plugin-scaffolder-react@1.10.0 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-search@1.4.14 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-search-common@1.2.13 + - @backstage/app-defaults@1.5.8 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/integration-react@1.1.29 + - @backstage/plugin-api-docs@0.11.7 + - @backstage/plugin-app-visualizer@0.1.8 + - @backstage/plugin-auth-react@0.1.4 + - @backstage/plugin-catalog-graph@0.4.7 + - @backstage/plugin-catalog-import@0.12.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6 + - @backstage/plugin-kubernetes@0.11.12 + - @backstage/plugin-kubernetes-cluster@0.0.13 + - @backstage/plugin-search-react@1.7.13 + - @backstage/plugin-signals@0.0.8 + - @backstage/plugin-user-settings@0.8.9 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## 0.0.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.7-next.2 + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-search@1.4.14-next.2 + - @backstage/app-defaults@1.5.8-next.2 + - @backstage/cli@0.26.11-next.1 + - @backstage/frontend-app-api@0.7.3-next.2 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-api-docs@0.11.7-next.2 + - @backstage/plugin-app-visualizer@0.1.8-next.1 + - @backstage/plugin-auth-react@0.1.4-next.1 + - @backstage/plugin-catalog@1.21.1-next.2 + - @backstage/plugin-catalog-graph@0.4.7-next.2 + - @backstage/plugin-catalog-import@0.12.1-next.2 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.1 + - @backstage/plugin-home@0.7.7-next.2 + - @backstage/plugin-kubernetes@0.11.12-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.13-next.2 + - @backstage/plugin-notifications@0.2.3-next.2 + - @backstage/plugin-org@0.6.27-next.2 + - @backstage/plugin-scaffolder@1.22.1-next.2 + - @backstage/plugin-scaffolder-react@1.10.0-next.2 + - @backstage/plugin-search-react@1.7.13-next.1 + - @backstage/plugin-signals@0.0.8-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.1 + - @backstage/plugin-techdocs-react@1.2.6-next.1 + - @backstage/plugin-user-settings@0.8.9-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## 0.0.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.6.27-next.1 + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/plugin-catalog@1.21.1-next.1 + - @backstage/plugin-techdocs@1.10.7-next.1 + - @backstage/plugin-notifications@0.2.3-next.1 + - @backstage/plugin-home@0.7.7-next.1 + - @backstage/plugin-scaffolder-react@1.10.0-next.1 + - @backstage/plugin-scaffolder@1.22.1-next.1 + - @backstage/cli@0.26.11-next.1 + - @backstage/app-defaults@1.5.8-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/frontend-app-api@0.7.3-next.1 + - @backstage/plugin-api-docs@0.11.7-next.1 + - @backstage/plugin-catalog-graph@0.4.7-next.1 + - @backstage/plugin-catalog-import@0.12.1-next.1 + - @backstage/plugin-search@1.4.13-next.1 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/plugin-signals@0.0.8-next.0 + - @backstage/plugin-user-settings@0.8.9-next.1 + - @backstage/plugin-kubernetes@0.11.12-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.13-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-app-visualizer@0.1.8-next.0 + - @backstage/plugin-auth-react@0.1.4-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.0 + - @backstage/plugin-permission-react@0.4.23 + - @backstage/plugin-search-common@1.2.12 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.0 + - @backstage/plugin-techdocs-react@1.2.6-next.0 + ## 0.0.13-next.0 ### Patch Changes diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index 271040ecec..f6d0174ca1 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -43,6 +43,8 @@ app: - entity-card:org/ownership - entity-card:org/user-profile + - entity-content:kubernetes/kubernetes + # scmAuthExtension: >- # createScmAuthExtension({ # id: 'apis.scmAuth.addons.ghe', diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 73141946ea..b4b9f69c51 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.13-next.0", + "version": "0.0.14-next.0", "private": true, "repository": { "type": "git", diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index c911a01258..b57c680474 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -36,18 +36,14 @@ 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, - SignInPageProps, -} from '@backstage/core-plugin-api'; +import { createApiFactory, configApiRef } 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'; +import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha'; +import { signInPageOverrides } from './overrides/SignInPage'; /* @@ -90,12 +86,6 @@ const homePageExtension = createExtension({ }, }); -const signInPage = createSignInPageExtension({ - name: 'guest', - loader: async () => (props: SignInPageProps) => - , -}); - const scmAuthExtension = createApiExtension({ factory: ScmAuth.createDefaultApiFactory(), }); @@ -121,13 +111,14 @@ const app = createApp({ userSettingsPlugin, homePlugin, appVisualizerPlugin, + kubernetesPlugin, + signInPageOverrides, ...collectedLegacyPlugins, createExtensionOverrides({ extensions: [ homePageExtension, scmAuthExtension, scmIntegrationApi, - signInPage, notFoundErrorPage, ], }), diff --git a/packages/app-next/src/index-public-experimental.tsx b/packages/app-next/src/index-public-experimental.tsx new file mode 100644 index 0000000000..56bc698ffd --- /dev/null +++ b/packages/app-next/src/index-public-experimental.tsx @@ -0,0 +1,49 @@ +/* + * 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 from 'react'; +import ReactDOM from 'react-dom/client'; +import { CookieAuthRedirect } from '@backstage/plugin-auth-react'; +import { createApp } from '@backstage/frontend-app-api'; +import { signInPageOverrides } from './overrides/SignInPage'; +import { + coreExtensionData, + createExtension, + createExtensionOverrides, +} from '@backstage/frontend-plugin-api'; + +const authRedirectExtension = createExtension({ + namespace: 'app', + name: 'layout', + attachTo: { id: 'app/root', input: 'children' }, + output: { + element: coreExtensionData.reactElement, + }, + factory: () => ({ + element: , + }), +}); + +const app = createApp({ + features: [ + signInPageOverrides, + createExtensionOverrides({ + extensions: [authRedirectExtension], + }), + ], +}); + +ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); diff --git a/packages/app-next/src/overrides/SignInPage.tsx b/packages/app-next/src/overrides/SignInPage.tsx new file mode 100644 index 0000000000..364a749293 --- /dev/null +++ b/packages/app-next/src/overrides/SignInPage.tsx @@ -0,0 +1,31 @@ +/* + * 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 React from 'react'; +import { SignInPage } from '@backstage/core-components'; +import { + createExtensionOverrides, + createSignInPageExtension, +} from '@backstage/frontend-plugin-api'; + +const signInPage = createSignInPageExtension({ + name: 'guest', + loader: async () => props => , +}); + +export const signInPageOverrides = createExtensionOverrides({ + extensions: [signInPage], +}); diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index ca64c31049..4ba5430905 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,161 @@ # example-app +## 0.2.100-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/plugin-search-react@1.7.14-next.0 + - @backstage/plugin-home@0.7.8-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/plugin-scaffolder-react@1.11.0-next.0 + - @backstage/frontend-app-api@0.7.4-next.0 + - @backstage/plugin-catalog-graph@0.4.8-next.0 + - @backstage/plugin-scaffolder@1.23.1-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/app-defaults@1.5.9-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.8-next.0 + - @backstage/plugin-catalog@1.21.2-next.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-catalog-import@0.12.2-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-devtools@0.1.17-next.0 + - @backstage/plugin-kubernetes@0.11.13-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.0 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-org@0.6.28-next.0 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-search@1.4.15-next.0 + - @backstage/plugin-search-common@1.2.13 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs@1.10.8-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + - @backstage/plugin-user-settings@0.8.10-next.0 + +## 0.2.99 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.23.0 + - @backstage/plugin-techdocs@1.10.7 + - @backstage/plugin-techdocs-react@1.2.6 + - @backstage/core-components@0.14.9 + - @backstage/cli@0.26.11 + - @backstage/core-app-api@1.14.0 + - @backstage/frontend-app-api@0.7.3 + - @backstage/plugin-org@0.6.27 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-catalog@1.21.1 + - @backstage/plugin-notifications@0.2.3 + - @backstage/plugin-home@0.7.7 + - @backstage/plugin-scaffolder-react@1.10.0 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-search@1.4.14 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-search-common@1.2.13 + - @backstage/app-defaults@1.5.8 + - @backstage/integration-react@1.1.29 + - @backstage/plugin-api-docs@0.11.7 + - @backstage/plugin-auth-react@0.1.4 + - @backstage/plugin-catalog-graph@0.4.7 + - @backstage/plugin-catalog-import@0.12.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6 + - @backstage/plugin-devtools@0.1.16 + - @backstage/plugin-kubernetes@0.11.12 + - @backstage/plugin-kubernetes-cluster@0.0.13 + - @backstage/plugin-search-react@1.7.13 + - @backstage/plugin-signals@0.0.8 + - @backstage/plugin-user-settings@0.8.9 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## 0.2.99-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.7-next.2 + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-search@1.4.14-next.2 + - @backstage/app-defaults@1.5.8-next.2 + - @backstage/cli@0.26.11-next.1 + - @backstage/frontend-app-api@0.7.3-next.2 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-api-docs@0.11.7-next.2 + - @backstage/plugin-auth-react@0.1.4-next.1 + - @backstage/plugin-catalog@1.21.1-next.2 + - @backstage/plugin-catalog-graph@0.4.7-next.2 + - @backstage/plugin-catalog-import@0.12.1-next.2 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.1 + - @backstage/plugin-devtools@0.1.16-next.1 + - @backstage/plugin-home@0.7.7-next.2 + - @backstage/plugin-kubernetes@0.11.12-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.13-next.2 + - @backstage/plugin-notifications@0.2.3-next.2 + - @backstage/plugin-org@0.6.27-next.2 + - @backstage/plugin-scaffolder@1.22.1-next.2 + - @backstage/plugin-scaffolder-react@1.10.0-next.2 + - @backstage/plugin-search-react@1.7.13-next.1 + - @backstage/plugin-signals@0.0.8-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.1 + - @backstage/plugin-techdocs-react@1.2.6-next.1 + - @backstage/plugin-user-settings@0.8.9-next.2 + +## 0.2.99-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.6.27-next.1 + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/plugin-catalog@1.21.1-next.1 + - @backstage/plugin-techdocs@1.10.7-next.1 + - @backstage/plugin-notifications@0.2.3-next.1 + - @backstage/plugin-home@0.7.7-next.1 + - @backstage/plugin-scaffolder-react@1.10.0-next.1 + - @backstage/plugin-scaffolder@1.22.1-next.1 + - @backstage/cli@0.26.11-next.1 + - @backstage/app-defaults@1.5.8-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/frontend-app-api@0.7.3-next.1 + - @backstage/plugin-api-docs@0.11.7-next.1 + - @backstage/plugin-catalog-graph@0.4.7-next.1 + - @backstage/plugin-catalog-import@0.12.1-next.1 + - @backstage/plugin-search@1.4.13-next.1 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/plugin-signals@0.0.8-next.0 + - @backstage/plugin-user-settings@0.8.9-next.1 + - @backstage/plugin-kubernetes@0.11.12-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.13-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.4-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-unprocessed-entities@0.2.6-next.0 + - @backstage/plugin-devtools@0.1.16-next.0 + - @backstage/plugin-permission-react@0.4.23 + - @backstage/plugin-search-common@1.2.12 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.12-next.0 + - @backstage/plugin-techdocs-react@1.2.6-next.0 + ## 0.2.99-next.0 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 067b2d84eb..0468a2f7a8 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.99-next.0", + "version": "0.2.100-next.0", "backstage": { "role": "frontend" }, diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index c9fe21c89e..94ab9afb6f 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,67 @@ # @backstage/backend-app-api +## 0.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## 0.8.0 + +### Minor Changes + +- 1cb84d7: **BREAKING**: Removed the depreacted `getPath` option from `httpRouterServiceFactory`, as well as the `HttpRouterFactoryOptions` type. +- f691c9b: **BREAKING**: Removed the ability to pass callback-form service factories through the `defaultServiceFactories` option of `createSpecializedBackend`. This is an immediate breaking change as usage of this function is expected to be very rare. + +### Patch Changes + +- 2f99178: The `ServiceFactoryTest.get` method was deprecated and the `ServiceFactoryTest.getSubject` should be used instead. The `getSubject` method has the same behavior, but has a better method name to indicate that the service instance returned is the subject currently being tested. +- b05e1e1: Service factories exported by this package have been updated to use the new service factory format that doesn't use a callback. +- 617a7d2: Internal refactor that avoids the use of service factory options. +- b60db08: Fixing exporting of classes properly from new packages +- 18b96b1: The ability to install backend features in callback form (`() => BackendFeature`) has been deprecated. This typically means that you need to update the installed features to use the latest version of `@backstage/backend-plugin-api`. If the feature is from a third-party package, please reach out to the package maintainer to update it. +- a63c4b6: Fixing issue with `MiddlewareFactory` deprecation wrapping +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/cli-node@0.2.7 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config-loader@1.8.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.7.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.6 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-permission-node@0.7.33-next.1 + ## 0.7.9-next.0 ### Patch Changes diff --git a/packages/backend-app-api/api-report-alpha.md b/packages/backend-app-api/api-report-alpha.md index 488feedf2c..e4769f4e72 100644 --- a/packages/backend-app-api/api-report-alpha.md +++ b/packages/backend-app-api/api-report-alpha.md @@ -4,12 +4,13 @@ ```ts import { FeatureDiscoveryService } from '@backstage/backend-plugin-api/alpha'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @alpha (undocumented) -export const featureDiscoveryServiceFactory: () => ServiceFactory< +export const featureDiscoveryServiceFactory: ServiceFactoryCompat< FeatureDiscoveryService, - 'root' + 'root', + undefined >; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 7518f8e2cc..7f9b54dd8f 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -39,14 +39,18 @@ import { RootLoggerService } from '@backstage/backend-plugin-api'; import { SchedulerService } from '@backstage/backend-plugin-api'; import type { Server } from 'node:http'; import { ServiceFactory } from '@backstage/backend-plugin-api'; -import { ServiceFactoryOrFunction } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; import { TokenManagerService } from '@backstage/backend-plugin-api'; import { transport } from 'winston'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { UserInfoService } from '@backstage/backend-plugin-api'; // @public @deprecated (undocumented) -export const authServiceFactory: () => ServiceFactory; +export const authServiceFactory: ServiceFactoryCompat< + AuthService, + 'plugin', + undefined +>; // @public (undocumented) export interface Backend { @@ -54,9 +58,16 @@ export interface Backend { add( feature: | BackendFeature + | Promise<{ + default: BackendFeature; + }>, + ): void; + // @deprecated (undocumented) + add( + feature: | (() => BackendFeature) | Promise<{ - default: BackendFeature | (() => BackendFeature); + default: () => BackendFeature; }>, ): void; // (undocumented) @@ -66,7 +77,11 @@ export interface Backend { } // @public @deprecated (undocumented) -export const cacheServiceFactory: () => ServiceFactory; +export const cacheServiceFactory: ServiceFactoryCompat< + CacheService, + 'plugin', + undefined +>; // Warning: (ae-forgotten-export) The symbol "createConfigSecretEnumerator_2" needs to be exported by the entry point index.d.ts // @@ -91,13 +106,14 @@ export function createSpecializedBackend( // @public (undocumented) export interface CreateSpecializedBackendOptions { // (undocumented) - defaultServiceFactories: ServiceFactoryOrFunction[]; + defaultServiceFactories: ServiceFactory[]; } // @public @deprecated (undocumented) -export const databaseServiceFactory: () => ServiceFactory< +export const databaseServiceFactory: ServiceFactoryCompat< DatabaseService, - 'plugin' + 'plugin', + undefined >; // @public @deprecated @@ -116,9 +132,10 @@ export class DefaultRootHttpRouter implements RootHttpRouterService { export type DefaultRootHttpRouterOptions = DefaultRootHttpRouterOptions_2; // @public @deprecated (undocumented) -export const discoveryServiceFactory: () => ServiceFactory< +export const discoveryServiceFactory: ServiceFactoryCompat< DiscoveryService, - 'plugin' + 'plugin', + undefined >; // Warning: (ae-forgotten-export) The symbol "ExtendedHttpServer_2" needs to be exported by the entry point index.d.ts @@ -141,20 +158,18 @@ export class HostDiscovery implements DiscoveryService { } // @public @deprecated (undocumented) -export const httpAuthServiceFactory: () => ServiceFactory< +export const httpAuthServiceFactory: ServiceFactoryCompat< HttpAuthService, - 'plugin' + 'plugin', + undefined >; -// Warning: (ae-forgotten-export) The symbol "HttpRouterFactoryOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type HttpRouterFactoryOptions = HttpRouterFactoryOptions_2; - // @public @deprecated -export const httpRouterServiceFactory: ( - options?: HttpRouterFactoryOptions_2 | undefined, -) => ServiceFactory; +export const httpRouterServiceFactory: ServiceFactoryCompat< + HttpRouterService, + 'plugin', + undefined +>; // Warning: (ae-forgotten-export) The symbol "HttpServerCertificateOptions_2" needs to be exported by the entry point index.d.ts // @@ -173,9 +188,11 @@ export type IdentityFactoryOptions = { }; // @public @deprecated (undocumented) -export const identityServiceFactory: ( - options?: IdentityFactoryOptions | undefined, -) => ServiceFactory; +export const identityServiceFactory: ServiceFactoryCompat< + IdentityService, + 'plugin', + IdentityFactoryOptions +>; // Warning: (ae-forgotten-export) The symbol "LifecycleMiddlewareOptions_2" needs to be exported by the entry point index.d.ts // @@ -183,9 +200,10 @@ export const identityServiceFactory: ( export type LifecycleMiddlewareOptions = LifecycleMiddlewareOptions_2; // @public @deprecated -export const lifecycleServiceFactory: () => ServiceFactory< +export const lifecycleServiceFactory: ServiceFactoryCompat< LifecycleService, - 'plugin' + 'plugin', + undefined >; // @public @deprecated @@ -199,9 +217,10 @@ export function loadBackendConfig(options: { }>; // @public @deprecated -export const loggerServiceFactory: () => ServiceFactory< +export const loggerServiceFactory: ServiceFactoryCompat< LoggerService, - 'plugin' + 'plugin', + undefined >; // @public @deprecated (undocumented) @@ -226,9 +245,10 @@ export type MiddlewareFactoryErrorOptions = MiddlewareFactoryErrorOptions_2; export type MiddlewareFactoryOptions = MiddlewareFactoryOptions_2; // @public @deprecated (undocumented) -export const permissionsServiceFactory: () => ServiceFactory< +export const permissionsServiceFactory: ServiceFactoryCompat< PermissionsService, - 'plugin' + 'plugin', + undefined >; // Warning: (ae-forgotten-export) The symbol "readCorsOptions_2" needs to be exported by the entry point index.d.ts @@ -255,9 +275,11 @@ export interface RootConfigFactoryOptions { } // @public @deprecated (undocumented) -export const rootConfigServiceFactory: ( - options?: RootConfigFactoryOptions | undefined, -) => ServiceFactory; +export const rootConfigServiceFactory: ServiceFactoryCompat< + RootConfigService, + 'root', + RootConfigFactoryOptions +>; // Warning: (ae-forgotten-export) The symbol "RootHttpRouterConfigureContext_2" needs to be exported by the entry point index.d.ts // @@ -270,44 +292,51 @@ export type RootHttpRouterConfigureContext = RootHttpRouterConfigureContext_2; export type RootHttpRouterFactoryOptions = RootHttpRouterFactoryOptions_2; // @public @deprecated (undocumented) -export const rootHttpRouterServiceFactory: ( +export const rootHttpRouterServiceFactory: (( options?: RootHttpRouterFactoryOptions_2 | undefined, -) => ServiceFactory; +) => ServiceFactory) & + ServiceFactory; // @public @deprecated -export const rootLifecycleServiceFactory: () => ServiceFactory< +export const rootLifecycleServiceFactory: ServiceFactoryCompat< RootLifecycleService, - 'root' + 'root', + undefined >; // @public @deprecated -export const rootLoggerServiceFactory: () => ServiceFactory< +export const rootLoggerServiceFactory: ServiceFactoryCompat< RootLoggerService, - 'root' + 'root', + undefined >; // @public @deprecated (undocumented) -export const schedulerServiceFactory: () => ServiceFactory< +export const schedulerServiceFactory: ServiceFactoryCompat< SchedulerService, - 'plugin' + 'plugin', + undefined >; // @public @deprecated (undocumented) -export const tokenManagerServiceFactory: () => ServiceFactory< +export const tokenManagerServiceFactory: ServiceFactoryCompat< TokenManagerService, - 'plugin' + 'plugin', + undefined >; // @public @deprecated (undocumented) -export const urlReaderServiceFactory: () => ServiceFactory< +export const urlReaderServiceFactory: ServiceFactoryCompat< UrlReaderService, - 'plugin' + 'plugin', + undefined >; // @public @deprecated (undocumented) -export const userInfoServiceFactory: () => ServiceFactory< +export const userInfoServiceFactory: ServiceFactoryCompat< UserInfoService, - 'plugin' + 'plugin', + undefined >; // @public @deprecated diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 51bbca14bc..9372b837e0 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-app-api", - "version": "0.7.9-next.0", + "version": "0.8.1-next.0", "description": "Core API used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts b/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts index 9d2b74fb43..0088e53f98 100644 --- a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts +++ b/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts @@ -15,16 +15,7 @@ */ // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - httpRouterServiceFactory as _httpRouterServiceFactory, - type HttpRouterFactoryOptions as _HttpRouterFactoryOptions, -} from '../../../../../backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead. - */ -export type HttpRouterFactoryOptions = _HttpRouterFactoryOptions; +import { httpRouterServiceFactory as _httpRouterServiceFactory } from '../../../../../backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory'; /** * HTTP route registration for plugins. diff --git a/packages/backend-app-api/src/services/implementations/httpRouter/index.ts b/packages/backend-app-api/src/services/implementations/httpRouter/index.ts index 928a232131..bc12480ae8 100644 --- a/packages/backend-app-api/src/services/implementations/httpRouter/index.ts +++ b/packages/backend-app-api/src/services/implementations/httpRouter/index.ts @@ -15,6 +15,5 @@ */ export { httpRouterServiceFactory } from './httpRouterServiceFactory'; -export type { HttpRouterFactoryOptions } from './httpRouterServiceFactory'; export { createLifecycleMiddleware } from './createLifecycleMiddleware'; export type { LifecycleMiddlewareOptions } from './createLifecycleMiddleware'; diff --git a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts b/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts index 7b889b3a0e..b16509cf96 100644 --- a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts +++ b/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts @@ -22,7 +22,7 @@ describe('schedulerFactory', () => { it('creates sidecar database features', async () => { const tester = ServiceFactoryTester.from(schedulerServiceFactory()); - const scheduler = await tester.get(); + const scheduler = await tester.getSubject(); await scheduler.scheduleTask({ id: 'task1', timeout: { seconds: 1 }, diff --git a/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.test.ts b/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.test.ts index f603c6295e..dc52130ec6 100644 --- a/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.test.ts +++ b/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.test.ts @@ -21,7 +21,7 @@ describe('tokenManagerFactory', () => { it('should create a disabled manager without configuration', async () => { const tokenManager = await ServiceFactoryTester.from( tokenManagerServiceFactory, - ).get(); + ).getSubject(); await expect(tokenManager.authenticate('abc')).rejects.toThrow( 'no legacy keys are configured', diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.ts index 6e20dd98e8..56d8d611bb 100644 --- a/packages/backend-app-api/src/wiring/ServiceRegistry.ts +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.ts @@ -48,13 +48,13 @@ function toInternalServiceFactory( return f; } -const pluginMetadataServiceFactory = createServiceFactory( - (options?: { pluginId: string }) => ({ +function createPluginMetadataServiceFactory(pluginId: string) { + return createServiceFactory({ service: coreServices.pluginMetadata, deps: {}, - factory: async () => ({ getId: () => options?.pluginId! }), - }), -); + factory: async () => ({ getId: () => pluginId }), + }); +} export class ServiceRegistry { static create(factories: Array): ServiceRegistry { @@ -97,7 +97,7 @@ export class ServiceRegistry { // Special case handling of the plugin metadata service, generating a custom factory for it each time if (ref.id === coreServices.pluginMetadata.id) { return Promise.resolve( - toInternalServiceFactory(pluginMetadataServiceFactory({ pluginId })), + toInternalServiceFactory(createPluginMetadataServiceFactory(pluginId)), ); } diff --git a/packages/backend-app-api/src/wiring/createSpecializedBackend.ts b/packages/backend-app-api/src/wiring/createSpecializedBackend.ts index f5c4d8b152..fd449be541 100644 --- a/packages/backend-app-api/src/wiring/createSpecializedBackend.ts +++ b/packages/backend-app-api/src/wiring/createSpecializedBackend.ts @@ -24,13 +24,9 @@ import { Backend, CreateSpecializedBackendOptions } from './types'; export function createSpecializedBackend( options: CreateSpecializedBackendOptions, ): Backend { - const services = options.defaultServiceFactories.map(sf => - typeof sf === 'function' ? sf() : sf, - ); - const exists = new Set(); const duplicates = new Set(); - for (const { service } of services) { + for (const { service } of options.defaultServiceFactories) { if (exists.has(service.id)) { duplicates.add(service.id); } else { @@ -47,5 +43,5 @@ export function createSpecializedBackend( ); } - return new BackstageBackend(services); + return new BackstageBackend(options.defaultServiceFactories); } diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts index 027b2bf254..075eb58b3f 100644 --- a/packages/backend-app-api/src/wiring/types.ts +++ b/packages/backend-app-api/src/wiring/types.ts @@ -18,18 +18,25 @@ import { BackendFeature, ExtensionPoint, ServiceRef, - ServiceFactoryOrFunction, + ServiceFactory, } from '@backstage/backend-plugin-api'; /** * @public */ export interface Backend { + add(feature: BackendFeature | Promise<{ default: BackendFeature }>): void; + /** + * @deprecated The ability to add features defined as a callback is being + * removed. Please update the installed feature to no longer be defined as a + * callback, typically this means updating it to use the latest version of + * `@backstage/backend-plugin-api`. If the feature is from a third-party + * package, please reach out to the package maintainer to update it. + */ add( feature: - | BackendFeature | (() => BackendFeature) - | Promise<{ default: BackendFeature | (() => BackendFeature) }>, + | Promise<{ default: () => BackendFeature }>, ): void; start(): Promise; stop(): Promise; @@ -39,7 +46,7 @@ export interface Backend { * @public */ export interface CreateSpecializedBackendOptions { - defaultServiceFactories: ServiceFactoryOrFunction[]; + defaultServiceFactories: ServiceFactory[]; } /** diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index d635b77c72..01499c5d92 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/backend-common +## 0.23.4-next.0 + +### Patch Changes + +- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. +- 7e13b7a: The remaining exports in the package have now been deprecated: + + - `cacheToPluginCacheManager` + - `createLegacyAuthAdapters` + - `LegacyCreateRouter` + - `legacyPlugin` + - `loggerToWinstonLogger` + - `makeLegacyPlugin` + + Users of these export should fully [migrate to the new backend system](https://backstage.io/docs/backend-system/building-backends/migrating). + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 0.23.3 + +### Patch Changes + +- 8c09c97: Deprecate legacy status check factory, handler and types. +- d228862: Update default backend plugin created by the cli to use non-deprecated error handling middleware +- c964a3d: Add dependencies that are needed by cross-imports from backend-defaults +- b60db08: Fixing exporting of classes properly from new packages +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config-loader@1.8.1 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## 0.23.3-next.1 + +### Patch Changes + +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 +- Updated dependencies + - @backstage/backend-dev-utils@0.1.4 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + ## 0.23.2-next.0 ### Patch Changes diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 52b661bcaf..23ed13b62c 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -129,7 +129,7 @@ export class CacheManager extends CacheManager_2 {} // @public @deprecated (undocumented) export type CacheManagerOptions = CacheManagerOptions_2; -// @public +// @public @deprecated export function cacheToPluginCacheManager(cache: CacheService): { getClient(options?: CacheServiceOptions): CacheService; }; @@ -142,7 +142,7 @@ export interface ContainerRunner { runContainer(opts: RunContainerOptions): Promise; } -// @public +// @public @deprecated export function createLegacyAuthAdapters< TOptions extends { auth?: AuthService; @@ -401,10 +401,10 @@ export type KubernetesContainerRunnerOptions = { timeoutMs?: number; }; -// @public (undocumented) +// @public @deprecated (undocumented) export type LegacyCreateRouter = (deps: TEnv) => Promise; -// @public +// @public @deprecated export const legacyPlugin: ( name: string, createRouterImport: Promise<{ @@ -447,13 +447,13 @@ export function loadBackendConfig(options: { watch?: boolean; }): Promise; -// @public (undocumented) +// @public @deprecated (undocumented) export function loggerToWinstonLogger( logger: LoggerService, opts?: TransportStreamOptions, ): Logger; -// @public +// @public @deprecated export function makeLegacyPlugin< TEnv extends Record, TEnvTransforms extends { diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 7944477c88..4ace5e950a 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-common", - "version": "0.23.2-next.0", + "version": "0.23.4-next.0", "description": "Common functionality library for Backstage backends", "backstage": { "role": "node-library" @@ -137,7 +137,7 @@ "@types/webpack-env": "^1.15.2", "@types/yauzl": "^2.10.0", "aws-sdk-client-mock": "^4.0.0", - "better-sqlite3": "^9.0.0", + "better-sqlite3": "^11.0.0", "http-errors": "^2.0.0", "msw": "^1.0.0", "mysql2": "^3.0.0", diff --git a/packages/backend-common/src/compat/auth/createLegacyAuthAdapters.ts b/packages/backend-common/src/compat/auth/createLegacyAuthAdapters.ts index 0f591eac6c..15cd881649 100644 --- a/packages/backend-common/src/compat/auth/createLegacyAuthAdapters.ts +++ b/packages/backend-common/src/compat/auth/createLegacyAuthAdapters.ts @@ -292,6 +292,7 @@ export class UserInfoCompat implements UserInfoService { /** * An adapter that ensures presence of the auth and/or httpAuth services. * @public + * @deprecated Migrate to use the new backend system and auth services instead. */ export function createLegacyAuthAdapters< TOptions extends { diff --git a/packages/backend-common/src/compat/cache/cacheToPluginCacheManager.ts b/packages/backend-common/src/compat/cache/cacheToPluginCacheManager.ts index 2654934804..2ae55b016f 100644 --- a/packages/backend-common/src/compat/cache/cacheToPluginCacheManager.ts +++ b/packages/backend-common/src/compat/cache/cacheToPluginCacheManager.ts @@ -24,6 +24,7 @@ import { * old-backend plugin cache manager. * * @public + * @deprecated Migrate to use the new CacheService instead. */ export function cacheToPluginCacheManager(cache: CacheService): { getClient(options?: CacheServiceOptions): CacheService; diff --git a/packages/backend-common/src/compat/legacy/legacy.ts b/packages/backend-common/src/compat/legacy/legacy.ts index 57220a8430..c779daacd2 100644 --- a/packages/backend-common/src/compat/legacy/legacy.ts +++ b/packages/backend-common/src/compat/legacy/legacy.ts @@ -27,6 +27,7 @@ import { TokenManager } from '../../deprecated'; /** * @public + * @deprecated Fully use the new backend system instead. */ export type LegacyCreateRouter = (deps: TEnv) => Promise; @@ -69,6 +70,7 @@ function wrapTokenManager(tokenManager: TokenManager, auth: AuthService) { * Creates a new custom plugin compatibility wrapper. * * @public + * @deprecated Fully use the new backend system instead. * @remarks * * Usually you can use {@link legacyPlugin} directly instead, but you might @@ -128,6 +130,7 @@ export function makeLegacyPlugin< * register it with the http router based on the plugin id. * * @public + * @deprecated Fully use the new backend system instead. * @remarks * * This is intended to be used by plugin authors to ease the transition to the diff --git a/packages/backend-common/src/compat/logging/loggerToWinstonLogger.ts b/packages/backend-common/src/compat/logging/loggerToWinstonLogger.ts index 4ba88358d2..83871f48e3 100644 --- a/packages/backend-common/src/compat/logging/loggerToWinstonLogger.ts +++ b/packages/backend-common/src/compat/logging/loggerToWinstonLogger.ts @@ -53,7 +53,10 @@ class BackstageLoggerTransport extends Transport { } } -/** @public */ +/** + * @public + * @deprecated Migrate to use the new LoggerService instead. + */ export function loggerToWinstonLogger( logger: LoggerService, opts?: TransportStreamOptions, diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index 7e9788756f..a8cc588f1e 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,81 @@ # @backstage/backend-defaults +## 0.4.2-next.0 + +### Patch Changes + +- 4e79d19: The `createHealthRouter` utility that allows you to create a health check router is now exported via `@backstage/backend-defaults/rootHttpRouter`. +- 78c1329: Updated `GitlabUrlReader.readUrl` and `GitlabUrlReader.readTree` to accept a user-provided token, supporting both bearer and private tokens. +- 8e967da: Fixed the routing of the new health check service, the health endpoints should now properly be available at `/.backstage/health/v1/readiness` and `/.backstage/health/v1/liveness`. +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-app-api@0.8.1-next.0 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## 0.4.0 + +### Minor Changes + +- 1cb84d7: **BREAKING**: Removed the depreacted `getPath` option from `httpRouterServiceFactory`, as well as the `HttpRouterFactoryOptions` type. + +### Patch Changes + +- 53ced70: Added a new Root Health Service which adds new endpoints for health checks. +- 2f99178: The `ServiceFactoryTest.get` method was deprecated and the `ServiceFactoryTest.getSubject` should be used instead. The `getSubject` method has the same behavior, but has a better method name to indicate that the service instance returned is the subject currently being tested. +- 083eaf9: Fix bug where ISO durations could no longer be used for schedules +- b05e1e1: Service factories exported by this package have been updated to use the new service factory format that doesn't use a callback. +- 419f387: Refactor of `rootHttpRouterServiceFactory` to allow it to be constructed with options, but without declaring options via `createServiceFactory`. +- cb14a05: Repack the package to fix issues with typescript with named exports +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 +- e28af58: Refactor of `rootConfigServiceFactory` to allow it to be constructed with options, but without declaring options via `createServiceFactory`. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-app-api@0.8.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config-loader@1.8.1 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## 0.3.4-next.1 + +### Patch Changes + +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-app-api@0.7.10-next.1 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-permission-node@0.7.33-next.1 + ## 0.3.3-next.0 ### Patch Changes diff --git a/packages/backend-defaults/api-report-auth.md b/packages/backend-defaults/api-report-auth.md index d64ce5bc2a..7df0b394e3 100644 --- a/packages/backend-defaults/api-report-auth.md +++ b/packages/backend-defaults/api-report-auth.md @@ -4,10 +4,14 @@ ```ts import { AuthService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @public -export const authServiceFactory: () => ServiceFactory; +export const authServiceFactory: ServiceFactoryCompat< + AuthService, + 'plugin', + undefined +>; // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/api-report-cache.md b/packages/backend-defaults/api-report-cache.md index 0bfd7eb463..0b2d8706df 100644 --- a/packages/backend-defaults/api-report-cache.md +++ b/packages/backend-defaults/api-report-cache.md @@ -7,7 +7,7 @@ import { CacheService } from '@backstage/backend-plugin-api'; import { CacheServiceOptions } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @public export class CacheManager { @@ -25,7 +25,11 @@ export type CacheManagerOptions = { }; // @public -export const cacheServiceFactory: () => ServiceFactory; +export const cacheServiceFactory: ServiceFactoryCompat< + CacheService, + 'plugin', + undefined +>; // @public (undocumented) export interface PluginCacheManager { diff --git a/packages/backend-defaults/api-report-database.md b/packages/backend-defaults/api-report-database.md index 116830343a..0d54b43777 100644 --- a/packages/backend-defaults/api-report-database.md +++ b/packages/backend-defaults/api-report-database.md @@ -8,7 +8,7 @@ import { DatabaseService } from '@backstage/backend-plugin-api'; import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; import { PluginMetadataService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @public export class DatabaseManager implements LegacyRootDatabaseService { @@ -32,9 +32,10 @@ export type DatabaseManagerOptions = { }; // @public -export const databaseServiceFactory: () => ServiceFactory< +export const databaseServiceFactory: ServiceFactoryCompat< DatabaseService, - 'plugin' + 'plugin', + undefined >; // @public @deprecated diff --git a/packages/backend-defaults/api-report-discovery.md b/packages/backend-defaults/api-report-discovery.md index b434dab6fc..dbb469f758 100644 --- a/packages/backend-defaults/api-report-discovery.md +++ b/packages/backend-defaults/api-report-discovery.md @@ -5,12 +5,13 @@ ```ts import { Config } from '@backstage/config'; import { DiscoveryService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @public -export const discoveryServiceFactory: () => ServiceFactory< +export const discoveryServiceFactory: ServiceFactoryCompat< DiscoveryService, - 'plugin' + 'plugin', + undefined >; // @public diff --git a/packages/backend-defaults/api-report-httpAuth.md b/packages/backend-defaults/api-report-httpAuth.md index 6ffe3d6539..613e6a4452 100644 --- a/packages/backend-defaults/api-report-httpAuth.md +++ b/packages/backend-defaults/api-report-httpAuth.md @@ -4,12 +4,13 @@ ```ts import { HttpAuthService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @public -export const httpAuthServiceFactory: () => ServiceFactory< +export const httpAuthServiceFactory: ServiceFactoryCompat< HttpAuthService, - 'plugin' + 'plugin', + undefined >; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-defaults/api-report-httpRouter.md b/packages/backend-defaults/api-report-httpRouter.md index a7ca2c8dd3..a2bf1cd39b 100644 --- a/packages/backend-defaults/api-report-httpRouter.md +++ b/packages/backend-defaults/api-report-httpRouter.md @@ -7,22 +7,19 @@ import { HttpRouterService } from '@backstage/backend-plugin-api'; import { HumanDuration } from '@backstage/types'; import { LifecycleService } from '@backstage/backend-plugin-api'; import { RequestHandler } from 'express'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @public export function createLifecycleMiddleware( options: LifecycleMiddlewareOptions, ): RequestHandler; -// @public (undocumented) -export interface HttpRouterFactoryOptions { - getPath?(pluginId: string): string; -} - // @public -export const httpRouterServiceFactory: ( - options?: HttpRouterFactoryOptions | undefined, -) => ServiceFactory; +export const httpRouterServiceFactory: ServiceFactoryCompat< + HttpRouterService, + 'plugin', + undefined +>; // @public export interface LifecycleMiddlewareOptions { diff --git a/packages/backend-defaults/api-report-lifecycle.md b/packages/backend-defaults/api-report-lifecycle.md index 4233584664..36adbbf6e7 100644 --- a/packages/backend-defaults/api-report-lifecycle.md +++ b/packages/backend-defaults/api-report-lifecycle.md @@ -4,12 +4,13 @@ ```ts import { LifecycleService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @public -export const lifecycleServiceFactory: () => ServiceFactory< +export const lifecycleServiceFactory: ServiceFactoryCompat< LifecycleService, - 'plugin' + 'plugin', + undefined >; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-defaults/api-report-logger.md b/packages/backend-defaults/api-report-logger.md index fa9018a40c..c66f886bf4 100644 --- a/packages/backend-defaults/api-report-logger.md +++ b/packages/backend-defaults/api-report-logger.md @@ -4,12 +4,13 @@ ```ts import { LoggerService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @public -export const loggerServiceFactory: () => ServiceFactory< +export const loggerServiceFactory: ServiceFactoryCompat< LoggerService, - 'plugin' + 'plugin', + undefined >; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-defaults/api-report-permissions.md b/packages/backend-defaults/api-report-permissions.md index c436443ff1..86b9605606 100644 --- a/packages/backend-defaults/api-report-permissions.md +++ b/packages/backend-defaults/api-report-permissions.md @@ -4,12 +4,13 @@ ```ts import { PermissionsService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @public -export const permissionsServiceFactory: () => ServiceFactory< +export const permissionsServiceFactory: ServiceFactoryCompat< PermissionsService, - 'plugin' + 'plugin', + undefined >; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-defaults/api-report-rootConfig.md b/packages/backend-defaults/api-report-rootConfig.md index 317c451e1b..61f5c9dc9d 100644 --- a/packages/backend-defaults/api-report-rootConfig.md +++ b/packages/backend-defaults/api-report-rootConfig.md @@ -26,9 +26,10 @@ export interface RootConfigFactoryOptions { } // @public (undocumented) -export const rootConfigServiceFactory: ( - options?: RootConfigFactoryOptions | undefined, -) => ServiceFactory; +export const rootConfigServiceFactory: (( + options?: RootConfigFactoryOptions, +) => ServiceFactory) & + ServiceFactory; // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/api-report-rootHealth.md b/packages/backend-defaults/api-report-rootHealth.md index ffa067605a..c69f66e747 100644 --- a/packages/backend-defaults/api-report-rootHealth.md +++ b/packages/backend-defaults/api-report-rootHealth.md @@ -4,12 +4,13 @@ ```ts import { RootHealthService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @public (undocumented) -export const rootHealthServiceFactory: () => ServiceFactory< +export const rootHealthServiceFactory: ServiceFactoryCompat< RootHealthService, - 'root' + 'root', + undefined >; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-defaults/api-report-rootHttpRouter.md b/packages/backend-defaults/api-report-rootHttpRouter.md index d9727fd36d..36f3aa3ab6 100644 --- a/packages/backend-defaults/api-report-rootHttpRouter.md +++ b/packages/backend-defaults/api-report-rootHttpRouter.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +/// /// import { Config } from '@backstage/config'; @@ -17,10 +18,17 @@ import { LoggerService } from '@backstage/backend-plugin-api'; import { RequestHandler } from 'express'; import { RequestListener } from 'http'; import { RootConfigService } from '@backstage/backend-plugin-api'; +import { RootHealthService } from '@backstage/backend-plugin-api'; import { RootHttpRouterService } from '@backstage/backend-plugin-api'; +import { Router } from 'express'; import type { Server } from 'node:http'; import { ServiceFactory } from '@backstage/backend-plugin-api'; +// @public (undocumented) +export function createHealthRouter(options: { + health: RootHealthService; +}): Router; + // @public export function createHttpServer( listener: RequestListener, @@ -141,9 +149,10 @@ export type RootHttpRouterFactoryOptions = { }; // @public (undocumented) -export const rootHttpRouterServiceFactory: ( - options?: RootHttpRouterFactoryOptions | undefined, -) => ServiceFactory; +export const rootHttpRouterServiceFactory: (( + options?: RootHttpRouterFactoryOptions, +) => ServiceFactory) & + ServiceFactory; // (No @packageDocumentation comment for this package) ``` diff --git a/packages/backend-defaults/api-report-rootLifecycle.md b/packages/backend-defaults/api-report-rootLifecycle.md index 00eb9c1ebb..faa83706a7 100644 --- a/packages/backend-defaults/api-report-rootLifecycle.md +++ b/packages/backend-defaults/api-report-rootLifecycle.md @@ -4,12 +4,13 @@ ```ts import { RootLifecycleService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @public -export const rootLifecycleServiceFactory: () => ServiceFactory< +export const rootLifecycleServiceFactory: ServiceFactoryCompat< RootLifecycleService, - 'root' + 'root', + undefined >; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-defaults/api-report-rootLogger.md b/packages/backend-defaults/api-report-rootLogger.md index 70dc0deee6..075a8b4aa9 100644 --- a/packages/backend-defaults/api-report-rootLogger.md +++ b/packages/backend-defaults/api-report-rootLogger.md @@ -7,13 +7,14 @@ import { Format } from 'logform'; import { JsonObject } from '@backstage/types'; import { LoggerService } from '@backstage/backend-plugin-api'; import { RootLoggerService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; import { transport } from 'winston'; // @public -export const rootLoggerServiceFactory: () => ServiceFactory< +export const rootLoggerServiceFactory: ServiceFactoryCompat< RootLoggerService, - 'root' + 'root', + undefined >; // @public diff --git a/packages/backend-defaults/api-report-scheduler.md b/packages/backend-defaults/api-report-scheduler.md index 58991ddca3..a3467b97de 100644 --- a/packages/backend-defaults/api-report-scheduler.md +++ b/packages/backend-defaults/api-report-scheduler.md @@ -6,7 +6,7 @@ import { DatabaseService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; import { SchedulerService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; // @public export class DefaultSchedulerService { @@ -18,9 +18,10 @@ export class DefaultSchedulerService { } // @public -export const schedulerServiceFactory: () => ServiceFactory< +export const schedulerServiceFactory: ServiceFactoryCompat< SchedulerService, - 'plugin' + 'plugin', + undefined >; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-defaults/api-report-urlReader.md b/packages/backend-defaults/api-report-urlReader.md index c56cdba69b..0980e3cd67 100644 --- a/packages/backend-defaults/api-report-urlReader.md +++ b/packages/backend-defaults/api-report-urlReader.md @@ -21,7 +21,7 @@ import { GitLabIntegration } from '@backstage/integration'; import { HarnessIntegration } from '@backstage/integration'; import { LoggerService } from '@backstage/backend-plugin-api'; import { Readable } from 'stream'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { UrlReaderServiceReadTreeOptions } from '@backstage/backend-plugin-api'; import { UrlReaderServiceReadTreeResponse } from '@backstage/backend-plugin-api'; @@ -426,9 +426,10 @@ export class UrlReaders { } // @public -export const urlReaderServiceFactory: () => ServiceFactory< +export const urlReaderServiceFactory: ServiceFactoryCompat< UrlReaderService, - 'plugin' + 'plugin', + undefined >; // @public diff --git a/packages/backend-defaults/api-report-userInfo.md b/packages/backend-defaults/api-report-userInfo.md index d11fb69718..b77878ff34 100644 --- a/packages/backend-defaults/api-report-userInfo.md +++ b/packages/backend-defaults/api-report-userInfo.md @@ -3,13 +3,14 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; import { UserInfoService } from '@backstage/backend-plugin-api'; // @public -export const userInfoServiceFactory: () => ServiceFactory< +export const userInfoServiceFactory: ServiceFactoryCompat< UserInfoService, - 'plugin' + 'plugin', + undefined >; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 376aa9d36f..9c04507003 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.3.3-next.0", + "version": "0.4.2-next.0", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" @@ -143,7 +143,7 @@ "@types/express": "^4.17.6", "archiver": "^6.0.0", "base64-stream": "^1.0.0", - "better-sqlite3": "^9.0.0", + "better-sqlite3": "^11.0.0", "compression": "^1.7.4", "concat-stream": "^2.0.0", "cookie": "^0.6.0", diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index e3195748a6..47fa09da49 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -40,26 +40,26 @@ import { userInfoServiceFactory } from '@backstage/backend-defaults/userInfo'; import { eventsServiceFactory } from '@backstage/plugin-events-node'; export const defaultServiceFactories = [ - authServiceFactory(), - cacheServiceFactory(), - rootConfigServiceFactory(), - databaseServiceFactory(), - discoveryServiceFactory(), - httpAuthServiceFactory(), - httpRouterServiceFactory(), - identityServiceFactory(), - lifecycleServiceFactory(), - loggerServiceFactory(), - permissionsServiceFactory(), - rootHealthServiceFactory(), - rootHttpRouterServiceFactory(), - rootLifecycleServiceFactory(), - rootLoggerServiceFactory(), - schedulerServiceFactory(), - tokenManagerServiceFactory(), - userInfoServiceFactory(), - urlReaderServiceFactory(), - eventsServiceFactory(), + authServiceFactory, + cacheServiceFactory, + rootConfigServiceFactory, + databaseServiceFactory, + discoveryServiceFactory, + httpAuthServiceFactory, + httpRouterServiceFactory, + identityServiceFactory, + lifecycleServiceFactory, + loggerServiceFactory, + permissionsServiceFactory, + rootHealthServiceFactory, + rootHttpRouterServiceFactory, + rootLifecycleServiceFactory, + rootLoggerServiceFactory, + schedulerServiceFactory, + tokenManagerServiceFactory, + userInfoServiceFactory, + urlReaderServiceFactory, + eventsServiceFactory, ]; /** diff --git a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts index 22357d0f49..f9ef4c9762 100644 --- a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts @@ -17,7 +17,7 @@ import { ServiceFactoryTester, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { tokenManagerServiceFactory } from '@backstage/backend-app-api'; import { authServiceFactory } from './authServiceFactory'; @@ -68,7 +68,7 @@ const mockDeps = [ ]; describe('authServiceFactory', () => { - setupRequestMockHandlers(server); + registerMswTestHooks(server); afterEach(() => { jest.useRealTimers(); @@ -86,8 +86,8 @@ describe('authServiceFactory', () => { dependencies: mockDeps, }); - const searchAuth = await tester.get('search'); - const catalogAuth = await tester.get('catalog'); + const searchAuth = await tester.getSubject('search'); + const catalogAuth = await tester.getSubject('catalog'); const { token: searchToken } = await searchAuth.getPluginRequestToken({ onBehalfOf: await searchAuth.getOwnServiceCredentials(), @@ -117,8 +117,8 @@ describe('authServiceFactory', () => { dependencies: mockDeps, }); - const searchAuth = await tester.get('search'); - const catalogAuth = await tester.get('catalog'); + const searchAuth = await tester.getSubject('search'); + const catalogAuth = await tester.getSubject('catalog'); server.use( rest.get( @@ -163,7 +163,7 @@ describe('authServiceFactory', () => { dependencies: mockDeps, }); - const catalogAuth = await tester.get('catalog'); + const catalogAuth = await tester.getSubject('catalog'); await expect( catalogAuth.getPluginRequestToken({ @@ -194,7 +194,7 @@ describe('authServiceFactory', () => { dependencies: mockDeps, }); - const catalogAuth = await tester.get('catalog'); + const catalogAuth = await tester.getSubject('catalog'); const { token } = await catalogAuth.getPluginRequestToken({ onBehalfOf: { @@ -261,7 +261,7 @@ describe('authServiceFactory', () => { dependencies: mockDeps, }); - const catalogAuth = await tester.get('catalog'); + const catalogAuth = await tester.getSubject('catalog'); const fullToken = 'eyJ0eXAiOiJ2bmQuYmFja3N0YWdlLnVzZXIiLCJhbGciOiJFUzI1NiIsImtpZCI6IjhkMDFjM2RiLTU2ZjktNDVmMC04NmRkLTA1YjNjODM1YjNkMyJ9.eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjcwMDcvYXBpL2F1dGgiLCJzdWIiOiJ1c2VyOmRldmVsb3BtZW50L2d1ZXN0IiwiZW50IjpbInVzZXI6ZGV2ZWxvcG1lbnQvZ3Vlc3QiLCJncm91cDpkZWZhdWx0L3RlYW0tYSJdLCJhdWQiOiJiYWNrc3RhZ2UiLCJpYXQiOjE3MTIwNzE3MTQsImV4cCI6MTcxMjA3NTMxNCwidWlwIjoiSmwxVEpycG9VUjR1NENjUE9nalJMeHpEMi1FMGZPR3ptSm81UWI2eS1aN19meG5oVVBEdWVWRE1CS0l6WF9pc0lvSDhlZm9EUFA5bG9aQnpPblB5Z2cifQ.1gVMq1ofO8PzRctu72D6c4IMqXuIabT79WdGEhW6vIrBRs_qhuWAa94Wvz_KYKpBTb2nxgzXJ5OeddeoYApMyQ'; @@ -318,9 +318,9 @@ describe('authServiceFactory', () => { dependencies: mockDeps, }); - const searchAuth = await tester.get('search'); - const catalogAuth = await tester.get('catalog'); - const permissionAuth = await tester.get('permission'); + const searchAuth = await tester.getSubject('search'); + const catalogAuth = await tester.getSubject('catalog'); + const permissionAuth = await tester.getSubject('permission'); /* Corresponding private key in case this test needs to be updated in the future: { @@ -431,7 +431,7 @@ describe('authServiceFactory', () => { dependencies: mockDeps, }); - const catalogAuth = await tester.get('catalog'); + const catalogAuth = await tester.getSubject('catalog'); await expect( catalogAuth.authenticate('limited-static-token'), @@ -450,7 +450,7 @@ describe('authServiceFactory', () => { }, }); - const scaffolderAuth = await tester.get('scaffolder'); + const scaffolderAuth = await tester.getSubject('scaffolder'); await expect( scaffolderAuth.authenticate('limited-static-token'), diff --git a/packages/backend-defaults/src/entrypoints/auth/external/ExternalTokenHandler.test.ts b/packages/backend-defaults/src/entrypoints/auth/external/ExternalTokenHandler.test.ts index 559107c59c..39b01c46b6 100644 --- a/packages/backend-defaults/src/entrypoints/auth/external/ExternalTokenHandler.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/external/ExternalTokenHandler.test.ts @@ -19,7 +19,7 @@ import { ExternalTokenHandler } from './ExternalTokenHandler'; import { TokenHandler } from './types'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { randomBytes } from 'crypto'; import { SignJWT, exportJWK, generateKeyPair } from 'jose'; @@ -81,7 +81,7 @@ class FakeTokenFactory { describe('ExternalTokenHandler', () => { const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); it('skips over inner handlers that do not match, and applies plugin restrictions', async () => { const handler1: TokenHandler = { diff --git a/packages/backend-defaults/src/entrypoints/auth/external/jwks.test.ts b/packages/backend-defaults/src/entrypoints/auth/external/jwks.test.ts index 56930e4480..321e1823aa 100644 --- a/packages/backend-defaults/src/entrypoints/auth/external/jwks.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/external/jwks.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { SignJWT, exportJWK, generateKeyPair } from 'jose'; import { rest } from 'msw'; @@ -81,7 +81,7 @@ describe('JWKSHandler', () => { let mockSubject: string; const keyDurationSeconds = 5; - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { mockSubject = 'test_subject'; diff --git a/packages/backend-defaults/src/entrypoints/auth/user/UserTokenHandler.test.ts b/packages/backend-defaults/src/entrypoints/auth/user/UserTokenHandler.test.ts index 76bafb2a8f..e85ec8db63 100644 --- a/packages/backend-defaults/src/entrypoints/auth/user/UserTokenHandler.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/user/UserTokenHandler.test.ts @@ -18,7 +18,7 @@ import { JsonObject } from '@backstage/types'; import { UserTokenHandler } from './UserTokenHandler'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -64,7 +64,7 @@ async function createToken(options: { describe('UserTokenHandler', () => { let userTokenHandler: UserTokenHandler; - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.useRealTimers(); diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.test.ts index 58c074f985..a8b749939b 100644 --- a/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.test.ts @@ -35,7 +35,7 @@ describe('httpRouterFactory', () => { dependencies: [rootHttpRouter.factory], }); - const router1 = await tester.get('test1'); + const router1 = await tester.getSubject('test1'); router1.use(() => {}); expect(rootHttpRouter.use).toHaveBeenCalledTimes(1); expect(rootHttpRouter.use).toHaveBeenCalledWith( @@ -43,7 +43,7 @@ describe('httpRouterFactory', () => { expect.any(Function), ); - const router2 = await tester.get('test2'); + const router2 = await tester.getSubject('test2'); router2.use(() => {}); expect(rootHttpRouter.use).toHaveBeenCalledTimes(2); expect(rootHttpRouter.use).toHaveBeenCalledWith( @@ -52,32 +52,6 @@ describe('httpRouterFactory', () => { ); }); - it('should use custom path generator', async () => { - const rootHttpRouter = mockServices.rootHttpRouter.mock(); - const tester = ServiceFactoryTester.from( - httpRouterServiceFactory({ - getPath: id => `/some/${id}/path`, - }), - { dependencies: [rootHttpRouter.factory] }, - ); - - const router1 = await tester.get('test1'); - router1.use(() => {}); - expect(rootHttpRouter.use).toHaveBeenCalledTimes(1); - expect(rootHttpRouter.use).toHaveBeenCalledWith( - '/some/test1/path', - expect.any(Function), - ); - - const router2 = await tester.get('test2'); - router2.use(() => {}); - expect(rootHttpRouter.use).toHaveBeenCalledTimes(2); - expect(rootHttpRouter.use).toHaveBeenCalledWith( - '/some/test2/path', - expect.any(Function), - ); - }); - describe('auth services', () => { const pluginSubject = createBackendPlugin({ pluginId: 'test', diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts b/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts index 4a0dec49e6..fb2646580b 100644 --- a/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.ts @@ -26,16 +26,6 @@ import { createCredentialsBarrier } from './createCredentialsBarrier'; import { createAuthIntegrationRouter } from './createAuthIntegrationRouter'; import { createCookieAuthRefreshMiddleware } from './createCookieAuthRefreshMiddleware'; -/** - * @public - */ -export interface HttpRouterFactoryOptions { - /** - * A callback used to generate the path for each plugin, defaults to `/api/{pluginId}`. - */ - getPath?(pluginId: string): string; -} - /** * HTTP route registration for plugins. * @@ -45,57 +35,39 @@ export interface HttpRouterFactoryOptions { * * @public */ -export const httpRouterServiceFactory = createServiceFactory( - (options?: HttpRouterFactoryOptions) => ({ - service: coreServices.httpRouter, - initialization: 'always', - deps: { - plugin: coreServices.pluginMetadata, - config: coreServices.rootConfig, - logger: coreServices.logger, - lifecycle: coreServices.lifecycle, - rootHttpRouter: coreServices.rootHttpRouter, - auth: coreServices.auth, - httpAuth: coreServices.httpAuth, - }, - async factory({ - auth, +export const httpRouterServiceFactory = createServiceFactory({ + service: coreServices.httpRouter, + initialization: 'always', + deps: { + plugin: coreServices.pluginMetadata, + config: coreServices.rootConfig, + lifecycle: coreServices.lifecycle, + rootHttpRouter: coreServices.rootHttpRouter, + auth: coreServices.auth, + httpAuth: coreServices.httpAuth, + }, + async factory({ auth, httpAuth, config, plugin, rootHttpRouter, lifecycle }) { + const router = PromiseRouter(); + + rootHttpRouter.use(`/api/${plugin.getId()}`, router); + + const credentialsBarrier = createCredentialsBarrier({ httpAuth, config, - logger, - plugin, - rootHttpRouter, - lifecycle, - }) { - if (options?.getPath) { - logger.warn( - `DEPRECATION WARNING: The 'getPath' option for HttpRouterService is deprecated. The ability to reconfigure the '/api/' path prefix for plugins will be removed in the future.`, - ); - } - const getPath = options?.getPath ?? (id => `/api/${id}`); - const path = getPath(plugin.getId()); + }); - const router = PromiseRouter(); - rootHttpRouter.use(path, router); + router.use(createAuthIntegrationRouter({ auth })); + router.use(createLifecycleMiddleware({ lifecycle })); + router.use(credentialsBarrier.middleware); + router.use(createCookieAuthRefreshMiddleware({ auth, httpAuth })); - const credentialsBarrier = createCredentialsBarrier({ - httpAuth, - config, - }); - - router.use(createAuthIntegrationRouter({ auth })); - router.use(createLifecycleMiddleware({ lifecycle })); - router.use(credentialsBarrier.middleware); - router.use(createCookieAuthRefreshMiddleware({ auth, httpAuth })); - - return { - use(handler: Handler): void { - router.use(handler); - }, - addAuthPolicy(policy: HttpRouterServiceAuthPolicy): void { - credentialsBarrier.addAuthPolicy(policy); - }, - }; - }, - }), -); + return { + use(handler: Handler): void { + router.use(handler); + }, + addAuthPolicy(policy: HttpRouterServiceAuthPolicy): void { + credentialsBarrier.addAuthPolicy(policy); + }, + }; + }, +}); diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/index.ts b/packages/backend-defaults/src/entrypoints/httpRouter/index.ts index 928a232131..bc12480ae8 100644 --- a/packages/backend-defaults/src/entrypoints/httpRouter/index.ts +++ b/packages/backend-defaults/src/entrypoints/httpRouter/index.ts @@ -15,6 +15,5 @@ */ export { httpRouterServiceFactory } from './httpRouterServiceFactory'; -export type { HttpRouterFactoryOptions } from './httpRouterServiceFactory'; export { createLifecycleMiddleware } from './createLifecycleMiddleware'; export type { LifecycleMiddlewareOptions } from './createLifecycleMiddleware'; diff --git a/packages/backend-defaults/src/entrypoints/rootConfig/rootConfigServiceFactory.ts b/packages/backend-defaults/src/entrypoints/rootConfig/rootConfigServiceFactory.ts index 5752a9c634..63a7353241 100644 --- a/packages/backend-defaults/src/entrypoints/rootConfig/rootConfigServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootConfig/rootConfigServiceFactory.ts @@ -45,11 +45,10 @@ export interface RootConfigFactoryOptions { watch?: boolean; } -/** - * @public - */ -export const rootConfigServiceFactory = createServiceFactory( - (options?: RootConfigFactoryOptions) => ({ +export const rootConfigServiceFactoryWithOptions = ( + options?: RootConfigFactoryOptions, +) => + createServiceFactory({ service: coreServices.rootConfig, deps: {}, async factory() { @@ -61,5 +60,12 @@ export const rootConfigServiceFactory = createServiceFactory( console.log(`Loading config from ${source}`); return await ConfigSources.toConfig(source); }, - }), + })(); + +/** + * @public + */ +export const rootConfigServiceFactory = Object.assign( + rootConfigServiceFactoryWithOptions, + rootConfigServiceFactoryWithOptions(), ); diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/createHealthRouter.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/createHealthRouter.ts index 13691ede28..3772ab0fab 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/createHealthRouter.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/createHealthRouter.ts @@ -1,5 +1,3 @@ -import { RootHealthService } from '@backstage/backend-plugin-api'; - /* * Copyright 2024 The Backstage Authors * @@ -16,14 +14,18 @@ import { RootHealthService } from '@backstage/backend-plugin-api'; * limitations under the License. */ +import { RootHealthService } from '@backstage/backend-plugin-api'; import Router from 'express-promise-router'; import { Request, Response } from 'express'; +/** + * @public + */ export function createHealthRouter(options: { health: RootHealthService }) { const router = Router(); router.get( - '.backstage/health/v1/readiness', + '/.backstage/health/v1/readiness', async (_request: Request, response: Response) => { const { status, payload } = await options.health.getReadiness(); response.status(status).json(payload); @@ -31,7 +33,7 @@ export function createHealthRouter(options: { health: RootHealthService }) { ); router.get( - '.backstage/health/v1/liveness', + '/.backstage/health/v1/liveness', async (_request: Request, response: Response) => { const { status, payload } = await options.health.getLiveness(); response.status(status).json(payload); diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/index.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/index.ts index 9c2d96dab2..e9aee8a0e0 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/index.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/index.ts @@ -18,6 +18,7 @@ export { DefaultRootHttpRouter, type DefaultRootHttpRouterOptions, } from './DefaultRootHttpRouter'; +export { createHealthRouter } from './createHealthRouter'; export * from './http'; export { rootHttpRouterServiceFactory, diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts new file mode 100644 index 0000000000..dea0159dff --- /dev/null +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { + ServiceFactoryTester, + mockServices, +} from '@backstage/backend-test-utils'; +import { Express } from 'express'; +import request from 'supertest'; +import { rootHttpRouterServiceFactory } from './rootHttpRouterServiceFactory'; +import { coreServices } from '@backstage/backend-plugin-api'; + +describe('rootHttpRouterServiceFactory', () => { + it('should make the health endpoints available', async () => { + let app: Express | undefined = undefined; + + const tester = ServiceFactoryTester.from( + rootHttpRouterServiceFactory({ + configure(options) { + options.applyDefaults(); + app = options.app; + }, + }), + { + dependencies: [ + mockServices.rootConfig.factory({ + data: { + app: { baseUrl: 'http://localhost' }, + backend: { + baseUrl: 'http://localhost', + listen: { host: '', port: 0 }, + }, + }, + }), + ], + }, + ); + + // Trigger creation of the http service, accessing the app instance through the configure callback + await tester.getSubject(); + + await request(app!) + .get('/.backstage/health/v1/liveness') + .expect(200, { status: 'ok' }); + + await request(app!).get('/.backstage/health/v1/readiness').expect(503, { + message: 'Backend has not started yet', + status: 'error', + }); + + const lifecycle = await tester.getService(coreServices.rootLifecycle); + + await (lifecycle as any).startup(); // Trigger startup by calling the private startup method + + await request(app!).get('/.backstage/health/v1/readiness').expect(200, { + status: 'ok', + }); + + expect('test').toBe('test'); + }); +}); diff --git a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts index 22f5c2f310..c3a97737e4 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory.ts @@ -69,9 +69,10 @@ function defaultConfigure({ applyDefaults }: RootHttpRouterConfigureContext) { applyDefaults(); } -/** @public */ -export const rootHttpRouterServiceFactory = createServiceFactory( - (options?: RootHttpRouterFactoryOptions) => ({ +const rootHttpRouterServiceFactoryWithOptions = ( + options?: RootHttpRouterFactoryOptions, +) => + createServiceFactory({ service: coreServices.rootHttpRouter, deps: { config: coreServices.rootConfig, @@ -122,5 +123,10 @@ export const rootHttpRouterServiceFactory = createServiceFactory( return router; }, - }), + })(); + +/** @public */ +export const rootHttpRouterServiceFactory = Object.assign( + rootHttpRouterServiceFactoryWithOptions, + rootHttpRouterServiceFactoryWithOptions(), ); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts index 7b889b3a0e..b16509cf96 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts @@ -22,7 +22,7 @@ describe('schedulerFactory', () => { it('creates sidecar database features', async () => { const tester = ServiceFactoryTester.from(schedulerServiceFactory()); - const scheduler = await tester.get(); + const scheduler = await tester.getSubject(); await scheduler.scheduleTask({ id: 'task1', timeout: { seconds: 1 }, diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.ts index 7f02f1468f..5a2655ad1c 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.ts @@ -213,6 +213,7 @@ export class AwsCodeCommitUrlReader implements UrlReaderService { ); const codeCommit = new CodeCommitClient({ + customUserAgent: 'backstage-aws-codecommit-url-reader', region: region, credentials: credentials, }); diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.ts index 27f60f5b77..324538add4 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader.ts @@ -222,6 +222,7 @@ export class AwsS3UrlReader implements UrlReaderService { ); const s3 = new S3Client({ + customUserAgent: 'backstage-aws-s3-url-reader', region: region, credentials: credentials, endpoint: integration.config.endpoint, diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/AzureUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/AzureUrlReader.test.ts index ce2385507b..f53a7b6c11 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/AzureUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/AzureUrlReader.test.ts @@ -26,7 +26,7 @@ import { import { createMockDirectory, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; import { rest } from 'msw'; @@ -76,7 +76,7 @@ describe('AzureUrlReader', () => { beforeEach(mockDir.clear); const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); describe('read', () => { beforeEach(() => { diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.test.ts index dc182af619..f5caade3fa 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader.test.ts @@ -21,7 +21,7 @@ import { } from '@backstage/integration'; import { createMockDirectory, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; import { rest } from 'msw'; @@ -56,7 +56,7 @@ describe('BitbucketCloudUrlReader', () => { beforeEach(mockDir.clear); const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); describe('readUrl', () => { it('should be able to readUrl via buffer without ETag', async () => { diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketServerUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketServerUrlReader.test.ts index 2870138535..6af2858cde 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketServerUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketServerUrlReader.test.ts @@ -21,7 +21,7 @@ import { } from '@backstage/integration'; import { createMockDirectory, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; import { rest } from 'msw'; @@ -51,7 +51,7 @@ const reader = new BitbucketServerUrlReader( describe('BitbucketServerUrlReader', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); describe('readTree', () => { const repoBuffer = fs.readFileSync( diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketUrlReader.test.ts index edd8d6d3fc..54880aaa75 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/BitbucketUrlReader.test.ts @@ -22,7 +22,7 @@ import { import { createMockDirectory, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; import { rest } from 'msw'; @@ -104,7 +104,7 @@ describe('BitbucketUrlReader', () => { ); const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); describe('readUrl', () => { it('should be able to readUrl via buffer without ETag', async () => { diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/FetchUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/FetchUrlReader.test.ts index 38936806c6..f84422c564 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/FetchUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/FetchUrlReader.test.ts @@ -18,7 +18,7 @@ import { ConfigReader } from '@backstage/config'; import { NotFoundError, NotModifiedError } from '@backstage/errors'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -30,7 +30,7 @@ const fetchUrlReader = new FetchUrlReader(); describe('FetchUrlReader', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); beforeEach(() => { jest.clearAllMocks(); diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader.test.ts index 94627e074f..14ca4c018e 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader.test.ts @@ -17,7 +17,7 @@ import { createMockDirectory, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { NotModifiedError, NotFoundError } from '@backstage/errors'; @@ -92,7 +92,7 @@ const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { // eslint-disable-next-line jest/no-disabled-tests describe.skip('GerritUrlReader', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); beforeEach(() => { mockDir.clear(); diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GiteaUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GiteaUrlReader.test.ts index 0b5983eefd..77c995c552 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GiteaUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GiteaUrlReader.test.ts @@ -16,7 +16,7 @@ import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { GiteaIntegration, readGiteaConfig } from '@backstage/integration'; @@ -64,7 +64,7 @@ const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { describe('GiteaUrlReader', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); afterAll(() => { jest.clearAllMocks(); diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts index f07ada0cc3..f1dd7c0dc6 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts @@ -22,7 +22,7 @@ import { } from '@backstage/integration'; import { createMockDirectory, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; import { rest } from 'msw'; @@ -74,7 +74,7 @@ const gheProcessor = new GithubUrlReader( describe('GithubUrlReader', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); beforeEach(mockDir.clear); diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts index 46276fcdda..dd6a0970d2 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts @@ -18,7 +18,7 @@ import { ConfigReader } from '@backstage/config'; import { createMockDirectory, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; import { rest } from 'msw'; @@ -72,7 +72,7 @@ describe('GitlabUrlReader', () => { beforeEach(mockDir.clear); const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); describe('read', () => { beforeEach(() => { diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts index 615eeb20a2..35123da851 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts @@ -23,21 +23,21 @@ import { UrlReaderServiceSearchOptions, UrlReaderServiceSearchResponse, } from '@backstage/backend-plugin-api'; +import { NotFoundError, NotModifiedError } from '@backstage/errors'; import { + GitLabIntegration, + ScmIntegrations, getGitLabFileFetchUrl, getGitLabIntegrationRelativePath, getGitLabRequestOptions, - GitLabIntegration, - ScmIntegrations, } from '@backstage/integration'; -import fetch, { Response } from 'node-fetch'; import parseGitUrl from 'git-url-parse'; -import { Minimatch } from 'minimatch'; -import { Readable } from 'stream'; -import { NotFoundError, NotModifiedError } from '@backstage/errors'; -import { ReadTreeResponseFactory, ReaderFactory } from './types'; import { trimEnd, trimStart } from 'lodash'; +import { Minimatch } from 'minimatch'; +import fetch, { Response } from 'node-fetch'; +import { Readable } from 'stream'; import { ReadUrlResponseFactory } from './ReadUrlResponseFactory'; +import { ReadTreeResponseFactory, ReaderFactory } from './types'; import { parseLastModified } from './util'; /** @@ -71,14 +71,14 @@ export class GitlabUrlReader implements UrlReaderService { url: string, options?: UrlReaderServiceReadUrlOptions, ): Promise { - const { etag, lastModifiedAfter, signal } = options ?? {}; + const { etag, lastModifiedAfter, signal, token } = options ?? {}; const builtUrl = await this.getGitlabFetchUrl(url); let response: Response; try { response = await fetch(builtUrl, { headers: { - ...getGitLabRequestOptions(this.integration.config).headers, + ...getGitLabRequestOptions(this.integration.config, token).headers, ...(etag && { 'If-None-Match': etag }), ...(lastModifiedAfter && { 'If-Modified-Since': lastModifiedAfter.toUTCString(), @@ -120,7 +120,7 @@ export class GitlabUrlReader implements UrlReaderService { url: string, options?: UrlReaderServiceReadTreeOptions, ): Promise { - const { etag, signal } = options ?? {}; + const { etag, signal, token } = options ?? {}; const { ref, full_name, filepath } = parseGitUrl(url); let repoFullName = full_name; @@ -147,7 +147,7 @@ export class GitlabUrlReader implements UrlReaderService { repoFullName, )}`, ).toString(), - getGitLabRequestOptions(this.integration.config), + getGitLabRequestOptions(this.integration.config, token), ); if (!projectGitlabResponse.ok) { const msg = `Failed to read tree from ${url}, ${projectGitlabResponse.status} ${projectGitlabResponse.statusText}`; @@ -175,7 +175,7 @@ export class GitlabUrlReader implements UrlReaderService { )}/repository/commits?${commitsReqParams.toString()}`, ).toString(), { - ...getGitLabRequestOptions(this.integration.config), + ...getGitLabRequestOptions(this.integration.config, token), // 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 @@ -209,7 +209,7 @@ export class GitlabUrlReader implements UrlReaderService { repoFullName, )}/repository/archive?${archiveReqParams.toString()}`, { - ...getGitLabRequestOptions(this.integration.config), + ...getGitLabRequestOptions(this.integration.config, token), // 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 diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/HarnessUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/HarnessUrlReader.test.ts index f1efbca108..004e1931d0 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/HarnessUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/HarnessUrlReader.test.ts @@ -16,7 +16,7 @@ import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { HarnessIntegration, readHarnessConfig } from '@backstage/integration'; @@ -120,7 +120,7 @@ const handlers = [ describe('HarnessUrlReader', () => { const worker = setupServer(...handlers); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); beforeAll(() => worker.listen({ onUnhandledRequest: 'bypass' })); afterAll(() => { jest.clearAllMocks(); diff --git a/packages/backend-defaults/src/entrypoints/userInfo/DefaultUserInfoService.test.ts b/packages/backend-defaults/src/entrypoints/userInfo/DefaultUserInfoService.test.ts index b63b24fb18..692d179ee2 100644 --- a/packages/backend-defaults/src/entrypoints/userInfo/DefaultUserInfoService.test.ts +++ b/packages/backend-defaults/src/entrypoints/userInfo/DefaultUserInfoService.test.ts @@ -17,7 +17,7 @@ import { BackstageUserPrincipal } from '@backstage/backend-plugin-api'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { JsonObject } from '@backstage/types'; import { SignJWT, base64url, importJWK } from 'jose'; @@ -28,7 +28,7 @@ import { DefaultUserInfoService } from './DefaultUserInfoService'; describe('DefaultUserInfoService', () => { const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); const mockPublicKey = { kty: 'EC', diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index 4ec9489674..ade7a7c0ec 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,84 @@ # @backstage/backend-dynamic-feature-service +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-app-api@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.23-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-events-backend@0.3.10-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-search-common@1.2.13 + +## 0.2.15 + +### Patch Changes + +- b05e1e1: Service factories exported by this package have been updated to use the new service factory format that doesn't use a callback. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-app-api@0.8.0 + - @backstage/backend-common@0.23.3 + - @backstage/cli-node@0.2.7 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-catalog-backend@1.24.0 + - @backstage/plugin-app-node@0.1.22 + - @backstage/plugin-events-backend@0.3.9 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/config-loader@1.8.1 + - @backstage/plugin-search-common@1.2.13 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-app-api@0.7.10-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.6 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-events-backend@0.3.9-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-search-common@1.2.12 + ## 0.2.14-next.0 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/api-report.md b/packages/backend-dynamic-feature-service/api-report.md index 20db7db71b..39aa60b09b 100644 --- a/packages/backend-dynamic-feature-service/api-report.md +++ b/packages/backend-dynamic-feature-service/api-report.md @@ -28,7 +28,7 @@ import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { RootLoggerService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; import { TaskRunner } from '@backstage/backend-tasks'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -114,18 +114,20 @@ export interface DynamicPluginsFactoryOptions { } // @public (undocumented) -export const dynamicPluginsFeatureDiscoveryServiceFactory: () => ServiceFactory< +export const dynamicPluginsFeatureDiscoveryServiceFactory: ServiceFactoryCompat< FeatureDiscoveryService, - 'root' + 'root', + undefined >; // @public (undocumented) export const dynamicPluginsFrontendSchemas: BackendFeatureCompat; // @public (undocumented) -export const dynamicPluginsRootLoggerServiceFactory: () => ServiceFactory< +export const dynamicPluginsRootLoggerServiceFactory: ServiceFactoryCompat< RootLoggerService, - 'root' + 'root', + undefined >; // @public (undocumented) @@ -142,14 +144,18 @@ export interface DynamicPluginsSchemasService { } // @public (undocumented) -export const dynamicPluginsSchemasServiceFactory: ( - options?: DynamicPluginsSchemasOptions | undefined, -) => ServiceFactory; +export const dynamicPluginsSchemasServiceFactory: ServiceFactoryCompat< + DynamicPluginsSchemasService, + 'root', + DynamicPluginsSchemasOptions +>; // @public (undocumented) -export const dynamicPluginsServiceFactory: ( - options?: DynamicPluginsFactoryOptions | undefined, -) => ServiceFactory; +export const dynamicPluginsServiceFactory: ServiceFactoryCompat< + DynamicPluginProvider, + 'root', + DynamicPluginsFactoryOptions +>; // @public (undocumented) export const dynamicPluginsServiceRef: ServiceRef< diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 60ff4d2d4e..cc7a8b390b 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-dynamic-feature-service", "description": "Backstage dynamic feature service", - "version": "0.2.14-next.0", + "version": "0.2.16-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-legacy/CHANGELOG.md b/packages/backend-legacy/CHANGELOG.md index 9535ba1211..5acae66b6a 100644 --- a/packages/backend-legacy/CHANGELOG.md +++ b/packages/backend-legacy/CHANGELOG.md @@ -1,5 +1,139 @@ # example-backend-legacy +## 0.2.101-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/plugin-devtools-backend@0.3.9-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.33-next.0 + - example-app@0.2.100-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-app-backend@0.3.72-next.0 + - @backstage/plugin-auth-backend@0.22.10-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.0 + - @backstage/plugin-events-backend@0.3.10-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-kubernetes-backend@0.18.4-next.0 + - @backstage/plugin-permission-backend@0.5.47-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + - @backstage/plugin-proxy-backend@0.5.4-next.0 + - @backstage/plugin-scaffolder-backend@1.23.1-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.0 + - @backstage/plugin-search-backend@1.5.15-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-signals-backend@0.1.9-next.0 + - @backstage/plugin-signals-node@0.1.9-next.0 + - @backstage/plugin-techdocs-backend@1.10.10-next.0 + +## 0.2.100 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-scaffolder-backend@1.23.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-backend@0.5.46 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-techdocs-backend@1.10.9 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.4 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.39 + - @backstage/integration@1.13.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-search-backend@1.5.14 + - @backstage/plugin-catalog-backend@1.24.0 + - @backstage/plugin-app-backend@0.3.71 + - @backstage/plugin-auth-backend@0.22.9 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.9 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-devtools-backend@0.3.8 + - @backstage/plugin-events-backend@0.3.9 + - @backstage/plugin-kubernetes-backend@0.18.3 + - @backstage/plugin-proxy-backend@0.5.3 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.23 + - @backstage/plugin-search-backend-module-catalog@0.1.28 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.3 + - @backstage/plugin-search-backend-module-explore@0.1.28 + - @backstage/plugin-search-backend-module-pg@0.5.32 + - @backstage/plugin-search-backend-module-techdocs@0.1.27 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/plugin-signals-backend@0.1.8 + - @backstage/plugin-signals-node@0.1.8 + - example-app@0.2.99 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.2.100-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.23.0-next.2 + - example-app@0.2.99-next.2 + +## 0.2.100-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@1.10.9-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.4-next.1 + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.27-next.1 + - example-app@0.2.99-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-app-backend@0.3.71-next.1 + - @backstage/plugin-auth-backend@0.22.9-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.9-next.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-devtools-backend@0.3.8-next.1 + - @backstage/plugin-events-backend@0.3.9-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-kubernetes-backend@0.18.3-next.1 + - @backstage/plugin-permission-backend@0.5.46-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + - @backstage/plugin-proxy-backend@0.5.3-next.1 + - @backstage/plugin-scaffolder-backend@1.23.0-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.23-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.39-next.1 + - @backstage/plugin-search-backend@1.5.14-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.28-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.3-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.28-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.32-next.1 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-signals-backend@0.1.8-next.1 + - @backstage/plugin-signals-node@0.1.8-next.1 + ## 0.2.100-next.0 ### Patch Changes diff --git a/packages/backend-legacy/package.json b/packages/backend-legacy/package.json index c8b019848f..8b07afca38 100644 --- a/packages/backend-legacy/package.json +++ b/packages/backend-legacy/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-legacy", - "version": "0.2.100-next.0", + "version": "0.2.101-next.0", "backstage": { "role": "backend" }, @@ -66,7 +66,7 @@ "@gitbeaker/node": "^35.1.0", "@octokit/rest": "^19.0.3", "azure-devops-node-api": "^12.0.0", - "better-sqlite3": "^9.0.0", + "better-sqlite3": "^11.0.0", "dockerode": "^4.0.0", "example-app": "link:../app", "express": "^4.17.1", diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index 0040eddc57..1056cc4cc1 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/backend-openapi-utils +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/errors@1.2.4 + +## 0.1.15 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/errors@1.2.4 + +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/errors@1.2.4 + ## 0.1.14-next.0 ### Patch Changes diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 4b2551eb13..7b7778f9fe 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.14-next.0", + "version": "0.1.16-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index b36d7a996d..9dd2e2587c 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,127 @@ # @backstage/backend-plugin-api +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-permission-common@0.8.0 + +## 0.7.0 + +### Minor Changes + +- 36f91e8: **BREAKING**: The `PermissionsService` no longer supports passing the deprecated `token` option, and the request options are now required. + +### Patch Changes + +- 53ced70: Added a new Root Health Service which adds new endpoints for health checks. +- 083eaf9: Fix bug where ISO durations could no longer be used for schedules +- 062c01c: Deprecated the ability to define options for service factories through `createServiceFactory`. In the future all service factories will return a plain `ServiceFactory` object, rather than allowing users to pass options to the factory. To allow for customization of a service implementation one can instead export one or a few building blocks that allows for simple re-implementation of the service instead. + + For example, instead of: + + ```ts + export const fooServiceFactory = createServiceFactory( + (options?: { bar: string }) => ({ + service: fooServiceRef, + deps: { logger: coreServices.logger }, + factory({ logger }) { + return { + // Implementation of the foo service using the `bar` option. + }; + }, + }), + ); + ``` + + We instead encourage service implementations to provide an easy to use API for re-implementing the service for advanced use-cases: + + ```ts + /** @public */ + export class DefaultFooService implements FooService { + static create(options: { bar: string; logger: LoggerService }) { + return new DefaultFooService(options.logger, options.bar ?? 'default'); + } + + private constructor( + private readonly logger: string, + private readonly bar: string, + ) {} + + // The rest of the implementation + } + ``` + + A user that wishes to customize the service can then easily do so by defining their own factory: + + ```ts + export const customFooServiceFactory = createServiceFactory({ + service: fooServiceRef, + deps: { logger: coreServices.logger }, + factory({ logger }) { + return DefaultFooService.create({ logger, bar: 'baz' }); + }, + }); + ``` + + This is of course more verbose than the previous solution where the factory could be customized through `fooServiceFactory({ bar: 'baz' })`, but this is a simplified which in practice should be using static configuration instead. + + In cases where the old options patterns significantly improves the usability of the service factory, the old pattern can still be implemented like this: + + ```ts + const fooServiceFactoryWithOptions = (options?: { bar: string }) => + createServiceFactory({ + service: fooServiceRef, + deps: { logger: coreServices.logger }, + factory({ logger }) { + return { + // Implementation of the foo service using the `bar` option. + }; + }, + }); + + export const fooServiceFactory = Object.assign( + fooServiceFactoryWithOptions, + fooServiceFactoryWithOptions(), + ); + ``` + + This change is being made because the ability to define an options callback encourages bad design of services factories. When possible, a service should be configurable through static configuration, and the existence of options may discourage that. More importantly though, the existing options do not work well with the dependency injection system of services, which is a problem for callbacks an other more advanced options. This lead to a bad pattern where only a few explicit dependencies where made available in callbacks, rather than providing an API that allowed simple re-implementation of the service with full access to dependency injection. + + A separate benefit of this change is that it simplifies the TypeScript types in a way that allows TypeScript to provide a much better error message when a service factory doesn't properly implement the service interface. + +- fe47a3e: All service config types were renamed to option types in order to standardize frontend and backend `create*` function signatures: + + - The `ServiceRefConfig` type was renamed to`ServiceRefOptions`; + - The `RootServiceFactoryConfig` type was renamed to `RootServiceFactoryOptions`; + - The `PluginServiceFactoryConfig` type was renamed to `PluginServiceFactoryOptions` + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.6.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-permission-common@0.7.14 + ## 0.6.21-next.0 ### Patch Changes diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index e9762f652e..b3c35d3699 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -9,6 +9,7 @@ import { AuthorizePermissionRequest } from '@backstage/plugin-permission-common' import { AuthorizePermissionResponse } from '@backstage/plugin-permission-common'; import { Config } from '@backstage/config'; import { Duration } from 'luxon'; +import { EvaluatorRequestOptions } from '@backstage/plugin-permission-common'; import { Handler } from 'express'; import { HumanDuration } from '@backstage/types'; import { IdentityApi } from '@backstage/plugin-auth-node'; @@ -256,10 +257,10 @@ export function createServiceFactory< }, TOpts extends object | undefined = undefined, >( - config: RootServiceFactoryConfig, -): () => ServiceFactory; + options: RootServiceFactoryOptions, +): ServiceFactoryCompat; -// @public +// @public @deprecated export function createServiceFactory< TService, TImpl extends TService, @@ -268,45 +269,47 @@ export function createServiceFactory< }, TOpts extends object | undefined = undefined, >( - config: (options?: TOpts) => RootServiceFactoryConfig, -): (options?: TOpts) => ServiceFactory; - -// @public -export function createServiceFactory< - TService, - TImpl extends TService, - TDeps extends { - [name in string]: ServiceRef; - }, - TContext = undefined, - TOpts extends object | undefined = undefined, ->( - config: PluginServiceFactoryConfig, -): () => ServiceFactory; - -// @public -export function createServiceFactory< - TService, - TImpl extends TService, - TDeps extends { - [name in string]: ServiceRef; - }, - TContext = undefined, - TOpts extends object | undefined = undefined, ->( - config: ( + options: ( options?: TOpts, - ) => PluginServiceFactoryConfig, -): (options?: TOpts) => ServiceFactory; + ) => RootServiceFactoryOptions, +): ServiceFactoryCompat; + +// @public +export function createServiceFactory< + TService, + TImpl extends TService, + TDeps extends { + [name in string]: ServiceRef; + }, + TContext = undefined, + TOpts extends object | undefined = undefined, +>( + options: PluginServiceFactoryOptions, +): ServiceFactoryCompat; + +// @public @deprecated +export function createServiceFactory< + TService, + TImpl extends TService, + TDeps extends { + [name in string]: ServiceRef; + }, + TContext = undefined, + TOpts extends object | undefined = undefined, +>( + options: ( + options?: TOpts, + ) => PluginServiceFactoryOptions, +): ServiceFactoryCompat; // @public export function createServiceRef( - config: ServiceRefConfig, + options: ServiceRefOptions, ): ServiceRef; // @public export function createServiceRef( - config: ServiceRefConfig, + options: ServiceRefOptions, ): ServiceRef; // @public @@ -421,30 +424,38 @@ export interface LoggerService { export interface PermissionsService extends PermissionEvaluator { authorize( requests: AuthorizePermissionRequest[], - options?: PermissionsServiceRequestOptions, + options: PermissionsServiceRequestOptions, ): Promise; authorizeConditional( requests: QueryPermissionRequest[], - options?: PermissionsServiceRequestOptions, + options: PermissionsServiceRequestOptions, ): Promise; } // @public -export type PermissionsServiceRequestOptions = - | { - token?: string; - } - | { - credentials: BackstageCredentials; - }; +export interface PermissionsServiceRequestOptions + extends EvaluatorRequestOptions { + // (undocumented) + credentials: BackstageCredentials; +} // @public export interface PluginMetadataService { getId(): string; } +// @public @deprecated (undocumented) +export type PluginServiceFactoryConfig< + TService, + TContext, + TImpl extends TService, + TDeps extends { + [name in string]: ServiceRef; + }, +> = PluginServiceFactoryOptions; + // @public (undocumented) -export interface PluginServiceFactoryConfig< +export interface PluginServiceFactoryOptions< TService, TContext, TImpl extends TService, @@ -524,8 +535,17 @@ export interface RootLifecycleService extends LifecycleService {} // @public export interface RootLoggerService extends LoggerService {} +// @public @deprecated (undocumented) +export type RootServiceFactoryConfig< + TService, + TImpl extends TService, + TDeps extends { + [name in string]: ServiceRef; + }, +> = RootServiceFactoryOptions; + // @public (undocumented) -export interface RootServiceFactoryConfig< +export interface RootServiceFactoryOptions< TService, TImpl extends TService, TDeps extends { @@ -624,7 +644,19 @@ export interface ServiceFactory< service: ServiceRef; } -// @public +// @public @deprecated (undocumented) +export interface ServiceFactoryCompat< + TService = unknown, + TScope extends 'plugin' | 'root' = 'plugin' | 'root', + TOpts extends object | undefined = undefined, +> extends ServiceFactory { + // @deprecated (undocumented) + ( + ...options: undefined extends TOpts ? [] : [options?: TOpts] + ): ServiceFactory; +} + +// @public @deprecated export type ServiceFactoryOrFunction = ServiceFactory | (() => ServiceFactory); // @public @@ -638,12 +670,22 @@ export type ServiceRef< $$type: '@backstage/ServiceRef'; }; +// @public @deprecated (undocumented) +export type ServiceRefConfig< + TService, + TScope extends 'root' | 'plugin', +> = ServiceRefOptions; + // @public (undocumented) -export interface ServiceRefConfig { +export interface ServiceRefOptions { // (undocumented) - defaultFactory?: ( + defaultFactory?( service: ServiceRef, - ) => Promise; + ): Promise; + // @deprecated (undocumented) + defaultFactory?( + service: ServiceRef, + ): Promise<() => ServiceFactory>; // (undocumented) id: string; // (undocumented) diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index dbb59f7991..4bad9325f1 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-plugin-api", - "version": "0.6.21-next.0", + "version": "0.7.1-next.0", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" diff --git a/packages/backend-plugin-api/src/deprecated.ts b/packages/backend-plugin-api/src/deprecated.ts new file mode 100644 index 0000000000..17ccccf320 --- /dev/null +++ b/packages/backend-plugin-api/src/deprecated.ts @@ -0,0 +1,52 @@ +/* + * 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 { + ServiceRef, + ServiceRefOptions, + RootServiceFactoryOptions, + PluginServiceFactoryOptions, +} from './services'; + +/** + * @public + * @deprecated Use {@link ServiceRefOptions} instead + */ +export type ServiceRefConfig< + TService, + TScope extends 'root' | 'plugin', +> = ServiceRefOptions; + +/** + * @public + * @deprecated Use {@link RootServiceFactoryOptions} instead + */ +export type RootServiceFactoryConfig< + TService, + TImpl extends TService, + TDeps extends { [name in string]: ServiceRef }, +> = RootServiceFactoryOptions; + +/** + * @public + * @deprecated Use {@link PluginServiceFactoryOptions} instead + */ +export type PluginServiceFactoryConfig< + TService, + TContext, + TImpl extends TService, + TDeps extends { [name in string]: ServiceRef }, +> = PluginServiceFactoryOptions; diff --git a/packages/backend-plugin-api/src/index.ts b/packages/backend-plugin-api/src/index.ts index e6e57db1dc..eb554db25d 100644 --- a/packages/backend-plugin-api/src/index.ts +++ b/packages/backend-plugin-api/src/index.ts @@ -24,3 +24,4 @@ export * from './services'; export type { BackendFeature, BackendFeatureCompat } from './types'; export * from './paths'; export * from './wiring'; +export * from './deprecated'; diff --git a/packages/backend-plugin-api/src/services/definitions/PermissionsService.ts b/packages/backend-plugin-api/src/services/definitions/PermissionsService.ts index c5046a988b..a25081d17a 100644 --- a/packages/backend-plugin-api/src/services/definitions/PermissionsService.ts +++ b/packages/backend-plugin-api/src/services/definitions/PermissionsService.ts @@ -17,6 +17,7 @@ import { AuthorizePermissionRequest, AuthorizePermissionResponse, + EvaluatorRequestOptions, PermissionEvaluator, QueryPermissionRequest, QueryPermissionResponse, @@ -24,18 +25,14 @@ import { import { BackstageCredentials } from './AuthService'; /** - * Options for {@link @backstage/plugin-permission-common#PermissionEvaluator} requests. + * Options for {@link PermissionsService} requests. * * @public */ -export type PermissionsServiceRequestOptions = - | { - /** @deprecated use the `credentials` option instead. */ - token?: string; - } - | { - credentials: BackstageCredentials; - }; +export interface PermissionsServiceRequestOptions + extends EvaluatorRequestOptions { + credentials: BackstageCredentials; +} /** * Permission system integration for authorization of user/service actions. @@ -59,7 +56,7 @@ export interface PermissionsService extends PermissionEvaluator { */ authorize( requests: AuthorizePermissionRequest[], - options?: PermissionsServiceRequestOptions, + options: PermissionsServiceRequestOptions, ): Promise; /** @@ -79,6 +76,6 @@ export interface PermissionsService extends PermissionEvaluator { */ authorizeConditional( requests: QueryPermissionRequest[], - options?: PermissionsServiceRequestOptions, + options: PermissionsServiceRequestOptions, ): Promise; } diff --git a/packages/backend-plugin-api/src/services/system/index.ts b/packages/backend-plugin-api/src/services/system/index.ts index 2b91758cbc..c2d3b9c6fc 100644 --- a/packages/backend-plugin-api/src/services/system/index.ts +++ b/packages/backend-plugin-api/src/services/system/index.ts @@ -16,10 +16,11 @@ export type { ServiceRef, - ServiceRefConfig, + ServiceRefOptions, ServiceFactory, - PluginServiceFactoryConfig, - RootServiceFactoryConfig, + PluginServiceFactoryOptions, + RootServiceFactoryOptions, ServiceFactoryOrFunction, + ServiceFactoryCompat, } from './types'; export { createServiceRef, createServiceFactory } from './types'; diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts index 13252ba6e4..d31350806d 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -55,6 +55,23 @@ export interface ServiceFactory< service: ServiceRef; } +/** + * @public + * @deprecated This type exists only as a helper for old code that relied on `createServiceFactory` to return `() => ServiceFactory` instead of `ServiceFactory`. You should remove the `()` parentheses at the end of your usages. This type will be removed in a future release. + */ +export interface ServiceFactoryCompat< + TService = unknown, + TScope extends 'plugin' | 'root' = 'plugin' | 'root', + TOpts extends object | undefined = undefined, +> extends ServiceFactory { + /** + * @deprecated Callable service factories will be removed in a future release, please re-implement the service factory using the available APIs instead. If no options are being passed, you can simply remove the trailing `()`. + */ + ( + ...options: undefined extends TOpts ? [] : [options?: TOpts] + ): ServiceFactory; +} + /** @internal */ export interface InternalServiceFactory< TService = unknown, @@ -73,17 +90,24 @@ export interface InternalServiceFactory< /** * Represents either a {@link ServiceFactory} or a function that returns one. * + * @deprecated The support for service factory functions is deprecated and will be removed. * @public */ export type ServiceFactoryOrFunction = ServiceFactory | (() => ServiceFactory); /** @public */ -export interface ServiceRefConfig { +export interface ServiceRefOptions { id: string; scope?: TScope; - defaultFactory?: ( + defaultFactory?( service: ServiceRef, - ) => Promise; + ): Promise; + /** + * @deprecated The defaultFactory must return a plain `ServiceFactory` object, support for returning a function will be removed. + */ + defaultFactory?( + service: ServiceRef, + ): Promise<() => ServiceFactory>; } /** @@ -92,7 +116,7 @@ export interface ServiceRefConfig { * @public */ export function createServiceRef( - config: ServiceRefConfig, + options: ServiceRefOptions, ): ServiceRef; /** @@ -101,12 +125,13 @@ export function createServiceRef( * @public */ export function createServiceRef( - config: ServiceRefConfig, + options: ServiceRefOptions, ): ServiceRef; + export function createServiceRef( - config: ServiceRefConfig, + options: ServiceRefOptions, ): ServiceRef { - const { id, scope = 'plugin', defaultFactory } = config; + const { id, scope = 'plugin', defaultFactory } = options; return { id, scope, @@ -114,7 +139,7 @@ export function createServiceRef( throw new Error(`tried to read ServiceRef.T of ${this}`); }, toString() { - return `serviceRef{${config.id}}`; + return `serviceRef{${options.id}}`; }, $$type: '@backstage/ServiceRef', __defaultFactory: defaultFactory, @@ -134,7 +159,7 @@ type ServiceRefsToInstances< }; /** @public */ -export interface RootServiceFactoryConfig< +export interface RootServiceFactoryOptions< TService, TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, @@ -156,7 +181,7 @@ export interface RootServiceFactoryConfig< } /** @public */ -export interface PluginServiceFactoryConfig< +export interface PluginServiceFactoryOptions< TService, TContext, TImpl extends TService, @@ -188,7 +213,7 @@ export interface PluginServiceFactoryConfig< * Creates a root scoped service factory without options. * * @public - * @param config - The service factory configuration. + * @param options - The service factory configuration. */ export function createServiceFactory< TService, @@ -196,13 +221,17 @@ export function createServiceFactory< TDeps extends { [name in string]: ServiceRef }, TOpts extends object | undefined = undefined, >( - config: RootServiceFactoryConfig, -): () => ServiceFactory; + options: RootServiceFactoryOptions, +): ServiceFactoryCompat; /** * Creates a root scoped service factory with optional options. * + * @deprecated The ability to define options for service factories is deprecated + * and will be removed. Please use the non-callback form of createServiceFactory + * and provide an API that allows for a simple re-implementation of the service + * factory instead. * @public - * @param config - The service factory configuration. + * @param options - The service factory configuration. */ export function createServiceFactory< TService, @@ -210,13 +239,15 @@ export function createServiceFactory< TDeps extends { [name in string]: ServiceRef }, TOpts extends object | undefined = undefined, >( - config: (options?: TOpts) => RootServiceFactoryConfig, -): (options?: TOpts) => ServiceFactory; + options: ( + options?: TOpts, + ) => RootServiceFactoryOptions, +): ServiceFactoryCompat; /** * Creates a plugin scoped service factory without options. * * @public - * @param config - The service factory configuration. + * @param options - The service factory configuration. */ export function createServiceFactory< TService, @@ -225,13 +256,17 @@ export function createServiceFactory< TContext = undefined, TOpts extends object | undefined = undefined, >( - config: PluginServiceFactoryConfig, -): () => ServiceFactory; + options: PluginServiceFactoryOptions, +): ServiceFactoryCompat; /** * Creates a plugin scoped service factory with optional options. * + * @deprecated The ability to define options for service factories is deprecated + * and will be removed. Please use the non-callback form of createServiceFactory + * and provide an API that allows for a simple re-implementation of the service + * factory instead. * @public - * @param config - The service factory configuration. + * @param options - The service factory configuration. */ export function createServiceFactory< TService, @@ -240,10 +275,10 @@ export function createServiceFactory< TContext = undefined, TOpts extends object | undefined = undefined, >( - config: ( + options: ( options?: TOpts, - ) => PluginServiceFactoryConfig, -): (options?: TOpts) => ServiceFactory; + ) => PluginServiceFactoryOptions, +): ServiceFactoryCompat; export function createServiceFactory< TService, TImpl extends TService, @@ -251,23 +286,24 @@ export function createServiceFactory< TContext, TOpts extends object | undefined = undefined, >( - config: - | RootServiceFactoryConfig - | PluginServiceFactoryConfig - | ((options: TOpts) => RootServiceFactoryConfig) + options: + | RootServiceFactoryOptions + | PluginServiceFactoryOptions + | ((options: TOpts) => RootServiceFactoryOptions) | (( options: TOpts, - ) => PluginServiceFactoryConfig) - | (() => RootServiceFactoryConfig) - | (() => PluginServiceFactoryConfig), -): (options: TOpts) => ServiceFactory { - const configCallback = typeof config === 'function' ? config : () => config; + ) => PluginServiceFactoryOptions) + | (() => RootServiceFactoryOptions) + | (() => PluginServiceFactoryOptions), +): ServiceFactoryCompat { + const configCallback = + typeof options === 'function' ? options : () => options; const factory = ( - options: TOpts, + o?: TOpts, ): InternalServiceFactory => { - const anyConf = configCallback(options); + const anyConf = configCallback(o!); if (anyConf.service.scope === 'root') { - const c = anyConf as RootServiceFactoryConfig; + const c = anyConf as RootServiceFactoryOptions; return { $$type: '@backstage/BackendFeature', version: 'v1', @@ -277,7 +313,7 @@ export function createServiceFactory< factory: async (deps: TDeps) => c.factory(deps), }; } - const c = anyConf as PluginServiceFactoryConfig< + const c = anyConf as PluginServiceFactoryOptions< TService, TContext, TImpl, @@ -299,7 +335,10 @@ export function createServiceFactory< }; }; - factory.$$type = '@backstage/BackendFeatureFactory'; - - return factory; + // This constructs the `ServiceFactoryCompat` type, which is both a plain + // factory object as well as a function that can be called to construct a + // factory, potentially with options. In the future only the plain factory + // form will be supported, but for now we need to allow callers to call the + // factory too. + return Object.assign(factory, factory(undefined as TOpts)); } diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 008ae3cb11..929a38084c 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/backend-tasks +## 0.5.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.27 + +### Patch Changes + +- 083eaf9: Fix bug where ISO durations could no longer be used for schedules +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.5.26-next.0 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 2033be826c..51e8c4611e 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.26-next.0", + "version": "0.5.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 435d1f8abc..72672bba7f 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/backend-test-utils +## 0.4.5-next.0 + +### Patch Changes + +- 4e79d19: The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service. +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.0 + - @backstage/backend-app-api@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.4.4 + +### Patch Changes + +- 2f99178: The `ServiceFactoryTest.get` method was deprecated and the `ServiceFactoryTest.getSubject` should be used instead. The `getSubject` method has the same behavior, but has a better method name to indicate that the service instance returned is the subject currently being tested. +- edf5cc3: The function `isDockerDisabledForTests` is deprecated and will no longer be exported in the near future as it should only be used internally. +- b05e1e1: Service factories exported by this package have been updated to use the new service factory format that doesn't use a callback. +- fce7887: Added mock for the Root Health Service in `mockServices`. +- 906c817: Updated `startTestBackend` and `ServiceFactoryTester` to only accept plain service factory or backend feature objects, no longer supporting the callback form. This lines up with the changes to `@backstage/backend-plugin-api` and should not require any code changes. +- 95a3a0b: Rename frontend and backend `setupRequestMockHandlers` methods to `registerMswTestHooks`. +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 +- 98ccf00: Internal refactor of `mockServices.httpAuth.factory` to allow it to still be constructed with options, but without declaring options via `createServiceFactory`. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-defaults@0.4.0 + - @backstage/backend-app-api@0.8.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.4-next.1 + +### Patch Changes + +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 +- Updated dependencies + - @backstage/backend-defaults@0.3.4-next.1 + - @backstage/backend-app-api@0.7.10-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.4.3-next.0 ### Patch Changes diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index a2fb4b7674..1f93573564 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -22,7 +22,6 @@ import { EventsService } from '@backstage/plugin-events-node'; import { ExtendedHttpServer } from '@backstage/backend-app-api'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { HttpAuthService } from '@backstage/backend-plugin-api'; -import { HttpRouterFactoryOptions } from '@backstage/backend-defaults/httpRouter'; import { HttpRouterService } from '@backstage/backend-plugin-api'; import { IdentityService } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; @@ -39,6 +38,7 @@ import { RootLifecycleService } from '@backstage/backend-plugin-api'; import { RootLoggerService } from '@backstage/backend-plugin-api'; import { SchedulerService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; import { TokenManagerService } from '@backstage/backend-plugin-api'; import { UrlReaderService } from '@backstage/backend-plugin-api'; @@ -55,7 +55,7 @@ export interface CreateMockDirectoryOptions { mockOsTmpDir?: boolean; } -// @public (undocumented) +// @public @deprecated (undocumented) export function isDockerDisabledForTests(): boolean; // @public (undocumented) @@ -156,7 +156,7 @@ export namespace mockServices { // (undocumented) export namespace auth { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -165,7 +165,7 @@ export namespace mockServices { // (undocumented) export namespace cache { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -174,7 +174,7 @@ export namespace mockServices { // (undocumented) export namespace database { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -185,7 +185,7 @@ export namespace mockServices { // (undocumented) export namespace discovery { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -194,7 +194,7 @@ export namespace mockServices { // (undocumented) export namespace events { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -206,13 +206,10 @@ export namespace mockServices { }): HttpAuthService; // (undocumented) export namespace httpAuth { - const factory: ( - options?: - | { - defaultCredentials?: BackstageCredentials | undefined; - } - | undefined, - ) => ServiceFactory; + const factory: ((options?: { + defaultCredentials?: BackstageCredentials; + }) => ServiceFactory) & + ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -221,9 +218,7 @@ export namespace mockServices { // (undocumented) export namespace httpRouter { const // (undocumented) - factory: ( - options?: HttpRouterFactoryOptions | undefined, - ) => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -234,7 +229,7 @@ export namespace mockServices { // (undocumented) export namespace identity { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -243,7 +238,7 @@ export namespace mockServices { // (undocumented) export namespace lifecycle { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -252,7 +247,7 @@ export namespace mockServices { // (undocumented) export namespace logger { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -261,7 +256,7 @@ export namespace mockServices { // (undocumented) export namespace permissions { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -276,14 +271,15 @@ export namespace mockServices { data?: JsonObject; }; const // (undocumented) - factory: ( - options?: Options | undefined, - ) => ServiceFactory; + factory: ServiceFactory & + (( + options?: Options | undefined, + ) => ServiceFactory); } // (undocumented) export namespace rootHealth { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -292,9 +288,10 @@ export namespace mockServices { // (undocumented) export namespace rootHttpRouter { const // (undocumented) - factory: ( + factory: (( options?: RootHttpRouterFactoryOptions | undefined, - ) => ServiceFactory; + ) => ServiceFactory) & + ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -303,7 +300,7 @@ export namespace mockServices { // (undocumented) export namespace rootLifecycle { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -318,9 +315,10 @@ export namespace mockServices { level?: 'none' | 'error' | 'warn' | 'info' | 'debug'; }; const // (undocumented) - factory: ( - options?: Options | undefined, - ) => ServiceFactory; + factory: ServiceFactory & + (( + options?: Options | undefined, + ) => ServiceFactory); const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -329,7 +327,7 @@ export namespace mockServices { // (undocumented) export namespace scheduler { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -340,7 +338,7 @@ export namespace mockServices { // (undocumented) export namespace tokenManager { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -349,7 +347,7 @@ export namespace mockServices { // (undocumented) export namespace urlReader { const // (undocumented) - factory: () => ServiceFactory; + factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -360,7 +358,7 @@ export namespace mockServices { ): UserInfoService; // (undocumented) export namespace userInfo { - const factory: () => ServiceFactory; + const factory: ServiceFactoryCompat; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -368,14 +366,20 @@ export namespace mockServices { } } +// @public +export function registerMswTestHooks(worker: { + listen: (t: any) => void; + close: () => void; + resetHandlers: () => void; +}): void; + // @public export class ServiceFactoryTester { static from( - subject: - | ServiceFactory - | (() => ServiceFactory), + subject: ServiceFactory, options?: ServiceFactoryTesterOptions, ): ServiceFactoryTester; + // @deprecated get( ...args: 'root' extends TScope ? [] : [pluginId?: string] ): Promise; @@ -383,11 +387,14 @@ export class ServiceFactoryTester { service: ServiceRef, ...args: 'root' extends TGetScope ? [] : [pluginId?: string] ): Promise; + getSubject( + ...args: 'root' extends TScope ? [] : [pluginId?: string] + ): Promise; } // @public export interface ServiceFactoryTesterOptions { - dependencies?: Array ServiceFactory)>; + dependencies?: Array; } // @public (undocumented) @@ -401,7 +408,7 @@ export type ServiceMock = { : TService[Key]; }; -// @public +// @public @deprecated (undocumented) export function setupRequestMockHandlers(worker: { listen: (t: any) => void; close: () => void; @@ -432,9 +439,8 @@ export interface TestBackendOptions { // (undocumented) features?: Array< | BackendFeature - | (() => BackendFeature) | Promise<{ - default: BackendFeature | (() => BackendFeature); + default: BackendFeature; }> >; } diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 47f2c9c628..e562b89c84 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-test-utils", - "version": "0.4.3-next.0", + "version": "0.4.5-next.0", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" @@ -56,7 +56,7 @@ "@keyv/memcache": "^1.3.5", "@keyv/redis": "^2.5.3", "@types/keyv": "^4.2.0", - "better-sqlite3": "^9.0.0", + "better-sqlite3": "^11.0.0", "cookie": "^0.6.0", "express": "^4.17.1", "fs-extra": "^11.0.0", diff --git a/packages/backend-test-utils/src/deprecated.ts b/packages/backend-test-utils/src/deprecated.ts index fad7523367..9790580a9c 100644 --- a/packages/backend-test-utils/src/deprecated.ts +++ b/packages/backend-test-utils/src/deprecated.ts @@ -14,10 +14,32 @@ * limitations under the License. */ +import { registerMswTestHooks } from './msw'; import { CreateMockDirectoryOptions } from './filesystem'; +import { isDockerDisabledForTests as _isDockerDisabledForTests } from './util'; /** * @public * @deprecated Use `CreateMockDirectoryOptions` from `@backstage/backend-test-utils` instead. */ export type MockDirectoryOptions = CreateMockDirectoryOptions; + +/** + * @public + * @deprecated Use `registerMswTestHooks` from `@backstage/backend-test-utils` instead. + */ +export function setupRequestMockHandlers(worker: { + listen: (t: any) => void; + close: () => void; + resetHandlers: () => void; +}): void { + registerMswTestHooks(worker); +} + +/** + * @public + * @deprecated This is an internal function and will no longer be exported from this package. + */ +export function isDockerDisabledForTests(): boolean { + return _isDockerDisabledForTests(); +} diff --git a/packages/backend-test-utils/src/index.ts b/packages/backend-test-utils/src/index.ts index a9feacbacd..4124a861f4 100644 --- a/packages/backend-test-utils/src/index.ts +++ b/packages/backend-test-utils/src/index.ts @@ -26,4 +26,3 @@ export * from './database'; export * from './msw'; export * from './filesystem'; export * from './next'; -export * from './util'; diff --git a/packages/backend-test-utils/src/msw/index.ts b/packages/backend-test-utils/src/msw/index.ts index 29cbdf854a..fa6a37445f 100644 --- a/packages/backend-test-utils/src/msw/index.ts +++ b/packages/backend-test-utils/src/msw/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { setupRequestMockHandlers } from './setupRequestMockHandlers'; +export { registerMswTestHooks } from './registerMswTestHooks'; diff --git a/packages/backend-test-utils/src/msw/setupRequestMockHandlers.ts b/packages/backend-test-utils/src/msw/registerMswTestHooks.ts similarity index 94% rename from packages/backend-test-utils/src/msw/setupRequestMockHandlers.ts rename to packages/backend-test-utils/src/msw/registerMswTestHooks.ts index 625ef0e0fc..4e26c38321 100644 --- a/packages/backend-test-utils/src/msw/setupRequestMockHandlers.ts +++ b/packages/backend-test-utils/src/msw/registerMswTestHooks.ts @@ -19,7 +19,7 @@ * @public * @param worker - service worker */ -export function setupRequestMockHandlers(worker: { +export function registerMswTestHooks(worker: { listen: (t: any) => void; close: () => void; resetHandlers: () => void; diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index d56179d958..8d36fec50e 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -70,21 +70,28 @@ function createLoggerMock() { } /** @internal */ -function simpleFactory< +function simpleFactoryWithOptions< TService, TScope extends 'root' | 'plugin', TOptions extends [options?: object] = [], >( ref: ServiceRef, factory: (...options: TOptions) => TService, -): (...options: TOptions) => ServiceFactory { - return createServiceFactory((options: unknown) => ({ - service: ref as ServiceRef, - deps: {}, - async factory() { - return (factory as any)(options); - }, - })) as (...options: TOptions) => ServiceFactory; +): ServiceFactory & + ((...options: TOptions) => ServiceFactory) { + const factoryWithOptions = (...options: TOptions) => + createServiceFactory({ + service: ref as ServiceRef, + deps: {}, + async factory() { + return factory(...options); + }, + })(); + return Object.assign( + factoryWithOptions, + factoryWithOptions(...([undefined] as unknown as TOptions)), + ) as ServiceFactory & + ((...options: TOptions) => ServiceFactory); } /** @public */ @@ -134,7 +141,10 @@ export namespace mockServices { export namespace rootConfig { export type Options = { data?: JsonObject }; - export const factory = simpleFactory(coreServices.rootConfig, rootConfig); + export const factory = simpleFactoryWithOptions( + coreServices.rootConfig, + rootConfig, + ); } export function rootLogger(options?: rootLogger.Options): LoggerService { @@ -145,7 +155,10 @@ export namespace mockServices { level?: 'none' | 'error' | 'warn' | 'info' | 'debug'; }; - export const factory = simpleFactory(coreServices.rootLogger, rootLogger); + export const factory = simpleFactoryWithOptions( + coreServices.rootLogger, + rootLogger, + ); export const mock = simpleMock(coreServices.rootLogger, () => ({ child: jest.fn(), debug: jest.fn(), @@ -168,10 +181,11 @@ export namespace mockServices { }; } export namespace tokenManager { - export const factory = simpleFactory( - coreServices.tokenManager, - tokenManager, - ); + export const factory = createServiceFactory({ + service: coreServices.tokenManager, + deps: {}, + factory: () => tokenManager(), + }); export const mock = simpleMock(coreServices.tokenManager, () => ({ authenticate: jest.fn(), getToken: jest.fn(), @@ -182,7 +196,11 @@ export namespace mockServices { return new MockIdentityService(); } export namespace identity { - export const factory = simpleFactory(coreServices.identity, identity); + export const factory = createServiceFactory({ + service: coreServices.identity, + deps: {}, + factory: () => identity(), + }); export const mock = simpleMock(coreServices.identity, () => ({ getIdentity: jest.fn(), })); @@ -270,15 +288,10 @@ export namespace mockServices { ); } export namespace httpAuth { - /** - * Creates a mock service factory for the `HttpAuthService`. - * - * By default all requests without credentials are treated as requests from - * the default mock user principal. This behavior can be configured with the - * `defaultCredentials` option. - */ - export const factory = createServiceFactory( - (options?: { defaultCredentials?: BackstageCredentials }) => ({ + const factoryWithOptions = (options?: { + defaultCredentials?: BackstageCredentials; + }) => + createServiceFactory({ service: coreServices.httpAuth, deps: { plugin: coreServices.pluginMetadata }, factory: ({ plugin }) => @@ -286,7 +299,17 @@ export namespace mockServices { plugin.getId(), options?.defaultCredentials ?? mockCredentials.user(), ), - }), + })(); + /** + * Creates a mock service factory for the `HttpAuthService`. + * + * By default all requests without credentials are treated as requests from + * the default mock user principal. This behavior can be configured with the + * `defaultCredentials` option. + */ + export const factory = Object.assign( + factoryWithOptions, + factoryWithOptions(), ); export const mock = simpleMock(coreServices.httpAuth, () => ({ credentials: jest.fn(), diff --git a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.test.ts b/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.test.ts index 14dd427ecf..5970f6ea52 100644 --- a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.test.ts +++ b/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.test.ts @@ -56,34 +56,34 @@ describe('ServiceFactoryTester', () => { it('should test a root service factory', async () => { const tester = ServiceFactoryTester.from(rootFactory); - await expect(tester.get()).resolves.toBe('root'); + await expect(tester.getSubject()).resolves.toBe('root'); }); it('should test a plugin service factory', async () => { const tester = ServiceFactoryTester.from(pluginFactory); - await expect(tester.get('x')).resolves.toBe('x-plugin'); - await expect(tester.get('y')).resolves.toBe('y-plugin'); - await expect(tester.get('z')).resolves.toBe('z-plugin'); + await expect(tester.getSubject('x')).resolves.toBe('x-plugin'); + await expect(tester.getSubject('y')).resolves.toBe('y-plugin'); + await expect(tester.getSubject('z')).resolves.toBe('z-plugin'); }); it('should test a plugin service factory with root context', async () => { const tester = ServiceFactoryTester.from(sharedPluginFactory); - await expect(tester.get('x')).resolves.toBe('x-1-plugin'); - await expect(tester.get('y')).resolves.toBe('y-2-plugin'); - await expect(tester.get('y')).resolves.toBe('y-2-plugin'); - await expect(tester.get('y')).resolves.toBe('y-2-plugin'); - await expect(tester.get('z')).resolves.toBe('z-3-plugin'); + await expect(tester.getSubject('x')).resolves.toBe('x-1-plugin'); + await expect(tester.getSubject('y')).resolves.toBe('y-2-plugin'); + await expect(tester.getSubject('y')).resolves.toBe('y-2-plugin'); + await expect(tester.getSubject('y')).resolves.toBe('y-2-plugin'); + await expect(tester.getSubject('z')).resolves.toBe('z-3-plugin'); const tester2 = ServiceFactoryTester.from(sharedPluginFactory); - await expect(tester2.get('z')).resolves.toBe('z-1-plugin'); - await expect(tester2.get('y')).resolves.toBe('y-2-plugin'); - await expect(tester2.get('x')).resolves.toBe('x-3-plugin'); - await expect(tester2.get('x')).resolves.toBe('x-3-plugin'); - await expect(tester2.get('y')).resolves.toBe('y-2-plugin'); - await expect(tester2.get('z')).resolves.toBe('z-1-plugin'); + await expect(tester2.getSubject('z')).resolves.toBe('z-1-plugin'); + await expect(tester2.getSubject('y')).resolves.toBe('y-2-plugin'); + await expect(tester2.getSubject('x')).resolves.toBe('x-3-plugin'); + await expect(tester2.getSubject('x')).resolves.toBe('x-3-plugin'); + await expect(tester2.getSubject('y')).resolves.toBe('y-2-plugin'); + await expect(tester2.getSubject('z')).resolves.toBe('z-1-plugin'); }); it('should use dependencies', async () => { @@ -96,7 +96,7 @@ describe('ServiceFactoryTester', () => { { dependencies: [rootFactory, pluginFactory()] }, ); - await expect(tester.get('x')).resolves.toBe('root, x-plugin'); + await expect(tester.getSubject('x')).resolves.toBe('root, x-plugin'); }); it('should use dependencies with root context', async () => { @@ -109,11 +109,11 @@ describe('ServiceFactoryTester', () => { { dependencies: [sharedPluginFactory(), pluginFactory] }, ); - await expect(tester.get('x')).resolves.toBe('x-1-plugin, x-plugin'); - await expect(tester.get('y')).resolves.toBe('y-2-plugin, y-plugin'); - await expect(tester.get('y')).resolves.toBe('y-2-plugin, y-plugin'); - await expect(tester.get('y')).resolves.toBe('y-2-plugin, y-plugin'); - await expect(tester.get('z')).resolves.toBe('z-3-plugin, z-plugin'); + await expect(tester.getSubject('x')).resolves.toBe('x-1-plugin, x-plugin'); + await expect(tester.getSubject('y')).resolves.toBe('y-2-plugin, y-plugin'); + await expect(tester.getSubject('y')).resolves.toBe('y-2-plugin, y-plugin'); + await expect(tester.getSubject('y')).resolves.toBe('y-2-plugin, y-plugin'); + await expect(tester.getSubject('z')).resolves.toBe('z-3-plugin, z-plugin'); }); it('should prioritize the subject implementation', async () => { @@ -126,7 +126,7 @@ describe('ServiceFactoryTester', () => { { dependencies: [rootFactory] }, ); - await expect(tester.get()).resolves.toBe('other-root'); + await expect(tester.getSubject()).resolves.toBe('other-root'); }); it('should throw on missing dependencies', async () => { @@ -138,7 +138,7 @@ describe('ServiceFactoryTester', () => { }), ); - await expect(tester.get('x')).rejects.toThrow( + await expect(tester.getSubject('x')).rejects.toThrow( "Failed to instantiate service 'b' for 'x' because the following dependent services are missing: 'a'", ); }); diff --git a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts b/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts index 0555ab10a4..aea0d9d270 100644 --- a/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts +++ b/packages/backend-test-utils/src/next/wiring/ServiceFactoryTester.ts @@ -35,7 +35,7 @@ export interface ServiceFactoryTesterOptions { * If a service factory is provided for a service that already has a default * implementation, the provided factory will override the default. */ - dependencies?: Array ServiceFactory)>; + dependencies?: Array; } /** @@ -55,20 +55,15 @@ export class ServiceFactoryTester { * @returns A new tester instance for the provided subject. */ static from( - subject: - | ServiceFactory - | (() => ServiceFactory), + subject: ServiceFactory, options?: ServiceFactoryTesterOptions, ) { - const subjectFactory = typeof subject === 'function' ? subject() : subject; const registry = ServiceRegistry.create([ ...defaultServiceFactories, - ...(options?.dependencies?.map(f => - typeof f === 'function' ? f() : f, - ) ?? []), - subjectFactory, + ...(options?.dependencies ?? []), + subject, ]); - return new ServiceFactoryTester(subjectFactory.service, registry); + return new ServiceFactoryTester(subject.service, registry); } private constructor( @@ -79,6 +74,17 @@ export class ServiceFactoryTester { this.#registry = registry; } + /** + * Returns the service instance for the subject. + * + * @deprecated Use `getSubject` instead. + */ + async get( + ...args: 'root' extends TScope ? [] : [pluginId?: string] + ): Promise { + return this.getSubject(...args); + } + /** * Returns the service instance for the subject. * @@ -89,7 +95,7 @@ export class ServiceFactoryTester { * * By default the plugin ID 'test' is used. */ - async get( + async getSubject( ...args: 'root' extends TScope ? [] : [pluginId?: string] ): Promise { const [pluginId] = args; 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 ecba34290d..a01d4bebf5 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts @@ -242,6 +242,15 @@ describe('TestBackend', () => { expect(res.body).toEqual({ message: 'pong' }); }); + it('should expose health check endpoints', async () => { + const { server } = await startTestBackend({ features: [] }); + + const res = await request(server).get('/.backstage/health/v1/liveness'); + + expect(res.status).toEqual(200); + expect(res.body).toEqual({ status: 'ok' }); + }); + it('should provide extension point implementations', async () => { expect.assertions(3); diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index f23883ba3e..3d662a8c12 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -37,6 +37,7 @@ import express from 'express'; // Direct internal import to avoid duplication // eslint-disable-next-line @backstage/no-forbidden-package-imports import { InternalBackendFeature } from '@backstage/backend-plugin-api/src/wiring/types'; +import { createHealthRouter } from '@backstage/backend-defaults/rootHttpRouter'; /** @public */ export interface TestBackendOptions { @@ -48,11 +49,7 @@ export interface TestBackendOptions { ]; }, ]; - features?: Array< - | BackendFeature - | (() => BackendFeature) - | Promise<{ default: BackendFeature | (() => BackendFeature) }> - >; + features?: Array>; } /** @public */ @@ -77,6 +74,7 @@ export const defaultServiceFactories = [ mockServices.lifecycle.factory(), mockServices.logger.factory(), mockServices.permissions.factory(), + mockServices.rootHealth.factory(), mockServices.rootLifecycle.factory(), mockServices.rootLogger.factory(), mockServices.scheduler.factory(), @@ -255,15 +253,18 @@ export async function startTestBackend( config: coreServices.rootConfig, lifecycle: coreServices.rootLifecycle, rootLogger: coreServices.rootLogger, + health: coreServices.rootHealth, }, - async factory({ config, lifecycle, rootLogger }) { + async factory({ config, lifecycle, rootLogger, health }) { const router = DefaultRootHttpRouter.create(); const logger = rootLogger.child({ service: 'rootHttpRouter' }); const app = express(); const middleware = MiddlewareFactory.create({ config, logger }); + const healthRouter = createHealthRouter({ health }); + app.use(healthRouter); app.use(router.handler()); app.use(middleware.notFound()); app.use(middleware.error()); diff --git a/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts b/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts index 05eeabd91a..ca7cc95425 100644 --- a/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts +++ b/packages/backend-test-utils/src/util/isDockerDisabledForTests.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -/** @public */ export function isDockerDisabledForTests() { // If we are not running in continuous integration, the default is to skip // the (relatively heavy, long running) docker based tests. If you want to diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 6e4257b62d..6110ab5f99 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,123 @@ # example-backend +## 0.0.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/plugin-devtools-backend@0.3.9-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-app-backend@0.3.72-next.0 + - @backstage/plugin-auth-backend@0.22.10-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.0 + - @backstage/plugin-kubernetes-backend@0.18.4-next.0 + - @backstage/plugin-notifications-backend@0.3.4-next.0 + - @backstage/plugin-permission-backend@0.5.47-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + - @backstage/plugin-proxy-backend@0.5.4-next.0 + - @backstage/plugin-scaffolder-backend@1.23.1-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.0 + - @backstage/plugin-search-backend@1.5.15-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.0 + - @backstage/plugin-search-backend-node@1.2.28-next.0 + - @backstage/plugin-signals-backend@0.1.9-next.0 + - @backstage/plugin-techdocs-backend@1.10.10-next.0 + +## 0.0.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-defaults@0.4.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.19 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-scaffolder-backend@1.23.0 + - @backstage/plugin-scaffolder-backend-module-github@0.4.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-backend@0.5.46 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-techdocs-backend@1.10.9 + - @backstage/plugin-notifications-backend@0.3.3 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-search-backend@1.5.14 + - @backstage/plugin-catalog-backend@1.24.0 + - @backstage/plugin-app-backend@0.3.71 + - @backstage/plugin-auth-backend@0.22.9 + - @backstage/plugin-auth-backend-module-github-provider@0.1.19 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.8 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.5 + - @backstage/plugin-catalog-backend-module-openapi@0.1.40 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.9 + - @backstage/plugin-devtools-backend@0.3.8 + - @backstage/plugin-kubernetes-backend@0.18.3 + - @backstage/plugin-proxy-backend@0.5.3 + - @backstage/plugin-search-backend-module-catalog@0.1.28 + - @backstage/plugin-search-backend-module-explore@0.1.28 + - @backstage/plugin-search-backend-module-techdocs@0.1.27 + - @backstage/plugin-search-backend-node@1.2.27 + - @backstage/plugin-signals-backend@0.1.8 + - @backstage/catalog-model@1.5.0 + +## 0.0.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.23.0-next.2 + +## 0.0.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@1.10.9-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.4.0-next.1 + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-defaults@0.3.4-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.27-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-app-backend@0.3.71-next.1 + - @backstage/plugin-auth-backend@0.22.9-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.19-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.8-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.5-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.40-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.9-next.1 + - @backstage/plugin-devtools-backend@0.3.8-next.1 + - @backstage/plugin-kubernetes-backend@0.18.3-next.1 + - @backstage/plugin-notifications-backend@0.3.3-next.1 + - @backstage/plugin-permission-backend@0.5.46-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.19-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + - @backstage/plugin-proxy-backend@0.5.3-next.1 + - @backstage/plugin-scaffolder-backend@1.23.0-next.1 + - @backstage/plugin-search-backend@1.5.14-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.28-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.28-next.1 + - @backstage/plugin-search-backend-node@1.2.27-next.1 + - @backstage/plugin-signals-backend@0.1.8-next.1 + ## 0.0.28-next.0 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index f81701a77d..27c378e339 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.28-next.0", + "version": "0.0.29-next.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/cli-node/CHANGELOG.md b/packages/cli-node/CHANGELOG.md index dcf9073277..21995e4812 100644 --- a/packages/cli-node/CHANGELOG.md +++ b/packages/cli-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/cli-node +## 0.2.7 + +### Patch Changes + +- 133464c: Added internal metadata for the new experimental `frontend-dynamic-container` role. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.2.6 ### Patch Changes diff --git a/packages/cli-node/package.json b/packages/cli-node/package.json index 56cf50a096..c727918510 100644 --- a/packages/cli-node/package.json +++ b/packages/cli-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli-node", - "version": "0.2.6", + "version": "0.2.7", "description": "Node.js library for Backstage CLIs", "backstage": { "role": "node-library" diff --git a/packages/cli-node/src/roles/PackageRoles.ts b/packages/cli-node/src/roles/PackageRoles.ts index 401568ad6d..70b62e2479 100644 --- a/packages/cli-node/src/roles/PackageRoles.ts +++ b/packages/cli-node/src/roles/PackageRoles.ts @@ -58,6 +58,11 @@ const packageRoleInfos: PackageRoleInfo[] = [ platform: 'web', output: ['types', 'esm'], }, + { + role: 'frontend-dynamic-container' as PackageRole, // experimental + platform: 'web', + output: ['bundle'], + }, { role: 'backend-plugin', platform: 'node', diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 0e65266410..cbeb8493eb 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,85 @@ # @backstage/cli +## 0.27.0-next.0 + +### Minor Changes + +- 32a38e1: **BREAKING**: The lockfile (`yarn.lock`) dependency analysis and mutations have been removed from several commands. + + The `versions:bump` command will no longer attempt to bump and deduplicate dependencies by modifying the lockfile, it will only update `package.json` files. + + The `versions:check` command has been removed, since its only purpose was verification and mutation of the lockfile. We recommend using the `yarn dedupe` command instead, or the `yarn-deduplicate` package if you're using Yarn classic. + + The check that was built into the `package start` command has been removed, it will no longer warn about lockfile mismatches. + + The packages in the Backstage ecosystem handle package duplications much better now than when these CLI features were first introduced, so the need for these features has diminished. By removing them, we drastically reduce the integration between the Backstage CLI and Yarn, making it much easier to add support for other package managers in the future. + +### Patch Changes + +- 7eb08a6: Add frontend-dynamic-container role to eslint config factory +- fbc7819: Use ES2022 in CLI bundler +- e53074f: Updated default backend plugin to use `RootConfigService` instead of `Config`. This also removes the dependency on `@backstage/config` as it's no longer used. +- ee2b0e5: The experimental module federation build now has the ability to force the use of development versions of `react` and `react-dom` by setting the `FORCE_REACT_DEVELOPMENT` flag. +- 239dffc: Remove usage of deprecated functionality from @backstage/config-loader +- 0eedec3: Add support for dynamic plugins via the EXPERIMENTAL_MODULE_FEDERATION environment variable when running `yarn start`. +- dc4fb4f: Fix for `repo build --all` not properly detecting the experimental public entry point. +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 0.26.11 + +### Patch Changes + +- 133464c: Added experimental support for dynamic frontend plugin builds, enabled via setting `EXPERIMENTAL_MODULE_FEDERATION` for the app build, and using the `frontend-dynamic-container` package role to create a container. Both of these are experimental and will change in the future. +- e2e320c: - remove unused dependencies `winston` and `yn` from the template of backend plugins; + - update `msw` to version `2.3.1` in the template of backend plugins; + starting with v1 and switching later to v2 is tedious and not straight forward; it's easier to start with v2; +- 0540c5a: Updated the scaffolding output message for `plugin-common` in `backstage-cli`. Now, when executing `backstage-cli new` to create a new `plugin-common` package, the output message accurately reflects the action by displaying `Creating common plugin package...` instead of the previous, less accurate `Creating backend plugin...`. +- 7652db4: Only bootstrap global-agent if it's actually being used +- f0c0039: Fix issue with CLI that was preventing upgrading from 1.28 +- d228862: Update default backend plugin created by the cli to use non-deprecated error handling middleware +- da90cce: Updated dependency `esbuild` to `^0.21.0`. +- a60d73b: Fix a few minor issues with the backend template that were causing failing linting checks in the main repo. +- 0510d98: Subpath export `package.json` should be of a unique name to avoid typescript resolution issues +- 4baac0c: The `backendPlugin` and `backendModule` factory now includes a step for automatically adding the new backend plugin/module to the `index.ts` file of the backend. +- Updated dependencies + - @backstage/cli-node@0.2.7 + - @backstage/integration@1.13.0 + - @backstage/config-loader@1.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 0.26.11-next.1 + +### Patch Changes + +- 4baac0c: The `backendPlugin` and `backendModule` factory now includes a step for automatically adding the new backend plugin/module to the `index.ts` file of the backend. +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.6 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.13.0-next.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + ## 0.26.10-next.0 ### Patch Changes diff --git a/packages/cli/cli-report.md b/packages/cli/cli-report.md index 01b0f7ddec..907f0298f2 100644 --- a/packages/cli/cli-report.md +++ b/packages/cli/cli-report.md @@ -22,7 +22,6 @@ Commands: package [command] migrate [command] versions:bump [options] - versions:check [options] versions:migrate [options] clean build-workspace [options] [packages...] @@ -607,16 +606,6 @@ Options: -h, --help ``` -### `backstage-cli versions:check` - -``` -Usage: backstage-cli versions:check [options] - -Options: - --fix - -h, --help -``` - ### `backstage-cli versions:migrate` ``` diff --git a/packages/cli/config/eslint-factory.js b/packages/cli/config/eslint-factory.js index 6bc200cade..96d8478ec0 100644 --- a/packages/cli/config/eslint-factory.js +++ b/packages/cli/config/eslint-factory.js @@ -201,6 +201,7 @@ function createConfigForRole(dir, role, extraConfig = {}) { case 'frontend': case 'frontend-plugin': case 'frontend-plugin-module': + case 'frontend-dynamic-container': return createConfig(dir, { ...extraConfig, extends: [ diff --git a/packages/cli/package.json b/packages/cli/package.json index b458cf162f..0c999b8b9c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.26.10-next.0", + "version": "0.27.0-next.0", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" @@ -53,6 +53,7 @@ "@backstage/release-manifests": "workspace:^", "@backstage/types": "workspace:^", "@manypkg/get-packages": "^1.1.3", + "@module-federation/enhanced": "^0.1.19", "@octokit/graphql": "^5.0.0", "@octokit/graphql-schema": "^13.7.0", "@octokit/oauth-app": "^4.2.0", diff --git a/packages/cli/src/commands/build/buildFrontend.ts b/packages/cli/src/commands/build/buildFrontend.ts index 6d7ca2e6a9..f29e0bdd38 100644 --- a/packages/cli/src/commands/build/buildFrontend.ts +++ b/packages/cli/src/commands/build/buildFrontend.ts @@ -16,7 +16,7 @@ import fs from 'fs-extra'; import { resolve as resolvePath } from 'path'; -import { buildBundle } from '../../lib/bundler'; +import { buildBundle, getModuleFederationOptions } from '../../lib/bundler'; import { getEnvironmentParallelism } from '../../lib/parallel'; import { loadCliConfig } from '../../lib/config'; @@ -24,16 +24,22 @@ interface BuildAppOptions { targetDir: string; writeStats: boolean; configPaths: string[]; + isModuleFederationRemote?: true; } export async function buildFrontend(options: BuildAppOptions) { const { targetDir, writeStats, configPaths } = options; const { name } = await fs.readJson(resolvePath(targetDir, 'package.json')); + await buildBundle({ targetDir, entry: 'src/index', parallelism: getEnvironmentParallelism(), statsJsonEnabled: writeStats, + moduleFederation: getModuleFederationOptions( + name, + options.isModuleFederationRemote, + ), ...(await loadCliConfig({ args: configPaths, fromPackage: name, diff --git a/packages/cli/src/commands/build/command.ts b/packages/cli/src/commands/build/command.ts index f15ae27ee0..c0ad3c912b 100644 --- a/packages/cli/src/commands/build/command.ts +++ b/packages/cli/src/commands/build/command.ts @@ -22,6 +22,7 @@ import { paths } from '../../lib/paths'; import { buildFrontend } from './buildFrontend'; import { buildBackend } from './buildBackend'; import { isValidUrl } from '../../lib/urls'; +import chalk from 'chalk'; export async function command(opts: OptionValues): Promise { const role = await findRoleFromCommand(opts); @@ -49,6 +50,21 @@ export async function command(opts: OptionValues): Promise { }); } + // experimental + if ((role as string) === 'frontend-dynamic-container') { + console.log( + chalk.yellow( + `⚠️ WARNING: The 'frontend-dynamic-container' package role is experimental and will receive immediate breaking changes in the future.`, + ), + ); + return buildFrontend({ + targetDir: paths.targetDir, + configPaths: [], + writeStats: Boolean(opts.stats), + isModuleFederationRemote: true, + }); + } + const roleInfo = PackageRoles.getRoleInfo(role); const outputs = new Set(); diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 25a3a4e26c..fa4bb05333 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -396,7 +396,7 @@ export function registerCommands(program: Command) { .action(lazy(() => import('./versions/bump').then(m => m.default))); program - .command('versions:check') + .command('versions:check', { hidden: true }) .option('--fix', 'Fix any auto-fixable versioning problems') .description('Check Backstage package versioning') .action(lazy(() => import('./versions/lint').then(m => m.default))); diff --git a/packages/cli/src/commands/start/command.ts b/packages/cli/src/commands/start/command.ts index 2f2af95d85..aa46b717bb 100644 --- a/packages/cli/src/commands/start/command.ts +++ b/packages/cli/src/commands/start/command.ts @@ -15,6 +15,7 @@ */ import { OptionValues } from 'commander'; +import { PackageRole } from '@backstage/cli-node'; import { findRoleFromCommand } from '../../lib/role'; import { startBackend, startBackendPlugin } from './startBackend'; import { startFrontend } from './startFrontend'; @@ -47,6 +48,13 @@ export async function command(opts: OptionValues): Promise { case 'frontend-plugin': case 'frontend-plugin-module': return startFrontend({ entry: 'dev/index', ...options }); + case 'frontend-dynamic-container' as PackageRole: // experimental + return startFrontend({ + entry: 'src/index', + ...options, + skipOpenBrowser: true, + isModuleFederationRemote: true, + }); default: throw new Error( `Start command is not supported for package role '${role}'`, diff --git a/packages/cli/src/commands/start/startFrontend.ts b/packages/cli/src/commands/start/startFrontend.ts index a809473788..20faf4a9ff 100644 --- a/packages/cli/src/commands/start/startFrontend.ts +++ b/packages/cli/src/commands/start/startFrontend.ts @@ -14,7 +14,9 @@ * limitations under the License. */ -import { serveBundle } from '../../lib/bundler'; +import { readJson } from 'fs-extra'; +import { getModuleFederationOptions, serveBundle } from '../../lib/bundler'; +import { paths } from '../../lib/paths'; interface StartAppOptions { verifyVersions?: boolean; @@ -22,14 +24,23 @@ interface StartAppOptions { checksEnabled: boolean; configPaths: string[]; + skipOpenBrowser?: boolean; + isModuleFederationRemote?: boolean; } export async function startFrontend(options: StartAppOptions) { + const { name } = await readJson(paths.resolveTarget('package.json')); + const waitForExit = await serveBundle({ entry: options.entry, checksEnabled: options.checksEnabled, configPaths: options.configPaths, verifyVersions: options.verifyVersions, + skipOpenBrowser: options.skipOpenBrowser, + moduleFederation: getModuleFederationOptions( + name, + options.isModuleFederationRemote, + ), }); await waitForExit(); diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index 0dfe2f61c3..cc48b91fd8 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -18,15 +18,11 @@ import fs from 'fs-extra'; import { Command } from 'commander'; import * as runObj from '../../lib/run'; import bump, { bumpBackstageJsonVersion, createVersionFinder } from './bump'; -import { - setupRequestMockHandlers, - withLogCollector, -} from '@backstage/test-utils'; +import { registerMswTestHooks, withLogCollector } from '@backstage/test-utils'; import { YarnInfoInspectData } from '../../lib/versioning/packages'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; import { NotFoundError } from '@backstage/errors'; -import { Lockfile } from '../../lib/versioning/Lockfile'; import { MockDirectory, createMockDirectory, @@ -120,17 +116,6 @@ const lockfileMock = `${HEADER} version "1.0.3" `; -// This is the lockfile that we produce to unlock versions before we run yarn install -const lockfileMockResult = `${HEADER} -"@backstage/core@^1.0.5": - version "1.0.6" - dependencies: - "@backstage/core-api" "^1.0.6" - -"@backstage/theme@^1.0.0": - version "1.0.0" -`; - // Avoid flakes by comparing sorted log lines. File system access is async, which leads to the log line order being indeterministic const expectLogsToMatch = ( recievedLogs: String[], @@ -156,7 +141,7 @@ describe('bump', () => { }); const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); it('should bump backstage dependencies', async () => { mockDir.setContent({ @@ -207,11 +192,7 @@ describe('bump', () => { 'Using default pattern glob @backstage/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', - 'Checking for updates of @backstage/core-api', 'Some packages are outdated, updating', - 'unlocking @backstage/core@^1.0.3 ~> 1.0.6', - 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7', - 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7', 'bumping @backstage/core in a to ^1.0.6', 'bumping @backstage/core in b to ^1.0.6', 'bumping @backstage/theme in b to ^2.0.0', @@ -223,9 +204,8 @@ describe('bump', () => { 'Version bump complete!', ]); - expect(mockFetchPackageInfo).toHaveBeenCalledTimes(3); + expect(mockFetchPackageInfo).toHaveBeenCalledTimes(2); expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core'); - expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core-api'); expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/theme'); expect(runObj.run).toHaveBeenCalledTimes(1); @@ -235,12 +215,6 @@ describe('bump', () => { expect.any(Object), ); - const lockfileContents = await fs.readFile( - mockDir.resolve('yarn.lock'), - 'utf8', - ); - expect(lockfileContents).toBe(lockfileMockResult); - const packageA = await fs.readJson( mockDir.resolve('packages/a/package.json'), ); @@ -315,11 +289,7 @@ describe('bump', () => { 'Using default pattern glob @backstage/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', - 'Checking for updates of @backstage/core-api', 'Some packages are outdated, updating', - 'unlocking @backstage/core@^1.0.3 ~> 1.0.6', - 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7', - 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7', 'bumping @backstage/core in a to ^1.0.6', 'bumping @backstage/core in b to ^1.0.6', 'bumping @backstage/theme in b to ^2.0.0', @@ -331,9 +301,8 @@ describe('bump', () => { 'Version bump complete!', ]); - expect(mockFetchPackageInfo).toHaveBeenCalledTimes(3); + expect(mockFetchPackageInfo).toHaveBeenCalledTimes(2); expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core'); - expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core-api'); expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/theme'); expect(runObj.run).not.toHaveBeenCalledWith( @@ -342,12 +311,6 @@ describe('bump', () => { expect.any(Object), ); - const lockfileContents = await fs.readFile( - mockDir.resolve('yarn.lock'), - 'utf8', - ); - expect(lockfileContents).toBe(lockfileMockResult); - const packageA = await fs.readJson( mockDir.resolve('packages/a/package.json'), ); @@ -428,12 +391,7 @@ describe('bump', () => { 'Using default pattern glob @backstage/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', - 'Checking for updates of @backstage/theme', - 'Checking for updates of @backstage/core-api', 'Some packages are outdated, updating', - 'unlocking @backstage/core@^1.0.3 ~> 1.0.6', - 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7', - 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7', 'bumping @backstage/theme in b to ^5.0.0', 'bumping @backstage/core in b to ^1.0.6', 'bumping @backstage/core in a to ^1.0.6', @@ -446,9 +404,8 @@ describe('bump', () => { 'Version bump complete!', ]); - expect(mockFetchPackageInfo).toHaveBeenCalledTimes(2); + expect(mockFetchPackageInfo).toHaveBeenCalledTimes(1); expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core'); - expect(mockFetchPackageInfo).not.toHaveBeenCalledWith('@backstage/theme'); expect(runObj.run).toHaveBeenCalledTimes(1); expect(runObj.run).toHaveBeenCalledWith( @@ -457,12 +414,6 @@ describe('bump', () => { expect.any(Object), ); - const lockfileContents = await fs.readFile( - mockDir.resolve('yarn.lock'), - 'utf8', - ); - expect(lockfileContents).toBe(lockfileMockResult); - const packageA = await fs.readJson( mockDir.resolve('packages/a/package.json'), ); @@ -632,12 +583,7 @@ describe('bump', () => { 'Using default pattern glob @backstage/*', 'Checking for updates of @backstage/core', 'Checking for updates of @backstage/theme', - 'Checking for updates of @backstage/theme', - 'Checking for updates of @backstage/core-api', 'Some packages are outdated, updating', - 'unlocking @backstage/core@^1.0.3 ~> 1.0.6', - 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7', - 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7', 'bumping @backstage/theme in b to ^5.0.0', 'bumping @backstage/core in b to ^1.0.6', 'bumping @backstage/core in a to ^1.0.6', @@ -661,21 +607,6 @@ describe('bump', () => { "@backstage-extra/custom-two@^1.0.0": version "1.0.0" -`; - const customLockfileMockResult = `${HEADER} -"@backstage-extra/custom-two@^1.0.0": - version "1.0.0" - -"@backstage-extra/custom@^1.1.0": - version "1.1.0" - -"@backstage/core@^1.0.5": - version "1.0.6" - dependencies: - "@backstage/core-api" "^1.0.6" - -"@backstage/theme@^1.0.0": - version "1.0.0" `; mockDir.setContent({ 'yarn.lock': customLockfileMock, @@ -734,12 +665,7 @@ describe('bump', () => { 'Checking for updates of @backstage-extra/custom', 'Checking for updates of @backstage-extra/custom-two', 'Checking for updates of @backstage/theme', - 'Checking for updates of @backstage/core-api', 'Some packages are outdated, updating', - 'unlocking @backstage/core@^1.0.3 ~> 1.0.6', - 'unlocking @backstage-extra/custom@^1.0.1 ~> 1.1.0', - 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7', - 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7', 'bumping @backstage/core in a to ^1.0.6', 'bumping @backstage-extra/custom in a to ^1.1.0', 'bumping @backstage-extra/custom-two in a to ^2.0.0', @@ -757,7 +683,7 @@ describe('bump', () => { 'Version bump complete!', ]); - expect(mockFetchPackageInfo).toHaveBeenCalledTimes(5); + expect(mockFetchPackageInfo).toHaveBeenCalledTimes(4); expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/core'); expect(mockFetchPackageInfo).toHaveBeenCalledWith('@backstage/theme'); @@ -768,12 +694,6 @@ describe('bump', () => { expect.any(Object), ); - const lockfileContents = await fs.readFile( - mockDir.resolve('yarn.lock'), - 'utf8', - ); - expect(lockfileContents).toEqual(customLockfileMockResult); - const packageA = await fs.readJson( mockDir.resolve('packages/a/package.json'), ); @@ -801,7 +721,7 @@ describe('bump', () => { it('should ignore not found packages', async () => { mockDir.setContent({ - 'yarn.lock': lockfileMockResult, + 'yarn.lock': lockfileMock, 'package.json': JSON.stringify({ workspaces: { packages: ['packages/*'], @@ -851,21 +771,11 @@ describe('bump', () => { 'Checking for updates of @backstage/theme', 'Package info not found, ignoring package @backstage/core', 'Package info not found, ignoring package @backstage/theme', - 'Checking for updates of @backstage/core', - 'Checking for updates of @backstage/theme', - 'Package info not found, ignoring package @backstage/core', - 'Package info not found, ignoring package @backstage/theme', 'All Backstage packages are up to date!', ]); expect(runObj.run).toHaveBeenCalledTimes(0); - const lockfileContents = await fs.readFile( - mockDir.resolve('yarn.lock'), - 'utf8', - ); - expect(lockfileContents).toBe(lockfileMockResult); - const packageA = await fs.readJson( mockDir.resolve('packages/a/package.json'), ); @@ -886,83 +796,6 @@ describe('bump', () => { }, }); }); - - // eslint-disable-next-line jest/expect-expect - it('should log duplicates', async () => { - jest.spyOn(Lockfile.prototype, 'analyze').mockReturnValue({ - invalidRanges: [], - newVersions: [], - newRanges: [ - { - name: '@backstage/backend-app-api', - oldRange: '^1.0.0', - newRange: '^2.0.0', - oldVersion: '1.0.0', - newVersion: '2.0.0', - }, - ], - }); - mockDir.setContent({ - 'yarn.lock': `${HEADER} -"@backstage/backend-app-api@^1.0.0": - version "1.0.0" -`, - 'package.json': JSON.stringify({ - workspaces: { - packages: ['packages/*'], - }, - }), - packages: { - a: { - 'package.json': JSON.stringify({ - name: 'a', - dependencies: { - '@backstage/backend-app-api': '^1.0.0', - }, - }), - }, - }, - }); - - jest.spyOn(runObj, 'run').mockResolvedValue(undefined); - worker.use( - rest.get( - 'https://versions.backstage.io/v1/tags/main/manifest.json', - (_, res, ctx) => - res( - ctx.status(200), - ctx.json({ - packages: [ - { - name: '@backstage/backend-app-api', - version: '2.0.0', - }, - ], - }), - ), - ), - ); - const { log: logs } = await withLogCollector(['log', 'warn'], async () => { - await bump({ pattern: null, release: 'main' } as unknown as Command); - }); - expectLogsToMatch(logs, [ - 'Using default pattern glob @backstage/*', - 'Checking for updates of @backstage/backend-app-api', - 'Checking for updates of @backstage/backend-app-api', - 'Some packages are outdated, updating', - 'bumping @backstage/backend-app-api in a to ^2.0.0', - 'Running yarn install to install new versions', - 'Checking for moved packages to the @backstage-community namespace...', - '⚠️ The following packages may have breaking changes:', - ' @backstage/backend-app-api : 1.0.0 ~> 2.0.0', - ' https://github.com/backstage/backstage/blob/master/packages/backend-app-api/CHANGELOG.md', - 'Version bump complete!', - ' ⚠️ Warning! ⚠️', - ' The below package(s) have incompatible duplicate installations, likely due to a bad dependency in a plugin.', - ' You can investigate this by running `yarn why `, and report the issue to the plugin maintainers.', - ' @backstage/backend-app-api', - ]); - }); }); describe('bumpBackstageJsonVersion', () => { diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index d6d5f424ba..5b981d3890 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -24,7 +24,6 @@ import fs from 'fs-extra'; import chalk from 'chalk'; import ora from 'ora'; import semver from 'semver'; -import { minimatch } from 'minimatch'; import { OptionValues } from 'commander'; import { isError, NotFoundError } from '@backstage/errors'; import { resolve as resolvePath } from 'path'; @@ -36,7 +35,6 @@ import { Lockfile, YarnInfoInspectData, } from '../../lib/versioning'; -import { forbiddenDuplicatesFilter } from './lint'; import { BACKSTAGE_JSON } from '@backstage/cli-common'; import { runParallelWorkers } from '../../lib/parallel'; import { @@ -44,7 +42,6 @@ import { getManifestByVersion, ReleaseManifest, } from '@backstage/release-manifests'; -import { PackageGraph } from '@backstage/cli-node'; import { migrateMovedPackages } from './migrate'; function shouldUseGlobalAgent(): boolean { @@ -125,8 +122,6 @@ export default async (opts: OptionValues) => { // Next check with the package registry to see which dependency ranges we need to bump const versionBumps = new Map(); - // Track package versions that we want to remove from yarn.lock in order to trigger a bump - const unlocked = Array<{ name: string; range: string; target: string }>(); await runParallelWorkers({ parallelismFactor: 4, @@ -157,72 +152,13 @@ export default async (opts: OptionValues) => { }, }); - const filter = (name: string) => minimatch(name, pattern); - - // Check for updates of transitive backstage dependencies - await runParallelWorkers({ - parallelismFactor: 4, - items: lockfile.keys(), - async worker(name) { - // Only check @backstage packages and friends, we don't want this to do a full update of all deps - if (!filter(name)) { - return; - } - - let target: string; - try { - target = await findTargetVersion(name); - } catch (error) { - if (isError(error) && error.name === 'NotFoundError') { - console.log(`Package info not found, ignoring package ${name}`); - return; - } - throw error; - } - - for (const entry of lockfile.get(name) ?? []) { - // Ignore lockfile entries that don't satisfy the version range, since - // these can't cause the package to be locked to an older version - if (!semver.satisfies(target, entry.range)) { - continue; - } - // Unlock all entries that are within range but on the old version - unlocked.push({ name, range: entry.range, target }); - } - }, - }); - - console.log(); - // Write all discovered version bumps to package.json in this repo - if (versionBumps.size === 0 && unlocked.length === 0) { + if (versionBumps.size === 0) { console.log(chalk.green('All Backstage packages are up to date!')); } else { console.log(chalk.yellow('Some packages are outdated, updating')); console.log(); - if (unlocked.length > 0) { - const removed = new Set(); - for (const { name, range, target } of unlocked) { - // Don't bother removing lockfile entries if they're already on the correct version - const existingEntry = lockfile.get(name)?.find(e => e.range === range); - if (existingEntry?.version === target) { - continue; - } - const key = JSON.stringify({ name, range }); - if (!removed.has(key)) { - removed.add(key); - console.log( - `${chalk.magenta('unlocking')} ${name}@${chalk.yellow( - range, - )} ~> ${chalk.yellow(target)}`, - ); - lockfile.remove(name, range); - } - } - await lockfile.save(lockfilePath); - } - const breakingUpdates = new Map(); await runParallelWorkers({ parallelismFactor: 4, @@ -334,38 +270,6 @@ export default async (opts: OptionValues) => { } console.log(); - - // Finally we make sure the new lockfile doesn't have any duplicates - const dedupLockfile = await Lockfile.load(lockfilePath); - - const result = dedupLockfile.analyze({ - filter, - localPackages: PackageGraph.fromPackages( - await PackageGraph.listTargetPackages(), - ), - }); - - const forbiddenNewRanges = result.newRanges.filter(({ name }) => - forbiddenDuplicatesFilter(name), - ); - if (forbiddenNewRanges.length > 0) { - console.log(chalk.yellow(' ⚠️ Warning! ⚠️')); - console.log(); - console.log( - chalk.yellow( - ' The below package(s) have incompatible duplicate installations, likely due to a bad dependency in a plugin.', - ), - ); - console.log( - chalk.yellow( - ' You can investigate this by running `yarn why `, and report the issue to the plugin maintainers.', - ), - ); - console.log(); - for (const { name } of forbiddenNewRanges) { - console.log(chalk.yellow(` ${name}`)); - } - } }; export function createStrictVersionFinder(options: { diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts index bfb2a752a0..66b5fc04c4 100644 --- a/packages/cli/src/commands/versions/lint.ts +++ b/packages/cli/src/commands/versions/lint.ts @@ -14,116 +14,8 @@ * limitations under the License. */ -import { OptionValues } from 'commander'; -import { Lockfile } from '../../lib/versioning'; -import { paths } from '../../lib/paths'; -import partition from 'lodash/partition'; -import { PackageGraph } from '@backstage/cli-node'; - -// Packages that we try to avoid duplicates for -const INCLUDED = [/^@backstage\//]; - -export const includedFilter = (name: string) => - INCLUDED.some(pattern => pattern.test(name)); - -// Packages that are not allowed to have any duplicates -const FORBID_DUPLICATES = [/^@backstage\/\w+-app-api$/, /^@backstage\/plugin-/]; - -// There are some packages that ARE explicitly allowed to have duplicates since -// they handle that appropriately. This takes precedence over FORBID_DUPLICATES -// above. -const ALLOW_DUPLICATES = [ - /^@backstage\/core-plugin-api$/, - // Duplicates of libraries are OK - // TODO(Rugvip): Check this using package role instead - /^@backstage\/plugin-.*-react$/, - /^@backstage\/plugin-.*-node$/, - /^@backstage\/plugin-.*-common$/, -]; - -export const forbiddenDuplicatesFilter = (name: string) => - FORBID_DUPLICATES.some(pattern => pattern.test(name)) && - !ALLOW_DUPLICATES.some(pattern => pattern.test(name)); - -export default async (cmd: OptionValues) => { - const fix = Boolean(cmd.fix); - - let success = true; - - const lockfilePath = paths.resolveTargetRoot('yarn.lock'); - const lockfile = await Lockfile.load(lockfilePath); - const result = lockfile.analyze({ - filter: includedFilter, - localPackages: PackageGraph.fromPackages( - await PackageGraph.listTargetPackages(), - ), - }); - - logArray( - result.invalidRanges, - "The following packages versions are invalid and can't be analyzed:", - e => ` ${e.name} @ ${e.range}`, +export default async () => { + throw new Error( + 'This command has been removed, please consider alternatives such as `yarn dedupe` instead.', ); - - if (fix) { - lockfile.replaceVersions(result.newVersions); - await lockfile.save(lockfilePath); - } else { - const [newVersionsForbidden, newVersionsAllowed] = partition( - result.newVersions, - ({ name }) => forbiddenDuplicatesFilter(name), - ); - if (newVersionsForbidden.length && !fix) { - success = false; - } - - logArray( - newVersionsForbidden, - 'The following packages must be deduplicated, this can be done automatically with --fix', - e => - ` ${e.name} @ ${e.range} bumped from ${e.oldVersion} to ${e.newVersion}`, - ); - logArray( - newVersionsAllowed, - 'The following packages can be deduplicated, this can be done automatically with --fix', - e => - ` ${e.name} @ ${e.range} bumped from ${e.oldVersion} to ${e.newVersion}`, - ); - } - - const [newRangesForbidden, newRangesAllowed] = partition( - result.newRanges, - ({ name }) => forbiddenDuplicatesFilter(name), - ); - if (newRangesForbidden.length) { - success = false; - } - - logArray( - newRangesForbidden, - 'The following packages must be deduplicated by updating dependencies in package.json', - e => ` ${e.name} @ ${e.oldRange} should be changed to ${e.newRange}`, - ); - logArray( - newRangesAllowed, - 'The following packages can be deduplicated by updating dependencies in package.json', - e => ` ${e.name} @ ${e.oldRange} should be changed to ${e.newRange}`, - ); - - if (!success) { - throw new Error('Failed versioning check'); - } }; - -function logArray(arr: T[], header: string, each: (item: T) => string) { - if (arr.length === 0) { - return; - } - - console.log(header); - console.log(); - for (const e of arr) { - console.log(each(e)); - } - console.log(); -} diff --git a/packages/cli/src/lib/bundler/bundle.ts b/packages/cli/src/lib/bundler/bundle.ts index 232657a3f2..0aa91a90a6 100644 --- a/packages/cli/src/lib/bundler/bundle.ts +++ b/packages/cli/src/lib/bundler/bundle.ts @@ -42,41 +42,50 @@ export async function buildBundle(options: BuildOptions) { const paths = resolveBundlingPaths(options); const publicPaths = await resolveOptionalBundlingPaths({ + targetDir: options.targetDir, entry: 'src/index-public-experimental', dist: 'dist/public', }); - const detectedModulesEntryPoint = await createDetectedModulesEntryPoint({ - config: options.fullConfig, - targetPath: paths.targetPath, - }); - const commonConfigOptions = { ...options, checksEnabled: false, isDev: false, getFrontendAppConfigs: () => options.frontendAppConfigs, }; - const configs = [ - await createConfig(paths, { - ...commonConfigOptions, - additionalEntryPoints: detectedModulesEntryPoint, - appMode: publicPaths ? 'protected' : 'public', - }), - ]; - if (publicPaths) { - console.log( - chalk.yellow( - `⚠️ WARNING: The app /public entry point is an experimental feature that may receive immediate breaking changes.`, - ), - ); + const configs = []; + + if (options.moduleFederation?.mode === 'remote') { + // Package detection is disabled for remote bundles + configs.push(await createConfig(paths, commonConfigOptions)); + } else { + const detectedModulesEntryPoint = await createDetectedModulesEntryPoint({ + config: options.fullConfig, + targetPath: paths.targetPath, + }); + configs.push( - await createConfig(publicPaths, { + await createConfig(paths, { ...commonConfigOptions, - appMode: 'public', + additionalEntryPoints: detectedModulesEntryPoint, + appMode: publicPaths ? 'protected' : 'public', }), ); + + if (publicPaths) { + console.log( + chalk.yellow( + `⚠️ WARNING: The app /public entry point is an experimental feature that may receive immediate breaking changes.`, + ), + ); + configs.push( + await createConfig(publicPaths, { + ...commonConfigOptions, + appMode: 'public', + }), + ); + } } const isCi = yn(process.env.CI, { default: false }); diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 53db7f3597..42cdae24e3 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -14,8 +14,13 @@ * limitations under the License. */ -import { BackendBundlingOptions, BundlingOptions } from './types'; -import { posix as posixPath, resolve as resolvePath } from 'path'; +import { + BackendBundlingOptions, + BundlingOptions, + ModuleFederationOptions, +} from './types'; +import { posix as posixPath, resolve as resolvePath, dirname } from 'path'; +import chalk from 'chalk'; import webpack, { ProvidePlugin } from 'webpack'; import { BackstagePackage } from '@backstage/cli-node'; @@ -24,15 +29,17 @@ import { Config } from '@backstage/config'; import ESLintPlugin from 'eslint-webpack-plugin'; import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'; import HtmlWebpackPlugin from 'html-webpack-plugin'; +import { ModuleFederationPlugin } from '@module-federation/enhanced/webpack'; import { LinkedPackageResolvePlugin } from './LinkedPackageResolvePlugin'; import ModuleScopePlugin from 'react-dev-utils/ModuleScopePlugin'; import { RunScriptWebpackPlugin } from 'run-script-webpack-plugin'; +import ReactRefreshPlugin from '@pmmmwh/react-refresh-webpack-plugin'; import { paths as cliPaths } from '../../lib/paths'; import fs from 'fs-extra'; import { getPackages } from '@manypkg/get-packages'; import { isChildPath } from '@backstage/cli-common'; import nodeExternals from 'webpack-node-externals'; -import { optimization } from './optimization'; +import { optimization as optimizationConfig } from './optimization'; import pickBy from 'lodash/pickBy'; import { readEntryPoints } from '../entryPoints'; import { runPlain } from '../run'; @@ -43,15 +50,42 @@ import { hasReactDomClient } from './hasReactDomClient'; const BUILD_CACHE_ENV_VAR = 'BACKSTAGE_CLI_EXPERIMENTAL_BUILD_CACHE'; -export function resolveBaseUrl(config: Config): URL { +export function resolveBaseUrl( + config: Config, + moduleFederation?: ModuleFederationOptions, +): URL { const baseUrl = config.getOptionalString('app.baseUrl'); + + const defaultBaseUrl = + moduleFederation?.mode === 'remote' + ? `http://localhost:${process.env.PORT ?? '3000'}` + : 'http://localhost:3000'; + try { - return new URL(baseUrl ?? '/', 'http://localhost:3000'); + return new URL(baseUrl ?? '/', defaultBaseUrl); } catch (error) { throw new Error(`Invalid app.baseUrl, ${error}`); } } +export function resolveEndpoint( + config: Config, + moduleFederation?: ModuleFederationOptions, +): { + host: string; + port: number; +} { + const url = resolveBaseUrl(config, moduleFederation); + + return { + host: config.getOptionalString('app.listen.host') ?? url.hostname, + port: + config.getOptionalNumber('app.listen.port') ?? + Number(url.port) ?? + (url.protocol === 'https:' ? 443 : 80), + }; +} + async function readBuildInfo() { const timestamp = Date.now(); @@ -92,7 +126,13 @@ export async function createConfig( paths: BundlingPaths, options: BundlingOptions, ): Promise { - const { checksEnabled, isDev, frontendConfig, publicSubPath = '' } = options; + const { + checksEnabled, + isDev, + frontendConfig, + moduleFederation, + publicSubPath = '', + } = options; const { plugins, loaders } = transforms(options); // Any package that is part of the monorepo but outside the monorepo root dir need @@ -100,12 +140,29 @@ export async function createConfig( const { packages } = await getPackages(cliPaths.targetDir); const externalPkgs = packages.filter(p => !isChildPath(paths.root, p.dir)); - const validBaseUrl = resolveBaseUrl(frontendConfig); + const validBaseUrl = resolveBaseUrl(frontendConfig, moduleFederation); let publicPath = validBaseUrl.pathname.replace(/\/$/, ''); if (publicSubPath) { publicPath = `${publicPath}${publicSubPath}`.replace('//', '/'); } + if (isDev) { + const { host, port } = resolveEndpoint( + options.frontendConfig, + options.moduleFederation, + ); + + plugins.push( + new ReactRefreshPlugin({ + overlay: { + sockProtocol: 'ws', + sockHost: host, + sockPort: port, + }, + }), + ); + } + if (checksEnabled) { plugins.push( new ForkTsCheckerWebpackPlugin({ @@ -128,18 +185,83 @@ export async function createConfig( }), ); - plugins.push( - new HtmlWebpackPlugin({ - meta: { - 'backstage-app-mode': options?.appMode ?? 'public', - }, - template: paths.targetHtml, - templateParameters: { - publicPath, - config: frontendConfig, - }, - }), - ); + if (options.moduleFederation?.mode !== 'remote') { + plugins.push( + new HtmlWebpackPlugin({ + meta: { + 'backstage-app-mode': options?.appMode ?? 'public', + }, + template: paths.targetHtml, + templateParameters: { + publicPath, + config: frontendConfig, + }, + }), + ); + } + + if (options.moduleFederation) { + const isRemote = options.moduleFederation?.mode === 'remote'; + + plugins.push( + new ModuleFederationPlugin({ + ...(isRemote && { + filename: 'remoteEntry.js', + exposes: { + '.': paths.targetEntry, + }, + }), + name: options.moduleFederation.name, + runtime: false, + shared: { + // React + react: { + singleton: true, + requiredVersion: '*', + eager: !isRemote, + }, + 'react-dom': { + singleton: true, + requiredVersion: '*', + eager: !isRemote, + }, + // React Router + 'react-router': { + singleton: true, + requiredVersion: '*', + eager: !isRemote, + }, + 'react-router-dom': { + singleton: true, + requiredVersion: '*', + eager: !isRemote, + }, + // MUI v4 + '@material-ui/core/styles': { + singleton: true, + requiredVersion: '*', + eager: !isRemote, + }, + '@material-ui/styles': { + singleton: true, + requiredVersion: '*', + eager: !isRemote, + }, + // MUI v5 + '@mui/material/styles/': { + singleton: true, + requiredVersion: '*', + eager: !isRemote, + }, + '@emotion/react': { + singleton: true, + requiredVersion: '*', + eager: !isRemote, + }, + }, + }), + ); + } const buildInfo = await readBuildInfo(); plugins.push( @@ -166,12 +288,53 @@ export async function createConfig( require.resolve('react-refresh'), ]; + const mode = isDev ? 'development' : 'production'; + const optimization = optimizationConfig(options); + + if ( + mode === 'production' && + process.env.EXPERIMENTAL_MODULE_FEDERATION && + process.env.FORCE_REACT_DEVELOPMENT + ) { + console.log( + chalk.yellow( + `⚠️ WARNING: Forcing react and react-dom into development mode. This build should not be used in production.`, + ), + ); + + const reactPackageDirs = [ + `${dirname(require.resolve('react/package.json'))}/`, + `${dirname(require.resolve('react-dom/package.json'))}/`, + ]; + + // Don't define process.env.NODE_ENV with value matching config.mode. If we + // don't set this to false, webpack will define the value of + // process.env.NODE_ENV for us, and the definition below will be ignored. + optimization.nodeEnv = false; + + // Instead, provide a custom definition which always uses "development" if + // the module is part of `react` or `react-dom`, and `config.mode` otherwise. + plugins.push( + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': webpack.DefinePlugin.runtimeValue( + ({ module }) => { + if (reactPackageDirs.some(val => module.resource.startsWith(val))) { + return '"development"'; + } + + return `"${mode}"`; + }, + ), + }), + ); + } + const withCache = yn(process.env[BUILD_CACHE_ENV_VAR], { default: false }); return { - mode: isDev ? 'development' : 'production', + mode, profile: false, - optimization: optimization(options), + optimization, bail: false, performance: { hints: false, // we check the gzip size instead @@ -211,8 +374,10 @@ export async function createConfig( rules: loaders, }, output: { + uniqueName: options.moduleFederation?.name, path: paths.targetDist, - publicPath: `${publicPath}/`, + publicPath: + options.moduleFederation?.mode === 'remote' ? 'auto' : `${publicPath}/`, filename: isDev ? '[name].js' : 'static/[name].[fullhash:8].js', chunkFilename: isDev ? '[name].chunk.js' diff --git a/packages/cli/src/lib/bundler/index.ts b/packages/cli/src/lib/bundler/index.ts index a3b584efc4..2219784dc9 100644 --- a/packages/cli/src/lib/bundler/index.ts +++ b/packages/cli/src/lib/bundler/index.ts @@ -16,4 +16,5 @@ export { serveBackend } from './backend'; export { buildBundle } from './bundle'; +export { getModuleFederationOptions } from './moduleFederation'; export { serveBundle } from './server'; diff --git a/packages/cli/src/lib/bundler/moduleFederation.ts b/packages/cli/src/lib/bundler/moduleFederation.ts new file mode 100644 index 0000000000..ae3f9ab048 --- /dev/null +++ b/packages/cli/src/lib/bundler/moduleFederation.ts @@ -0,0 +1,44 @@ +/* + * 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 chalk from 'chalk'; +import { ModuleFederationOptions } from './types'; + +export function getModuleFederationOptions( + name: string, + isModuleFederationRemote?: boolean, +): ModuleFederationOptions | undefined { + if ( + !isModuleFederationRemote && + !process.env.EXPERIMENTAL_MODULE_FEDERATION + ) { + return undefined; + } + + console.log( + chalk.yellow( + `⚠️ WARNING: Module federation is experimental and will receive immediate breaking changes in the future.`, + ), + ); + + return { + mode: isModuleFederationRemote ? 'remote' : 'host', + // The default output mode requires the name to be a usable as a code + // symbol, there might be better options here but for now we need to + // sanitize the name. + name: name.replaceAll('@', '').replaceAll('/', '__').replaceAll('-', '_'), + }; +} diff --git a/packages/cli/src/lib/bundler/optimization.ts b/packages/cli/src/lib/bundler/optimization.ts index 88a0343099..cf240b4791 100644 --- a/packages/cli/src/lib/bundler/optimization.ts +++ b/packages/cli/src/lib/bundler/optimization.ts @@ -28,8 +28,15 @@ export const optimization = ( minimize: !isDev, minimizer: [ new EsbuildPlugin({ - target: 'es2019', + target: 'ES2022', format: 'iife', + exclude: 'remoteEntry.js', + }), + // Avoid iife wrapping of module federation remote entry as it breaks the variable assignment + new EsbuildPlugin({ + target: 'ES2022', + format: undefined, + include: 'remoteEntry.js', }), ], runtimeChunk: 'single', diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 1da15cd37e..259f3263eb 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -14,23 +14,16 @@ * limitations under the License. */ -import { PackageGraph } from '@backstage/cli-node'; import { AppConfig } from '@backstage/config'; import chalk from 'chalk'; import fs from 'fs-extra'; -import uniq from 'lodash/uniq'; import openBrowser from 'react-dev-utils/openBrowser'; import webpack from 'webpack'; import WebpackDevServer from 'webpack-dev-server'; -import { - forbiddenDuplicatesFilter, - includedFilter, -} from '../../commands/versions/lint'; import { paths as libPaths } from '../../lib/paths'; import { loadCliConfig } from '../config'; -import { Lockfile } from '../versioning'; -import { createConfig, resolveBaseUrl } from './config'; +import { createConfig, resolveBaseUrl, resolveEndpoint } from './config'; import { createDetectedModulesEntryPoint } from './packageDetection'; import { resolveBundlingPaths, resolveOptionalBundlingPaths } from './paths'; import { ServeOptions } from './types'; @@ -41,38 +34,6 @@ export async function serveBundle(options: ServeOptions) { const targetPkg = await fs.readJson(paths.targetPackageJson); if (options.verifyVersions) { - const lockfile = await Lockfile.load( - libPaths.resolveTargetRoot('yarn.lock'), - ); - const result = lockfile.analyze({ - filter: includedFilter, - localPackages: PackageGraph.fromPackages( - await PackageGraph.listTargetPackages(), - ), - }); - const problemPackages = [...result.newVersions, ...result.newRanges] - .map(({ name }) => name) - .filter(forbiddenDuplicatesFilter); - - if (problemPackages.length > 1) { - console.log( - chalk.yellow( - `⚠️ Some of the following packages may be outdated or have duplicate installations: - - ${uniq(problemPackages).join(', ')} - `, - ), - ); - console.log( - chalk.yellow( - `⚠️ This can be resolved using the following command: - - yarn backstage-cli versions:check --fix - `, - ), - ); - } - if ( targetPkg.dependencies?.['react-router']?.includes('beta') || targetPkg.dependencies?.['react-router-dom']?.includes('beta') @@ -130,14 +91,11 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be } const { frontendConfig, fullConfig } = cliConfig; - const url = resolveBaseUrl(frontendConfig); - - const host = - frontendConfig.getOptionalString('app.listen.host') || url.hostname; - const port = - frontendConfig.getOptionalNumber('app.listen.port') || - Number(url.port) || - (url.protocol === 'https:' ? 443 : 80); + const url = resolveBaseUrl(frontendConfig, options.moduleFederation); + const { host, port } = resolveEndpoint( + frontendConfig, + options.moduleFederation, + ); const detectedModulesEntryPoint = await createDetectedModulesEntryPoint({ config: fullConfig, @@ -162,6 +120,7 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be const config = await createConfig(paths, { ...commonConfigOptions, additionalEntryPoints: detectedModulesEntryPoint, + moduleFederation: options.moduleFederation, }); if (process.env.EXPERIMENTAL_VITE) { @@ -232,14 +191,17 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be directory: paths.targetPublic, } : undefined, - historyApiFallback: { - // Paths with dots should still use the history fallback. - // See https://github.com/facebookincubator/create-react-app/issues/387. - disableDotRule: true, + historyApiFallback: + options.moduleFederation?.mode === 'remote' + ? false + : { + // Paths with dots should still use the history fallback. + // See https://github.com/facebookincubator/create-react-app/issues/387. + disableDotRule: true, - // The index needs to be rewritten relative to the new public path, including subroutes. - index: `${config.output?.publicPath}index.html`, - }, + // The index needs to be rewritten relative to the new public path, including subroutes. + index: `${config.output?.publicPath}index.html`, + }, server: url.protocol === 'https:' ? { @@ -256,7 +218,13 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be // When the dev server is behind a proxy, the host and public hostname differ allowedHosts: [url.hostname], client: { - webSocketURL: 'auto://0.0.0.0:0/ws', + webSocketURL: { hostname: host, port }, + }, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': + 'X-Requested-With, content-type, Authorization', }, }, compiler, @@ -278,7 +246,9 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be } }); - openBrowser(url.href); + if (!options.skipOpenBrowser) { + openBrowser(url.href); + } const waitForExit = async () => { for (const signal of ['SIGINT', 'SIGTERM'] as const) { diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts index da1eec4e11..302b2fbe68 100644 --- a/packages/cli/src/lib/bundler/transforms.ts +++ b/packages/cli/src/lib/bundler/transforms.ts @@ -17,7 +17,6 @@ import { ModuleOptions, WebpackPluginInstance } from 'webpack'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import { svgrTemplate } from '../svgrTemplate'; -import ReactRefreshPlugin from '@pmmmwh/react-refresh-webpack-plugin'; type Transforms = { loaders: ModuleOptions['rules']; @@ -58,7 +57,7 @@ export const transforms = (options: TransformOptions): Transforms => { loader: require.resolve('swc-loader'), options: { jsc: { - target: 'es2019', + target: 'es2022', externalHelpers: !isBackend, parser: { syntax: 'typescript', @@ -86,7 +85,7 @@ export const transforms = (options: TransformOptions): Transforms => { loader: require.resolve('swc-loader'), options: { jsc: { - target: 'es2019', + target: 'es2022', externalHelpers: !isBackend, parser: { syntax: 'ecmascript', @@ -119,7 +118,7 @@ export const transforms = (options: TransformOptions): Transforms => { loader: require.resolve('swc-loader'), options: { jsc: { - target: 'es2019', + target: 'es2022', externalHelpers: !isBackend, parser: { syntax: 'ecmascript', @@ -193,15 +192,7 @@ export const transforms = (options: TransformOptions): Transforms => { const plugins = new Array(); - if (isDev) { - if (!isBackend) { - plugins.push( - new ReactRefreshPlugin({ - overlay: { sockProtocol: 'ws' }, - }), - ); - } - } else { + if (!isDev) { plugins.push( new MiniCssExtractPlugin({ filename: 'static/[name].[contenthash:8].css', diff --git a/packages/cli/src/lib/bundler/types.ts b/packages/cli/src/lib/bundler/types.ts index 85a641465f..f8e228c5c4 100644 --- a/packages/cli/src/lib/bundler/types.ts +++ b/packages/cli/src/lib/bundler/types.ts @@ -18,6 +18,13 @@ import { AppConfig, Config } from '@backstage/config'; import { BundlingPathsOptions } from './paths'; import { ConfigSchema } from '@backstage/config-loader'; +export type ModuleFederationOptions = { + // Unique name for this module federation bundle + name: string; + // Whether this is a host or remote bundle + mode: 'host' | 'remote'; +}; + export type BundlingOptions = { checksEnabled: boolean; isDev: boolean; @@ -29,12 +36,15 @@ export type BundlingOptions = { publicSubPath?: string; // Mode that the app is running in, 'protected' or 'public', default is 'public' appMode?: string; + moduleFederation?: ModuleFederationOptions; }; export type ServeOptions = BundlingPathsOptions & { checksEnabled: boolean; configPaths: string[]; verifyVersions?: boolean; + skipOpenBrowser?: boolean; + moduleFederation?: ModuleFederationOptions; }; export type BuildOptions = BundlingPathsOptions & { @@ -46,6 +56,7 @@ export type BuildOptions = BundlingPathsOptions & { frontendConfig: Config; frontendAppConfigs: AppConfig[]; fullConfig: Config; + moduleFederation?: ModuleFederationOptions; }; export type BackendBundlingOptions = { diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index 0c657b640e..49cce1f1ed 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -14,14 +14,9 @@ * limitations under the License. */ -import { - ConfigTarget, - loadConfig, - loadConfigSchema, -} from '@backstage/config-loader'; +import { ConfigSources, loadConfigSchema } from '@backstage/config-loader'; import { AppConfig, ConfigReader } from '@backstage/config'; import { paths } from './paths'; -import { isValidUrl } from './urls'; import { getPackages } from '@manypkg/get-packages'; import { PackageGraph } from '@backstage/cli-node'; @@ -37,13 +32,6 @@ type Options = { }; export async function loadCliConfig(options: Options) { - const configTargets: ConfigTarget[] = []; - options.args.forEach(arg => { - if (!isValidUrl(arg)) { - configTargets.push({ path: paths.resolveTarget(arg) }); - } - }); - // Consider all packages in the monorepo when loading in config const { packages } = await getPackages(paths.targetDir); @@ -75,32 +63,62 @@ export async function loadCliConfig(options: Options) { noUndeclaredProperties: options.strict, }); - const { appConfigs } = await loadConfig({ - experimentalEnvFunc: options.mockEnv + const source = ConfigSources.default({ + allowMissingDefaultConfig: true, + substitutionFunc: options.mockEnv ? async name => process.env[name] || 'x' : undefined, - configRoot: paths.targetRoot, - configTargets: configTargets, - watch: options.watch && { - onChange(newAppConfigs) { - const newFrontendAppConfigs = schema.process(newAppConfigs, { - visibility: options.fullVisibility - ? ['frontend', 'backend', 'secret'] - : ['frontend'], - withFilteredKeys: options.withFilteredKeys, - withDeprecatedKeys: options.withDeprecatedKeys, - ignoreSchemaErrors: !options.strict, - }); - options.watch?.(newFrontendAppConfigs); - }, - }, + watch: Boolean(options.watch), + rootDir: paths.targetRoot, + argv: options.args, }); + const appConfigs = await new Promise((resolve, reject) => { + async function loadConfigReaderLoop() { + let loaded = false; + + try { + const abortController = new AbortController(); + for await (const { configs } of source.readConfigData({ + signal: abortController.signal, + })) { + if (loaded) { + const newFrontendAppConfigs = schema.process(configs, { + visibility: options.fullVisibility + ? ['frontend', 'backend', 'secret'] + : ['frontend'], + withFilteredKeys: options.withFilteredKeys, + withDeprecatedKeys: options.withDeprecatedKeys, + ignoreSchemaErrors: !options.strict, + }); + options.watch?.(newFrontendAppConfigs); + } else { + resolve(configs); + loaded = true; + + if (!options.watch) { + abortController.abort(); + } + } + } + } catch (error) { + if (loaded) { + console.error(`Failed to reload configuration, ${error}`); + } else { + reject(error); + } + } + } + loadConfigReaderLoop(); + }); + + const configurationLoadedMessage = appConfigs.length + ? `Loaded config from ${appConfigs.map(c => c.context).join(', ')}` + : `No configuration files found, running without config`; + // printing to stderr to not clobber stdout in case the cli command // outputs structured data (e.g. as config:schema does) - process.stderr.write( - `Loaded config from ${appConfigs.map(c => c.context).join(', ')}\n`, - ); + process.stderr.write(`${configurationLoadedMessage}\n`); try { const frontendAppConfigs = schema.process(appConfigs, { diff --git a/packages/cli/src/lib/new/factories/backendModule.test.ts b/packages/cli/src/lib/new/factories/backendModule.test.ts index 78ef5f96bb..2d6a4c9c83 100644 --- a/packages/cli/src/lib/new/factories/backendModule.test.ts +++ b/packages/cli/src/lib/new/factories/backendModule.test.ts @@ -26,6 +26,14 @@ import { import { backendModule } from './backendModule'; import { createMockDirectory } from '@backstage/backend-test-utils'; +const backendIndexTsContent = ` +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.start(); +`; + describe('backendModule factory', () => { const mockDir = createMockDirectory(); @@ -44,6 +52,9 @@ describe('backendModule factory', () => { packages: { backend: { 'package.json': JSON.stringify({}), + src: { + 'index.ts': backendIndexTsContent, + }, }, }, plugins: {}, @@ -86,8 +97,20 @@ describe('backendModule factory', () => { 'Installing:', `moving plugins${sep}test-backend-module-tester-two`, 'backend adding dependency', + 'backend adding module', ]); + await expect( + fs.readFile(mockDir.resolve('packages/backend/src/index.ts'), 'utf8'), + ).resolves.toBe(` +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.add(import('backstage-plugin-test-backend-module-tester-two')); +backend.start(); +`); + await expect( fs.readJson(mockDir.resolve('packages/backend/package.json')), ).resolves.toEqual({ diff --git a/packages/cli/src/lib/new/factories/backendModule.ts b/packages/cli/src/lib/new/factories/backendModule.ts index 698146a229..67860b812e 100644 --- a/packages/cli/src/lib/new/factories/backendModule.ts +++ b/packages/cli/src/lib/new/factories/backendModule.ts @@ -20,7 +20,7 @@ import camelCase from 'lodash/camelCase'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; import { CreateContext, createFactory } from '../types'; -import { addPackageDependency, Task } from '../../tasks'; +import { addPackageDependency, addToBackend, Task } from '../../tasks'; import { moduleIdIdPrompt, ownerPrompt, @@ -91,6 +91,10 @@ export const backendModule = createFactory({ }); } + await addToBackend(name, { + type: 'module', + }); + if (options.owner) { await addCodeownersEntry(`/plugins/${dirName}`, options.owner); } diff --git a/packages/cli/src/lib/new/factories/backendPlugin.test.ts b/packages/cli/src/lib/new/factories/backendPlugin.test.ts index c5930592a3..f6eeb8cf33 100644 --- a/packages/cli/src/lib/new/factories/backendPlugin.test.ts +++ b/packages/cli/src/lib/new/factories/backendPlugin.test.ts @@ -26,6 +26,14 @@ import { import { backendPlugin } from './backendPlugin'; import { createMockDirectory } from '@backstage/backend-test-utils'; +const backendIndexTsContent = ` +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.start(); +`; + describe('backendPlugin factory', () => { const mockDir = createMockDirectory(); @@ -44,6 +52,9 @@ describe('backendPlugin factory', () => { packages: { backend: { 'package.json': JSON.stringify({}), + src: { + 'index.ts': backendIndexTsContent, + }, }, }, plugins: {}, @@ -89,6 +100,7 @@ describe('backendPlugin factory', () => { 'Installing:', `moving plugins${sep}test-backend`, 'backend adding dependency', + 'backend adding plugin', ]); await expect( @@ -99,6 +111,17 @@ describe('backendPlugin factory', () => { }, }); + await expect( + fs.readFile(mockDir.resolve('packages/backend/src/index.ts'), 'utf8'), + ).resolves.toBe(` +import { createBackend } from '@backstage/backend-defaults'; + +const backend = createBackend(); + +backend.add(import('backstage-plugin-test-backend')); +backend.start(); +`); + expect(Task.forCommand).toHaveBeenCalledTimes(2); expect(Task.forCommand).toHaveBeenCalledWith('yarn install', { cwd: mockDir.resolve('plugins/test-backend'), diff --git a/packages/cli/src/lib/new/factories/backendPlugin.ts b/packages/cli/src/lib/new/factories/backendPlugin.ts index 09ea98fbe7..fc77896461 100644 --- a/packages/cli/src/lib/new/factories/backendPlugin.ts +++ b/packages/cli/src/lib/new/factories/backendPlugin.ts @@ -20,7 +20,7 @@ import camelCase from 'lodash/camelCase'; import { paths } from '../../paths'; import { addCodeownersEntry, getCodeownersFilePath } from '../../codeowners'; import { CreateContext, createFactory } from '../types'; -import { addPackageDependency, Task } from '../../tasks'; +import { addPackageDependency, addToBackend, Task } from '../../tasks'; import { ownerPrompt, pluginIdPrompt } from './common/prompts'; import { executePluginPackageTemplate } from './common/tasks'; import { resolvePackageName } from './common/util'; @@ -78,6 +78,10 @@ export const backendPlugin = createFactory({ }, ); }); + + await addToBackend(name, { + type: 'plugin', + }); } if (options.owner) { diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/lib/tasks.ts index 851f962c62..ffff2892fb 100644 --- a/packages/cli/src/lib/tasks.ts +++ b/packages/cli/src/lib/tasks.ts @@ -23,6 +23,7 @@ import { basename, dirname } from 'path'; import recursive from 'recursive-readdir'; import { exec as execCb } from 'child_process'; import { assertError } from '@backstage/errors'; +import { paths } from './paths'; const exec = promisify(execCb); @@ -196,3 +197,37 @@ export async function addPackageDependency( throw new Error(`Failed to add package dependencies, ${error}`); } } + +export async function addToBackend( + name: string, + options: { + type: 'plugin' | 'module'; + }, +) { + if (await fs.pathExists(paths.resolveTargetRoot('packages/backend'))) { + await Task.forItem('backend', `adding ${options.type}`, async () => { + const backendFilePath = paths.resolveTargetRoot( + 'packages/backend/src/index.ts', + ); + if (!(await fs.pathExists(backendFilePath))) { + return; + } + + const content = await fs.readFile(backendFilePath, 'utf8'); + const lines = content.split('\n'); + const backendAddLine = `backend.add(import('${name}'));`; + + const backendStartIndex = lines.findIndex(line => + line.match(/backend.start/), + ); + + if (backendStartIndex !== -1) { + const [indentation] = lines[backendStartIndex].match(/^\s*/)!; + lines.splice(backendStartIndex, 0, `${indentation}${backendAddLine}`); + + const newContent = lines.join('\n'); + await fs.writeFile(backendFilePath, newContent, 'utf8'); + } + }); + } +} diff --git a/packages/cli/src/lib/versioning/Lockfile.test.ts b/packages/cli/src/lib/versioning/Lockfile.test.ts index a4267acdb7..d6996b9143 100644 --- a/packages/cli/src/lib/versioning/Lockfile.test.ts +++ b/packages/cli/src/lib/versioning/Lockfile.test.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import fs from 'fs-extra'; -import { BackstagePackage } from '@backstage/cli-node'; import { Lockfile } from './Lockfile'; import { createMockDirectory } from '@backstage/backend-test-utils'; @@ -47,34 +45,6 @@ b@^2: version "2.0.0" `; -const mockADedup = `${LEGACY_HEADER} -a@^1: - version "1.0.1" - resolved "https://my-registry/a-1.0.01.tgz#abc123" - integrity sha512-xyz - dependencies: - b "^2" - -b@2.0.x, b@^2: - version "2.0.1" -`; - -const mockB = `${LEGACY_HEADER} -"@s/a@*", "@s/a@1 || 2", "@s/a@^1": - version "1.0.1" - -"@s/a@^2.0.x": - version "2.0.0" -`; - -const mockBDedup = `${LEGACY_HEADER} -"@s/a@*", "@s/a@1 || 2", "@s/a@^2.0.x": - version "2.0.0" - -"@s/a@^1": - version "1.0.1" -`; - describe('Lockfile', () => { const mockDir = createMockDirectory(); @@ -93,75 +63,6 @@ describe('Lockfile', () => { ]); expect(lockfile.toString()).toBe(mockA); }); - - it('should deduplicate and save mockA', async () => { - mockDir.setContent({ - 'yarn.lock': mockA, - }); - - const lockfilePath = mockDir.resolve('yarn.lock'); - const lockfile = await Lockfile.load(lockfilePath); - const result = lockfile.analyze({ localPackages: new Map() }); - expect(result).toEqual({ - invalidRanges: [], - newRanges: [], - newVersions: [ - { - name: 'b', - range: '^2', - oldVersion: '2.0.0', - newVersion: '2.0.1', - }, - ], - }); - - expect(lockfile.toString()).toBe(mockA); - lockfile.replaceVersions(result.newVersions); - expect(lockfile.toString()).toBe(mockADedup); - - await expect(fs.readFile(lockfilePath, 'utf8')).resolves.toBe(mockA); - await expect(lockfile.save(lockfilePath)).resolves.toBeUndefined(); - await expect(fs.readFile(lockfilePath, 'utf8')).resolves.toBe(mockADedup); - }); - - it('should deduplicate mockB', async () => { - mockDir.setContent({ - 'yarn.lock': mockB, - }); - - const lockfile = await Lockfile.load(mockDir.resolve('yarn.lock')); - const result = lockfile.analyze({ localPackages: new Map() }); - expect(result).toEqual({ - invalidRanges: [], - newRanges: [ - { - name: '@s/a', - oldRange: '^1', - newRange: '^2.0.x', - oldVersion: '1.0.1', - newVersion: '2.0.0', - }, - ], - newVersions: [ - { - name: '@s/a', - range: '*', - oldVersion: '1.0.1', - newVersion: '2.0.0', - }, - { - name: '@s/a', - range: '1 || 2', - oldVersion: '1.0.1', - newVersion: '2.0.0', - }, - ], - }); - - expect(lockfile.toString()).toBe(mockB); - lockfile.replaceVersions(result.newVersions); - expect(lockfile.toString()).toBe(mockBDedup); - }); }); const mockANew = `${MODERN_HEADER} @@ -179,51 +80,6 @@ b@^2: version: 2.0.0 `; -const mockANewDedup = `${MODERN_HEADER} -a@^1: - version: 1.0.1 - dependencies: - b: ^2 - integrity: sha512-xyz - resolved: "https://my-registry/a-1.0.01.tgz#abc123" - -"b@2.0.x, b@^2.0.1": - version: 2.0.1 - -b@^2: - version: 2.0.1 -`; - -const mockANewLocal = `${MODERN_HEADER} -a@^1: - version: 1.0.1 - dependencies: - b: ^2 - integrity: sha512-xyz - resolved: "https://my-registry/a-1.0.01.tgz#abc123" - -"b@2.0.x, b@^2.0.1": - version: 0.0.0-use.local - -b@^2: - version: 2.0.0 -`; - -const mockANewLocalDedup = `${MODERN_HEADER} -a@^1: - version: 1.0.1 - dependencies: - b: ^2 - integrity: sha512-xyz - resolved: "https://my-registry/a-1.0.01.tgz#abc123" - -"b@2.0.x, b@^2.0.1": - version: 0.0.0-use.local - -b@^2: - version: 0.0.0-use.local -`; - describe('New Lockfile', () => { const mockDir = createMockDirectory(); @@ -243,79 +99,4 @@ describe('New Lockfile', () => { ]); expect(lockfile.toString()).toBe(mockANew); }); - - it('should deduplicate and save mockANew', async () => { - mockDir.setContent({ - 'yarn.lock': mockANew, - }); - - const lockfilePath = mockDir.resolve('yarn.lock'); - const lockfile = await Lockfile.load(lockfilePath); - const result = lockfile.analyze({ localPackages: new Map() }); - expect(result).toEqual({ - invalidRanges: [], - newRanges: [], - newVersions: [ - { - name: 'b', - range: '^2', - oldVersion: '2.0.0', - newVersion: '2.0.1', - }, - ], - }); - - expect(lockfile.toString()).toBe(mockANew); - lockfile.replaceVersions(result.newVersions); - expect(lockfile.toString()).toBe(mockANewDedup); - - await expect(fs.readFile(lockfilePath, 'utf8')).resolves.toBe(mockANew); - await expect(lockfile.save(lockfilePath)).resolves.toBeUndefined(); - await expect(fs.readFile(lockfilePath, 'utf8')).resolves.toBe( - mockANewDedup, - ); - }); - - it('should deduplicate and save mockANewLocal', async () => { - mockDir.setContent({ - 'yarn.lock': mockANewLocal, - }); - - const lockfilePath = mockDir.resolve('yarn.lock'); - const lockfile = await Lockfile.load(lockfilePath); - const result = lockfile.analyze({ - localPackages: new Map([ - [ - 'b', - { - packageJson: { version: '2.0.1' }, - } as BackstagePackage, - ], - ]), - }); - expect(result).toEqual({ - invalidRanges: [], - newRanges: [], - newVersions: [ - { - name: 'b', - range: '^2', - oldVersion: '2.0.0', - newVersion: '0.0.0-use.local', - }, - ], - }); - - expect(lockfile.toString()).toBe(mockANewLocal); - lockfile.replaceVersions(result.newVersions); - expect(lockfile.toString()).toBe(mockANewLocalDedup); - - await expect(fs.readFile(lockfilePath, 'utf8')).resolves.toBe( - mockANewLocal, - ); - await expect(lockfile.save(lockfilePath)).resolves.toBeUndefined(); - await expect(fs.readFile(lockfilePath, 'utf8')).resolves.toBe( - mockANewLocalDedup, - ); - }); }); diff --git a/packages/cli/src/lib/versioning/Lockfile.ts b/packages/cli/src/lib/versioning/Lockfile.ts index 50d0a77cfd..047cf5aefc 100644 --- a/packages/cli/src/lib/versioning/Lockfile.ts +++ b/packages/cli/src/lib/versioning/Lockfile.ts @@ -15,10 +15,8 @@ */ import fs from 'fs-extra'; -import semver from 'semver'; import { parseSyml, stringifySyml } from '@yarnpkg/parsers'; import { stringify as legacyStringifyLockfile } from '@yarnpkg/lockfile'; -import { BackstagePackage } from '@backstage/cli-node'; const ENTRY_PATTERN = /^((?:@[^/]+\/)?[^@/]+)@(.+)$/; @@ -39,35 +37,6 @@ type LockfileQueryEntry = { dataKey: string; }; -/** Entries that have an invalid version range, for example an npm tag */ -type AnalyzeResultInvalidRange = { - name: string; - range: string; -}; - -/** Entries that can be deduplicated by bumping to an existing higher version */ -type AnalyzeResultNewVersion = { - name: string; - range: string; - oldVersion: string; - newVersion: string; -}; - -/** Entries that would need a dependency update in package.json to be deduplicated */ -type AnalyzeResultNewRange = { - name: string; - oldRange: string; - newRange: string; - oldVersion: string; - newVersion: string; -}; - -type AnalyzeResult = { - invalidRanges: AnalyzeResultInvalidRange[]; - newVersions: AnalyzeResultNewVersion[]; - newRanges: AnalyzeResultNewRange[]; -}; - // the new yarn header is handled out of band of the parsing // https://github.com/yarnpkg/berry/blob/0c5974f193a9397630e9aee2b3876cca62611149/packages/yarnpkg-core/sources/Project.ts#L1741-L1746 const NEW_HEADER = `${[ @@ -153,186 +122,6 @@ export class Lockfile { return this.packages.keys(); } - /** Analyzes the lockfile to identify possible actions and warnings for the entries */ - analyze(options: { - filter?: (name: string) => boolean; - localPackages: Map; - }): AnalyzeResult { - const { filter, localPackages } = options; - const result: AnalyzeResult = { - invalidRanges: [], - newVersions: [], - newRanges: [], - }; - - for (const [name, allEntries] of this.packages) { - if (filter && !filter(name)) { - continue; - } - - // Get rid of and signal any invalid ranges upfront - const invalid = allEntries.filter( - e => !semver.validRange(e.range) && !e.range.startsWith('workspace:'), - ); - result.invalidRanges.push( - ...invalid.map(({ range }) => ({ name, range })), - ); - - // Grab all valid entries, if there aren't at least 2 different valid ones we're done - const entries = allEntries.filter(e => semver.validRange(e.range)); - if (entries.length < 2) { - continue; - } - - // Find all versions currently in use - const versions = Array.from(new Set(entries.map(e => e.version))) - .map(v => { - // Translate workspace:^ references to the actual version - if (v === '0.0.0-use.local') { - const local = localPackages.get(name); - if (!local) { - throw new Error(`No local package found for ${name}`); - } - if (!local.packageJson.version) { - throw new Error(`No version found for local package ${name}`); - } - return { - entryVersion: v, - actualVersion: local.packageJson.version, - }; - } - return { entryVersion: v, actualVersion: v }; - }) - .sort((v1, v2) => semver.rcompare(v1.actualVersion, v2.actualVersion)); - - // If we're not using at least 2 different versions we're done - if (versions.length < 2) { - continue; - } - - // TODO(Rugvip): Support bumping into workspace ranges too - const acceptedVersions = new Set(); - for (const { version, range } of entries) { - // Finds the highest matching version from the the known versions - // TODO(Rugvip): We may want to select the version that satisfies the most ranges rather than the highest one - const acceptedVersion = versions.find(v => - semver.satisfies(v.actualVersion, range), - ); - if (!acceptedVersion) { - throw new Error( - `No existing version was accepted for range ${range}, searching through ${versions}, for package ${name}`, - ); - } - - if (acceptedVersion.entryVersion !== version) { - result.newVersions.push({ - name, - range, - newVersion: acceptedVersion.entryVersion, - oldVersion: version, - }); - } - - acceptedVersions.add(acceptedVersion.actualVersion); - } - - // If all ranges were able to accept the same version, we're done - if (acceptedVersions.size === 1) { - continue; - } - - // Find the max version that we may want bump older packages to - const maxVersion = Array.from(acceptedVersions).sort(semver.rcompare)[0]; - // Find all existing ranges that satisfy the new max version, and pick the one that - // results in the highest minimum allowed version, usually being the more specific one - const maxEntry = entries - .filter(e => semver.satisfies(maxVersion, e.range)) - .map(e => ({ e, min: semver.minVersion(e.range) })) - .filter(p => p.min) - .sort((a, b) => semver.rcompare(a.min!, b.min!))[0]?.e; - if (!maxEntry) { - throw new Error( - `No entry found that satisfies max version '${maxVersion}'`, - ); - } - - // Find all entries that don't satisfy the max version - for (const { version, range } of entries) { - if (semver.satisfies(maxVersion, range)) { - continue; - } - - result.newRanges.push({ - name, - oldRange: range, - newRange: maxEntry.range, - oldVersion: version, - newVersion: maxVersion, - }); - } - } - - return result; - } - - remove(name: string, range: string): boolean { - const query = `${name}@${range}`; - const existed = Boolean(this.data[query]); - delete this.data[query]; - - const newEntries = this.packages.get(name)?.filter(e => e.range !== range); - if (newEntries) { - this.packages.set(name, newEntries); - } - - return existed; - } - - /** Modifies the lockfile by bumping packages to the suggested versions */ - replaceVersions(results: AnalyzeResultNewVersion[]) { - for (const { name, range, oldVersion, newVersion } of results) { - const query = `${name}@${range}`; - - // Update the backing data - const entryData = this.data[query]; - if (!entryData) { - throw new Error(`No entry data for ${query}`); - } - if (entryData.version !== oldVersion) { - throw new Error( - `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`, - ); - } - - // Modifying the data in the entry is not enough, we need to reference an existing version object - const matchingEntry = Object.entries(this.data).find( - ([q, e]) => q.startsWith(`${name}@`) && e.version === newVersion, - ); - if (!matchingEntry) { - throw new Error( - `No matching entry found for ${name} at version ${newVersion}`, - ); - } - this.data[query] = matchingEntry[1]; - - // Update our internal data structure - const entry = this.packages.get(name)?.find(e => e.range === range); - if (!entry) { - throw new Error(`No entry data for ${query}`); - } - if (entry.version !== oldVersion) { - throw new Error( - `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`, - ); - } - entry.version = newVersion; - } - } - - async save(path: string) { - await fs.writeFile(path, this.toString(), 'utf8'); - } - toString() { return this.legacy ? legacyStringifyLockfile(this.data) diff --git a/packages/cli/templates/default-backend-plugin/package.json.hbs b/packages/cli/templates/default-backend-plugin/package.json.hbs index 3b808fc5e5..8dd31cbc38 100644 --- a/packages/cli/templates/default-backend-plugin/package.json.hbs +++ b/packages/cli/templates/default-backend-plugin/package.json.hbs @@ -31,7 +31,6 @@ "@backstage/backend-common": "{{versionQuery '@backstage/backend-common'}}", "@backstage/backend-defaults": "{{versionQuery '@backstage/backend-defaults'}}", "@backstage/backend-plugin-api": "{{versionQuery '@backstage/backend-plugin-api'}}", - "@backstage/config": "{{versionQuery '@backstage/config'}}", "express": "{{versionQuery 'express' '4.17.1'}}", "express-promise-router": "{{versionQuery 'express-promise-router' '4.1.0'}}", "node-fetch": "{{versionQuery 'node-fetch' '2.6.7'}}" diff --git a/packages/cli/templates/default-backend-plugin/src/service/router.ts b/packages/cli/templates/default-backend-plugin/src/service/router.ts index 312d7e252e..ced654aa82 100644 --- a/packages/cli/templates/default-backend-plugin/src/service/router.ts +++ b/packages/cli/templates/default-backend-plugin/src/service/router.ts @@ -1,12 +1,11 @@ import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; +import { LoggerService, RootConfigService } from '@backstage/backend-plugin-api'; import express from 'express'; import Router from 'express-promise-router'; export interface RouterOptions { logger: LoggerService; - config: Config; + config: RootConfigService; } export async function createRouter( diff --git a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.hbs index 9db62feebc..c79977f301 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleComponent/ExampleComponent.test.tsx.hbs @@ -4,14 +4,14 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { screen } from '@testing-library/react'; import { - setupRequestMockHandlers, + registerMswTestHooks, renderInTestApp, } from '@backstage/test-utils'; describe('ExampleComponent', () => { const server = setupServer(); // Enable sane handlers for network requests - setupRequestMockHandlers(server); + registerMswTestHooks(server); // setup mock response beforeEach(() => { diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index fb048e7237..f19fa29e29 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/config-loader +## 1.8.2-next.0 + +### Patch Changes + +- 493feac: Add boolean `allowMissingDefaultConfig` option to `ConfigSources.default` and + `ConfigSources.defaultForTargets`, which results in omission of a ConfigSource + for the default app-config.yaml configuration file if it's not present. +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 1.8.1 ### Patch Changes diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md index bc8f217bbb..1fef972089 100644 --- a/packages/config-loader/api-report.md +++ b/packages/config-loader/api-report.md @@ -21,6 +21,7 @@ export type AsyncConfigSourceGenerator = AsyncGenerator< // @public export interface BaseConfigSourcesOptions { + allowMissingDefaultConfig?: boolean; // (undocumented) remote?: Pick; // (undocumented) @@ -87,7 +88,7 @@ export interface ConfigSourcesDefaultOptions extends BaseConfigSourcesOptions { // (undocumented) argv?: string[]; // (undocumented) - env?: Record; + env?: Record; } // @public diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 28718b8290..16ff3e4f0b 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/config-loader", - "version": "1.8.1", + "version": "1.8.2-next.0", "description": "Config loading functionality used by Backstage backend, and CLI", "backstage": { "role": "node-library" diff --git a/packages/config-loader/src/sources/ConfigSources.test.ts b/packages/config-loader/src/sources/ConfigSources.test.ts index 4b14572666..e19ac1d51f 100644 --- a/packages/config-loader/src/sources/ConfigSources.test.ts +++ b/packages/config-loader/src/sources/ConfigSources.test.ts @@ -69,13 +69,18 @@ describe('ConfigSources', () => { }); it('should create default sources for targets', () => { + const fsSpy = jest.spyOn(fs, 'pathExistsSync').mockImplementation(path => { + return path === `${root}app-config.yaml`; + }); + expect( mergeSources( ConfigSources.defaultForTargets({ rootDir: '/', targets: [] }), ), ).toEqual([{ name: 'FileConfigSource', path: `${root}app-config.yaml` }]); - const fsSpy = jest.spyOn(fs, 'pathExistsSync').mockReturnValue(true); + fsSpy.mockReturnValue(true); + expect( mergeSources( ConfigSources.defaultForTargets({ rootDir: '/', targets: [] }), @@ -159,6 +164,10 @@ describe('ConfigSources', () => { }); it('should create a default source', () => { + const fsSpy = jest.spyOn(fs, 'pathExistsSync').mockImplementation(path => { + return path === `${root}app-config.yaml`; + }); + expect( mergeSources( ConfigSources.default({ @@ -184,6 +193,8 @@ describe('ConfigSources', () => { { name: 'FileConfigSource', path: resolvePath('b.yaml') }, { name: 'EnvConfigSource', env: { HOME: '/' } }, ]); + + fsSpy.mockRestore(); }); it('should merge sources', () => { diff --git a/packages/config-loader/src/sources/ConfigSources.ts b/packages/config-loader/src/sources/ConfigSources.ts index ecff39da2d..37c1c6b087 100644 --- a/packages/config-loader/src/sources/ConfigSources.ts +++ b/packages/config-loader/src/sources/ConfigSources.ts @@ -74,6 +74,11 @@ export interface BaseConfigSourcesOptions { watch?: boolean; rootDir?: string; remote?: Pick; + /** + * Allow the default app-config.yaml to be missing, in which case the source + * will not be created. + */ + allowMissingDefaultConfig?: boolean; /** * A custom substitution function that overrides the default one. @@ -102,7 +107,7 @@ export interface ConfigSourcesDefaultForTargetsOptions */ export interface ConfigSourcesDefaultOptions extends BaseConfigSourcesOptions { argv?: string[]; - env?: Record; + env?: Record; } /** @@ -177,14 +182,19 @@ export class ConfigSources { if (argSources.length === 0) { const defaultPath = resolvePath(rootDir, 'app-config.yaml'); const localPath = resolvePath(rootDir, 'app-config.local.yaml'); + const alwaysIncludeDefaultConfigSource = + !options.allowMissingDefaultConfig; + + if (alwaysIncludeDefaultConfigSource || fs.pathExistsSync(defaultPath)) { + argSources.push( + FileConfigSource.create({ + watch: options.watch, + path: defaultPath, + substitutionFunc: options.substitutionFunc, + }), + ); + } - argSources.push( - FileConfigSource.create({ - watch: options.watch, - path: defaultPath, - substitutionFunc: options.substitutionFunc, - }), - ); if (fs.pathExistsSync(localPath)) { argSources.push( FileConfigSource.create({ diff --git a/packages/config-loader/src/sources/RemoteConfigSource.test.ts b/packages/config-loader/src/sources/RemoteConfigSource.test.ts index 6082c05e65..e825bf3434 100644 --- a/packages/config-loader/src/sources/RemoteConfigSource.test.ts +++ b/packages/config-loader/src/sources/RemoteConfigSource.test.ts @@ -15,14 +15,14 @@ */ import { rest } from 'msw'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { setupServer } from 'msw/node'; import { RemoteConfigSource } from './RemoteConfigSource'; import { readN } from './__testUtils__/testUtils'; describe('RemoteConfigSource', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); it('should load config from a remote URL', async () => { worker.use( diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 32edc95e49..26b9eb20fe 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/core-app-api +## 1.14.1-next.0 + +### Patch Changes + +- 9a46a81: The request to delete the session cookie when running the app in protected mode is now done with a plain `fetch` rather than `FetchApi`. This fixes a bug where the app would immediately try to sign-in again when removing the cookie during logout. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.14.0 + +### Minor Changes + +- d3c39fc: Allow for the disabling of external routes through config, which was rendered impossible after the introduction of default targets. + + ```yaml + app: + routes: + bindings: + # This has the effect of removing the button for registering new + # catalog entities in the scaffolder template list view + scaffolder.registerComponent: false + ``` + +### Patch Changes + +- db2e2d5: Updated config schema to support app.routes.bindings +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.13.1-next.1 + +### Patch Changes + +- db2e2d5: Updated config schema to support app.routes.bindings +- Updated dependencies + - @backstage/core-plugin-api@1.9.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 1.13.0-next.0 ### Minor Changes diff --git a/packages/core-app-api/config.d.ts b/packages/core-app-api/config.d.ts index 22f241fbcf..1a7ccb666f 100644 --- a/packages/core-app-api/config.d.ts +++ b/packages/core-app-api/config.d.ts @@ -65,6 +65,18 @@ export interface Config { }>; }>; }; + + routes?: { + /** + * Maps external route references to regular route references. Both the + * key and the value is expected to be on the form `.`. + * If the value is `false`, the route will be disabled even if it has a + * default mapping. + * + * @deepVisibility frontend + */ + bindings?: { [externalRouteRefId: string]: string | false }; + }; }; /** diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index bdd6a1f663..0f78b11f98 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.13.0-next.0", + "version": "1.14.1-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts index 92e2abffb7..941ec90d66 100644 --- a/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts +++ b/packages/core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy.ts @@ -153,8 +153,9 @@ export class AppIdentityProxy implements IdentityApi { // It is fine if we do NOT worry yet about deleting cookies for OTHER backends like techdocs const appBaseUrl = await ctx.discoveryApi.getBaseUrl('app'); try { - await ctx.fetchApi.fetch(`${appBaseUrl}/.backstage/auth/v1/cookie`, { + await fetch(`${appBaseUrl}/.backstage/auth/v1/cookie`, { method: 'DELETE', + credentials: 'include', }); } catch { // Ignore the error for those who use static serving of the frontend diff --git a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.test.ts b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.test.ts index b6d59bd3f2..0143aa2ded 100644 --- a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.test.ts @@ -19,13 +19,13 @@ import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi'; import { UrlPatternDiscovery } from '../../DiscoveryApi'; import { ConfigReader } from '@backstage/config'; import { microsoftAuthApiRef } from '@backstage/core-plugin-api'; -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { registerMswTestHooks } from '@backstage/test-utils'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; describe('MicrosoftAuth', () => { const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); const microsoftAuth = MicrosoftAuth.create({ configApi: new ConfigReader(undefined), oauthRequestApi: new MockOAuthApi(), diff --git a/packages/core-app-api/src/app/AppManager.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx index 85b6bcd684..01277a09c7 100644 --- a/packages/core-app-api/src/app/AppManager.test.tsx +++ b/packages/core-app-api/src/app/AppManager.test.tsx @@ -19,9 +19,12 @@ import { MockAnalyticsApi, renderWithEffects, withLogCollector, + registerMswTestHooks, } from '@backstage/test-utils'; -import { screen, waitFor, act } from '@testing-library/react'; +import { screen, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; import React, { PropsWithChildren, ReactNode } from 'react'; import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; import { @@ -51,6 +54,9 @@ import { } from '@backstage/core-plugin-api/alpha'; describe('Integration Test', () => { + const server = setupServer(); + registerMswTestHooks(server); + const noOpAnalyticsApi = createApiFactory( analyticsApiRef, new NoOpAnalyticsApi(), @@ -847,6 +853,17 @@ describe('Integration Test', () => { }); it('should clear app cookie when the user logs out', async () => { + const logoutSignal = jest.fn(); + server.use( + rest.delete( + 'http://localhost:7007/app/.backstage/auth/v1/cookie', + (_req, res, ctx) => { + logoutSignal(); + return res(ctx.status(200)); + }, + ), + ); + const meta = global.document.createElement('meta'); meta.name = 'backstage-app-mode'; meta.content = 'protected'; @@ -901,12 +918,7 @@ describe('Integration Test', () => { await userEvent.click(screen.getByText('Sign Out')); }); - await waitFor(() => - expect(fetchApiMock.fetch).toHaveBeenCalledWith( - 'http://localhost:7007/app/.backstage/auth/v1/cookie', - { method: 'DELETE' }, - ), - ); + expect(logoutSignal).toHaveBeenCalled(); global.document.head.removeChild(meta); }); diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts index 4273ea99ee..60c4a3b8d9 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts @@ -18,7 +18,7 @@ import { DefaultAuthConnector } from './DefaultAuthConnector'; import MockOAuthApi from '../../apis/implementations/OAuthRequestApi/MockOAuthApi'; import * as loginPopup from '../loginPopup'; import { UrlPatternDiscovery } from '../../apis'; -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { registerMswTestHooks } from '@backstage/test-utils'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; import { ConfigReader } from '@backstage/config'; @@ -53,7 +53,7 @@ const defaultOptions = { describe('DefaultAuthConnector', () => { const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); afterEach(() => { jest.resetAllMocks(); diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index 8bcedca9c4..43acba43f0 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/core-compat-api +## 0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## 0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## 0.2.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.7-next.1 + ## 0.2.7-next.0 ### Patch Changes diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 954cd6deb3..dd954f3318 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.2.7-next.0", + "version": "0.2.8-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx index b9f4e5528c..3523b0a40b 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx @@ -101,7 +101,7 @@ function visitRouteChildren(options: { parentExtensionId: string; context: { pluginId: string; - extensions: ExtensionDefinition[]; + extensions: ExtensionDefinition[]; getUniqueName: () => string; discoverPlugin: (plugin: LegacyBackstagePlugin) => void; }; @@ -154,7 +154,7 @@ export function collectLegacyRoutes( ): BackstagePlugin[] { const pluginExtensions = new Map< LegacyBackstagePlugin, - ExtensionDefinition[] + ExtensionDefinition[] >(); const getUniqueName = (() => { diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index f42b6396ce..a2463f594e 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/core-components +## 0.14.10-next.0 + +### Patch Changes + +- 678971a: Move the `Link` component to the `RoutedTabs` instead of the `HeaderTabs` component +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/version-bridge@1.0.8 + +## 0.14.9 + +### Patch Changes + +- d4ffdbb: Fixed bug where `', () => { expect(getByTestId('select').textContent).toBe('All results'); }); + + it('should function correctly when a custom data-testid is provided', async () => { + const { getByTestId } = render( + diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 73060ebd4a..c4ce7148f4 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + +## 0.2.21 + +### Patch Changes + +- b5deed0: Add support for `autocomplete` handler to provide autocomplete options for `RepoUrlPicker` +- Updated dependencies + - @backstage/integration@1.13.0 + ## 0.2.21-next.0 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index 0e98fcc505..06fd4e6f3a 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", - "version": "0.2.21-next.0", + "version": "0.2.22-next.0", "description": "Common functionalities for bitbucket-cloud plugins", "backstage": { "role": "common-library", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index bd5663758a..22a372051e 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,59 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.3.18-next.0 + +### Patch Changes + +- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. +- 90a7340: `AwsOrganizationCloudAccountProcessor` configuration field `roleArn` is deprecated in favor of new field `accountId` +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-kubernetes-common@0.8.1 + +## 0.3.17 + +### Patch Changes + +- 4afa050: Export `defaultEksClusterEntityTransformer` to allow library consumers to layer additional changes on top of the default transformer. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + +## 0.3.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-kubernetes-common@0.8.0 + ## 0.3.16-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/api-report.md b/plugins/catalog-backend-module-aws/api-report.md index 5098ab5c32..428a39d934 100644 --- a/plugins/catalog-backend-module-aws/api-report.md +++ b/plugins/catalog-backend-module-aws/api-report.md @@ -106,6 +106,9 @@ export class AwsS3EntityProvider implements EntityProvider { refresh(logger: LoggerService): Promise; } +// @public +export const defaultEksClusterEntityTransformer: EksClusterEntityTransformer; + // @public export type EksClusterEntityTransformer = ( cluster: Cluster, diff --git a/plugins/catalog-backend-module-aws/config.d.ts b/plugins/catalog-backend-module-aws/config.d.ts index 4c9c98512e..0a9d9c97db 100644 --- a/plugins/catalog-backend-module-aws/config.d.ts +++ b/plugins/catalog-backend-module-aws/config.d.ts @@ -26,8 +26,14 @@ export interface Config { provider: { /** * The role to be assumed by this processor + * @deprecated Use `accountId` instead. */ roleArn?: string; + + /** + * The AWS account ID to query for organizational data + */ + accountId?: string; }; }; }; diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 3b75c3cfcd..eeb26b2e1c 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.3.16-next.0", + "version": "0.3.18-next.0", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-aws/src/awsOrganization/config.test.ts b/plugins/catalog-backend-module-aws/src/awsOrganization/config.test.ts index 7e0dc58559..a84c65abda 100644 --- a/plugins/catalog-backend-module-aws/src/awsOrganization/config.test.ts +++ b/plugins/catalog-backend-module-aws/src/awsOrganization/config.test.ts @@ -22,11 +22,13 @@ describe('readAwsOrganizationConfig', () => { const config = { provider: { roleArn: 'aws::arn::foo', + accountId: '111111111111', }, }; const actual = readAwsOrganizationConfig(new ConfigReader(config)); const expected = { roleArn: 'aws::arn::foo', + accountId: '111111111111', }; expect(actual).toEqual(expected); }); diff --git a/plugins/catalog-backend-module-aws/src/awsOrganization/config.ts b/plugins/catalog-backend-module-aws/src/awsOrganization/config.ts index e6096b932b..ea16394e0a 100644 --- a/plugins/catalog-backend-module-aws/src/awsOrganization/config.ts +++ b/plugins/catalog-backend-module-aws/src/awsOrganization/config.ts @@ -22,8 +22,14 @@ import { Config } from '@backstage/config'; export type AwsOrganizationProviderConfig = { /** * The role to assume for the processor. + * @deprecated Use `accountId` instead. */ roleArn?: string; + + /** + * The AWS accountId + */ + accountId?: string; }; export function readAwsOrganizationConfig( @@ -32,7 +38,9 @@ export function readAwsOrganizationConfig( const providerConfig = config.getOptionalConfig('provider'); const roleArn = providerConfig?.getOptionalString('roleArn'); + const accountId = providerConfig?.getOptionalString('accountId'); return { roleArn, + accountId, }; } diff --git a/plugins/catalog-backend-module-aws/src/index.ts b/plugins/catalog-backend-module-aws/src/index.ts index 72ddc6386a..de8f9fd7b2 100644 --- a/plugins/catalog-backend-module-aws/src/index.ts +++ b/plugins/catalog-backend-module-aws/src/index.ts @@ -24,3 +24,4 @@ export * from './processors'; export * from './providers'; export * from './types'; export * from './constants'; +export { defaultEksClusterEntityTransformer } from './lib'; diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts index a491dbab7d..a5f5ce2f6c 100644 --- a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts @@ -106,6 +106,7 @@ export class AwsEKSClusterProcessor implements CatalogProcessor { } const eksClient = new EKS({ + customUserAgent: 'backstage-aws-catalog-eks-cluster-processor', credentials, credentialDefaultProvider: providerFunction, region, diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts index 4a454fce6f..73d7a63e2b 100644 --- a/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts @@ -59,10 +59,18 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { static async fromConfig(config: Config, options: { logger: LoggerService }) { const c = config.getOptionalConfig('catalog.processors.awsOrganization'); const orgConfig = c ? readAwsOrganizationConfig(c) : undefined; + + if (orgConfig?.roleArn) { + options.logger.warn( + 'The roleArn configuration for AwsOrganizationCloudAccountProcessor ignores the role name, use accountId configuration instead', + ); + } + const awsCredentialsManager = DefaultAwsCredentialsManager.fromConfig(config); const credProvider = await awsCredentialsManager.getCredentialProvider({ arn: orgConfig?.roleArn, + accountId: orgConfig?.accountId, }); return new AwsOrganizationCloudAccountProcessor( credProvider, diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts index 91ba76b695..0c58fe3f3a 100644 --- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts +++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts @@ -154,6 +154,7 @@ export class AwsS3EntityProvider implements EntityProvider { accountId ? { accountId } : undefined, ); this.s3 = new S3({ + customUserAgent: 'backstage-aws-catalog-s3-entity-provider', apiVersion: '2006-03-01', credentialDefaultProvider: () => credProvider.sdkCredentialProvider, endpoint: this.integration.config.endpoint, diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 598636a239..1706dc02bf 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.25 + +## 0.1.42 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/config@1.2.0 + +## 0.1.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/config@1.2.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + ## 0.1.41-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index bf0b69f177..5fdd95140d 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", - "version": "0.1.41-next.0", + "version": "0.1.43-next.0", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts index ef00610dc0..4839b625d2 100644 --- a/plugins/catalog-backend-module-azure/src/lib/azure.test.ts +++ b/plugins/catalog-backend-module-azure/src/lib/azure.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { codeSearch, CodeSearchResponse } from './azure'; @@ -26,7 +26,7 @@ import { ConfigReader } from '@backstage/config'; describe('azure', () => { const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); const createFixture = (host: string, token: string) => { const azureConfig = { diff --git a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md index fa23d67c67..afdd92803f 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-openapi-utils@0.1.16-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-tasks@0.5.27 + - @backstage/backend-openapi-utils@0.1.15 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-openapi-utils@0.1.15-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.4-next.1 + ## 0.2.4-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 4d06de5ddb..6da2d92d5f 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.2.4-next.0", + "version": "0.2.6-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index 2a977a4f7b..2c01eb8857 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,56 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-bitbucket-cloud-common@0.2.21 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.21-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.2.8-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index d0261ad01f..234bfea5c5 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", - "version": "0.2.8-next.0", + "version": "0.2.10-next.0", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts index 55b701e016..80d09f3e2f 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts @@ -22,7 +22,7 @@ import { } from '@backstage/backend-tasks'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { CatalogApi } from '@backstage/catalog-client'; import { Entity, LocationEntity } from '@backstage/catalog-model'; @@ -62,7 +62,7 @@ const logger = mockServices.logger.mock(); const server = setupServer(); describe('BitbucketCloudEntityProvider', () => { - setupRequestMockHandlers(server); + registerMswTestHooks(server); const simpleConfig = new ConfigReader({ catalog: { diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index f395e731ec..7f47be269c 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.36 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-catalog-node@1.12.4-next.1 + ## 0.1.35-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 4d2e0a1e78..45d8c5cd33 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.35-next.0", + "version": "0.1.37-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-bitbucket-server/src/lib/BitbucketServerClient.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/lib/BitbucketServerClient.test.ts index a3d4155121..db2a05d0b1 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/lib/BitbucketServerClient.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/lib/BitbucketServerClient.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { BitbucketServerIntegrationConfig } from '@backstage/integration'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -50,7 +50,7 @@ describe('BitbucketServerClient', () => { config: config, }); - setupRequestMockHandlers(server); + registerMswTestHooks(server); it('listProjects', async () => { server.use( diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts index 9624e396cd..49b69f0fa6 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts @@ -21,7 +21,7 @@ import { } from '@backstage/backend-tasks'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; @@ -103,7 +103,7 @@ function setupStubs(projects: Project[], baseUrl: string) { } describe('BitbucketServerEntityProvider', () => { - setupRequestMockHandlers(server); + registerMswTestHooks(server); afterEach(() => { jest.clearAllMocks(); }); diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index 5948b92338..ca2d4412b2 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-kubernetes-common@0.8.1 + +## 0.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.1.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-kubernetes-common@0.8.0 + ## 0.1.22-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 5cfcca3ba9..442c7540e3 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gcp", - "version": "0.1.22-next.0", + "version": "0.1.24-next.0", "description": "A Backstage catalog backend module that helps integrate towards GCP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 5cab7509bc..63dcfa9562 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.39 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-catalog-node@1.12.4-next.1 + ## 0.1.38-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 7bac2566da..61f2ac8dfd 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.38-next.0", + "version": "0.1.40-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts index 8b1acbdbaa..ee3555c7f9 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts @@ -21,7 +21,7 @@ import { } from '@backstage/backend-tasks'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; @@ -57,7 +57,7 @@ class PersistingTaskRunner implements TaskRunner { const logger = mockServices.logger.mock(); describe('GerritEntityProvider', () => { - setupRequestMockHandlers(server); + registerMswTestHooks(server); afterEach(() => { jest.clearAllMocks(); diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index 950185b415..72c80a97cd 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-backend-module-github@0.6.6-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-catalog-backend-module-github@0.6.5 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/config@1.2.0 + +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-github@0.6.5-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.1.16-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 31daeea5ee..9a363613de 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", - "version": "0.1.16-next.0", + "version": "0.1.18-next.0", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index d9dc710268..12e2d70eb1 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,110 @@ # @backstage/plugin-catalog-backend-module-github +## 0.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.6.5 + +### Patch Changes + +- 9112efc: Adds support for `repository` events. + + The provider adds a subscription to the topic `github.repository`. + + Hereby, it supports events of type `repository` with actions + + - `archived` + - `deleted` + - `edited` + - `renamed` + - `transferred` + - `unarchived` + + Actions skipped as they don't require entity changes: + + - `created` + - `privatized` + - `publicized` + + If the config option `validateLocationsExist` is enabled, an API request + is necessary and will be executed. + This affects the actions `renamed`, `transferred`, and `unarchive` + of event type `repository`. + + Catalog entities related to the `GithubEntityProvider` instance will be adjusted + according to action and its meaning for them. + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-catalog-backend@1.24.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.6.5-next.1 + +### Patch Changes + +- 9112efc: Adds support for `repository` events. + + The provider adds a subscription to the topic `github.repository`. + + Hereby, it supports events of type `repository` with actions + + - `archived` + - `deleted` + - `edited` + - `renamed` + - `transferred` + - `unarchived` + + Actions skipped as they don't require entity changes: + + - `created` + - `privatized` + - `publicized` + + If the config option `validateLocationsExist` is enabled, an API request + is necessary and will be executed. + This affects the actions `renamed`, `transferred`, and `unarchive` + of event type `repository`. + + Catalog entities related to the `GithubEntityProvider` instance will be adjusted + according to action and its meaning for them. + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.6.4-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 3528786e6d..aa1f750aef 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github", - "version": "0.6.4-next.0", + "version": "0.6.6-next.0", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts index e5c7eb5896..29c8cbc66d 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts @@ -35,7 +35,7 @@ jest.mock('@octokit/rest', () => { import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { GithubLocationAnalyzer } from './GithubLocationAnalyzer'; import { - setupRequestMockHandlers, + registerMswTestHooks, mockServices, } from '@backstage/backend-test-utils'; import { setupServer } from 'msw/node'; @@ -63,7 +63,7 @@ describe('GithubLocationAnalyzer', () => { }, }); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { server.use( diff --git a/plugins/catalog-backend-module-github/src/lib/github.test.ts b/plugins/catalog-backend-module-github/src/lib/github.test.ts index d83e8031cc..9eae9e1161 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.test.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { graphql as graphqlOctokit } from '@octokit/graphql'; import { graphql as graphqlMsw } from 'msw'; @@ -39,7 +39,7 @@ describe('github', () => { const graphql = graphqlOctokit.defaults({ request: { fetch } }); const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); describe('getOrganizationUsers using defaultUserMapper', () => { it('reads members', async () => { diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index eff52ea98d..cba65e522e 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -38,6 +38,7 @@ export type QueryResponse = { type RepositoryOwnerResponse = { repositories?: Connection; + repository?: RepositoryResponse; }; export type OrganizationResponse = { @@ -509,6 +510,61 @@ export async function getOrganizationRepositories( return { repositories }; } +export async function getOrganizationRepository( + client: typeof graphql, + org: string, + repoName: string, + catalogPath: string, +): Promise { + let relativeCatalogPathRef: string; + // We must strip the leading slash or the query for objects does not work + if (catalogPath.startsWith('/')) { + relativeCatalogPathRef = catalogPath.substring(1); + } else { + relativeCatalogPathRef = catalogPath; + } + const catalogPathRef = `HEAD:${relativeCatalogPathRef}`; + const query = ` + query repository($org: String!, $repoName: String!, $catalogPathRef: String!) { + repositoryOwner(login: $org) { + repository(name: $repoName) { + name + catalogInfoFile: object(expression: $catalogPathRef) { + __typename + ... on Blob { + id + text + } + } + url + isArchived + isFork + visibility + repositoryTopics(first: 100) { + nodes { + ... on RepositoryTopic { + topic { + name + } + } + } + } + defaultBranchRef { + name + } + } + } + }`; + + const response: QueryResponse = await client(query, { + org, + repoName, + catalogPathRef, + }); + + return response.repositoryOwner?.repository || null; +} + /** * Gets all the users out of a Github organization. * diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index 42e49c2f72..002b2bbfd1 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -19,12 +19,22 @@ import { TaskInvocationDefinition, TaskRunner, } from '@backstage/backend-tasks'; -import { ConfigReader } from '@backstage/config'; -import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; +import { Config, ConfigReader } from '@backstage/config'; +import { + DeferredEntity, + EntityProviderConnection, + locationSpecToMetadataName, +} from '@backstage/plugin-catalog-node'; import { GithubEntityProvider } from './GithubEntityProvider'; import * as helpers from '../lib/github'; import { EventParams } from '@backstage/plugin-events-node'; import { mockServices } from '@backstage/backend-test-utils'; +import { + Commit, + PushEvent, + RepositoryEvent, + RepositoryRenamedEvent, +} from '@octokit/webhooks-types'; jest.mock('../lib/github', () => { return { @@ -47,62 +57,88 @@ class PersistingTaskRunner implements TaskRunner { const logger = mockServices.logger.mock(); describe('GithubEntityProvider', () => { - afterEach(() => jest.clearAllMocks()); + const createSingleProviderConfig = (options?: { + providerConfig?: object; + unwrapped?: boolean; + }): Config => { + const providerConfig = { + organization: 'test-org', + ...options?.providerConfig, + }; - it('no provider config', () => { + return new ConfigReader({ + catalog: { + providers: { + github: options?.unwrapped + ? providerConfig + : { myProvider: providerConfig }, + }, + }, + }); + }; + + const createProviders = (config: Config) => { const schedule = new PersistingTaskRunner(); - const config = new ConfigReader({}); - const providers = GithubEntityProvider.fromConfig(config, { + return GithubEntityProvider.fromConfig(config, { logger, schedule, }); + }; + + const createExpectedEntitiesForUrl = (url: string): DeferredEntity[] => { + return [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:${url}`, + 'backstage.io/managed-by-origin-location': `url:${url}`, + }, + name: locationSpecToMetadataName({ type: 'url', target: url }), + }, + spec: { + presence: 'optional', + target: url, + type: 'url', + }, + }, + locationKey: 'github-provider:myProvider', + }, + ]; + }; + + afterEach(() => jest.clearAllMocks()); + + it('no provider config', () => { + const config = new ConfigReader({}); + const providers = createProviders(config); expect(providers).toHaveLength(0); }); it('single simple provider config', () => { - const schedule = new PersistingTaskRunner(); - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', - }, - }, - }, - }); - const providers = GithubEntityProvider.fromConfig(config, { - logger, - schedule, - }); + const config = createSingleProviderConfig({ unwrapped: true }); + const providers = createProviders(config); expect(providers).toHaveLength(1); expect(providers[0].getProviderName()).toEqual('github-provider:default'); }); it('throws when the integration config does not exist', () => { - const schedule = new PersistingTaskRunner(); - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', - host: 'ghe.internal.com', - }, - }, + const config = createSingleProviderConfig({ + providerConfig: { + host: 'ghe.internal.com', }, }); - expect(() => - GithubEntityProvider.fromConfig(config, { - logger, - schedule, - }), - ).toThrow(/There is no GitHub config that matches host ghe.internal.com/); + expect(() => createProviders(config)).toThrow( + /There is no GitHub config that matches host ghe.internal.com/, + ); }); it('multiple provider configs', () => { - const schedule = new PersistingTaskRunner(); const config = new ConfigReader({ catalog: { providers: { @@ -117,10 +153,7 @@ describe('GithubEntityProvider', () => { }, }, }); - const providers = GithubEntityProvider.fromConfig(config, { - logger, - schedule, - }); + const providers = createProviders(config); expect(providers).toHaveLength(2); expect(providers[0].getProviderName()).toEqual( @@ -132,19 +165,12 @@ describe('GithubEntityProvider', () => { }); it('apply full update on scheduled execution with basic filters', async () => { - const config = new ConfigReader({ - catalog: { - providers: { - github: { - myProvider: { - organization: 'test-org', - catalogPath: 'custom/path/catalog-custom.yaml', - filters: { - branch: 'main', - repository: 'test-.*', - }, - }, - }, + const config = createSingleProviderConfig({ + providerConfig: { + catalogPath: 'custom/path/catalog-custom.yaml', + filters: { + branch: 'main', + repository: 'test-.*', }, }, }); @@ -194,27 +220,7 @@ describe('GithubEntityProvider', () => { await (taskDef.fn as () => Promise)(); const url = `https://github.com/test-org/test-repo/blob/main/custom/path/catalog-custom.yaml`; - const expectedEntities = [ - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Location', - metadata: { - annotations: { - 'backstage.io/managed-by-location': `url:${url}`, - 'backstage.io/managed-by-origin-location': `url:${url}`, - }, - name: 'generated-5e4b9498097f15434e88c477cfba6c079aa8ca7f', - }, - spec: { - presence: 'optional', - target: `${url}`, - type: 'url', - }, - }, - locationKey: 'github-provider:myProvider', - }, - ]; + const expectedEntities = createExpectedEntitiesForUrl(url); expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ @@ -379,27 +385,7 @@ describe('GithubEntityProvider', () => { await (taskDef.fn as () => Promise)(); const url = `https://github.com/test-org/test-repo/blob/main/custom/path/catalog-custom.yaml`; - const expectedEntities = [ - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Location', - metadata: { - annotations: { - 'backstage.io/managed-by-location': `url:${url}`, - 'backstage.io/managed-by-origin-location': `url:${url}`, - }, - name: 'generated-5e4b9498097f15434e88c477cfba6c079aa8ca7f', - }, - spec: { - presence: 'optional', - target: `${url}`, - type: 'url', - }, - }, - locationKey: 'github-provider:myProvider', - }, - ]; + const expectedEntities = createExpectedEntitiesForUrl(url); expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ @@ -487,27 +473,7 @@ describe('GithubEntityProvider', () => { await (taskDef.fn as () => Promise)(); const url = `https://github.com/test-org/another-repo/blob/main/catalog-custom.yaml`; - const expectedEntities = [ - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Location', - metadata: { - annotations: { - 'backstage.io/managed-by-location': `url:${url}`, - 'backstage.io/managed-by-origin-location': `url:${url}`, - }, - name: 'generated-934f500db2ba2e8ea3524567926f45a73bb0b532', - }, - spec: { - presence: 'optional', - target: `${url}`, - type: 'url', - }, - }, - locationKey: 'github-provider:myProvider', - }, - ]; + const expectedEntities = createExpectedEntitiesForUrl(url); expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ @@ -636,27 +602,7 @@ describe('GithubEntityProvider', () => { await (taskDef.fn as () => Promise)(); const url = `https://github.com/test-org/test-repo/blob/main/custom/path/catalog-custom.yaml`; - const expectedEntities = [ - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Location', - metadata: { - annotations: { - 'backstage.io/managed-by-location': `url:${url}`, - 'backstage.io/managed-by-origin-location': `url:${url}`, - }, - name: 'generated-5e4b9498097f15434e88c477cfba6c079aa8ca7f', - }, - spec: { - presence: 'optional', - target: `${url}`, - type: 'url', - }, - }, - locationKey: 'github-provider:myProvider', - }, - ]; + const expectedEntities = createExpectedEntitiesForUrl(url); expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ @@ -666,15 +612,7 @@ describe('GithubEntityProvider', () => { }); it('fail without schedule and scheduler', () => { - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', - }, - }, - }, - }); + const config = createSingleProviderConfig(); expect(() => GithubEntityProvider.fromConfig(config, { @@ -687,15 +625,7 @@ describe('GithubEntityProvider', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), } as unknown as PluginTaskScheduler; - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', - }, - }, - }, - }); + const config = createSingleProviderConfig(); expect(() => GithubEntityProvider.fromConfig(config, { @@ -703,7 +633,7 @@ describe('GithubEntityProvider', () => { scheduler, }), ).toThrow( - 'No schedule provided neither via code nor config for github-provider:default', + 'No schedule provided neither via code nor config for github-provider:myProvider', ); }); @@ -712,18 +642,14 @@ describe('GithubEntityProvider', () => { const scheduler = { createScheduledTaskRunner: (_: any) => schedule, } as unknown as PluginTaskScheduler; - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', - schedule: { - frequency: 'P1M', - timeout: 'PT3M', - }, - }, + const config = createSingleProviderConfig({ + providerConfig: { + schedule: { + frequency: 'P1M', + timeout: 'PT3M', }, }, + unwrapped: true, }); const providers = GithubEntityProvider.fromConfig(config, { logger, @@ -734,622 +660,836 @@ describe('GithubEntityProvider', () => { expect(providers[0].getProviderName()).toEqual('github-provider:default'); }); - it('apply delta update on added files from push event with glob catalog path', async () => { - const schedule = new PersistingTaskRunner(); - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', + describe('on event', () => { + const createExpectedEntitiesForEvent = ( + event: EventParams, + options?: { branch?: string; catalogFilePath?: string }, + ): DeferredEntity[] => { + const url = `${event.eventPayload.repository.url}/blob/${ + options?.branch ?? 'main' + }/${options?.catalogFilePath ?? 'catalog-info.yaml'}`; + return createExpectedEntitiesForUrl(url); + }; + + describe('on push event', () => { + const createPushEvent = (options?: { + catalogFile?: { + action?: 'added' | 'removed' | 'modified'; + path?: string; + }; + org?: string; + ref?: string; + }): EventParams => { + const organization = options?.org ?? 'test-org'; + const repo = { + default_branch: 'main', + master_branch: 'main', + name: 'test-repo', + organization, + topics: [], + url: `https://github.com/${organization}/test-repo`, + } as Partial; + + const catalogCommit = { + added: [] as string[], + modified: [] as string[], + removed: [] as string[], + }; + catalogCommit[options?.catalogFile?.action ?? 'modified'] = [ + options?.catalogFile?.path ?? 'catalog-info.yaml', + ]; + + const event = { + ref: options?.ref ?? 'refs/heads/main', + repository: repo as PushEvent['repository'], + created: true, + deleted: false, + forced: false, + commits: [ + { + added: ['new-file.yaml'], + removed: [], + modified: [], + }, + catalogCommit, + ] as Partial[], + } as PushEvent; + + return { + topic: 'github.push', + metadata: { + 'x-github-event': 'push', + }, + eventPayload: event, + }; + }; + + it('apply delta update on added files with glob catalog path', async () => { + const config = createSingleProviderConfig({ + providerConfig: { catalogPath: '**/catalog-info.yaml', }, - }, - }, - }); + }); + const provider = createProviders(config)[0]; - const provider = GithubEntityProvider.fromConfig(config, { - logger, - schedule, - })[0]; + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - await provider.connect(entityProviderConnection); - - const event: EventParams = { - topic: 'github.push', - metadata: { - 'x-github-event': 'push', - }, - eventPayload: { - ref: 'refs/heads/main', - repository: { - name: 'teste-1', - url: 'https://github.com/test-org/test-repo', - default_branch: 'main', - stargazers: 0, - master_branch: 'main', - organization: 'test-org', - topics: [], - }, - created: true, - deleted: false, - forced: false, - commits: [ - { - added: ['new-file.yaml'], - removed: [], - modified: [], + const event = createPushEvent({ + catalogFile: { + action: 'added', + path: 'folder1/folder2/folder3/catalog-info.yaml', }, - { - added: ['folder1/folder2/folder3/catalog-info.yaml'], - removed: [], - modified: [], + }); + const expectedEntities = createExpectedEntitiesForEvent(event, { + catalogFilePath: 'folder1/folder2/folder3/catalog-info.yaml', + }); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', + added: expectedEntities, + removed: [], + }); + }); + + it('apply delta update on added files', async () => { + const config = createSingleProviderConfig(); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createPushEvent({ + catalogFile: { + action: 'added', }, - ], - }, - }; - const url = - 'https://github.com/test-org/test-repo/blob/main/folder1/folder2/folder3/catalog-info.yaml'; - const expectedEntities = [ - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Location', - metadata: { - annotations: { - 'backstage.io/managed-by-location': `url:${url}`, - 'backstage.io/managed-by-origin-location': `url:${url}`, + }); + const expectedEntities = createExpectedEntitiesForEvent(event); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', + added: expectedEntities, + removed: [], + }); + }); + + it('apply delta update on removed files', async () => { + const config = createSingleProviderConfig(); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createPushEvent({ + catalogFile: { + action: 'removed', + }, + }); + const expectedEntities = createExpectedEntitiesForEvent(event); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', + added: [], + removed: expectedEntities, + }); + }); + + it('apply refresh call on modified files', async () => { + const config = createSingleProviderConfig(); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createPushEvent(); + + await provider.onEvent(event); + + expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.refresh).toHaveBeenCalledWith({ + keys: [ + 'url:https://github.com/test-org/test-repo/tree/main/catalog-info.yaml', + 'url:https://github.com/test-org/test-repo/blob/main/catalog-info.yaml', + ], + }); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); + }); + + it('apply refresh call on modified files with glob catalog path', async () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', + catalogPath: '**/catalog-info.yaml', + }, }, - name: 'generated-c499bfb5e3f159d2bfefe26cac86a8a0b95b47f0', }, - spec: { - presence: 'optional', - target: `${url}`, - type: 'url', - }, - }, - locationKey: 'github-provider:default', - }, - ]; + }); + const provider = createProviders(config)[0]; - await provider.onEvent(event); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ - type: 'delta', - added: expectedEntities, - removed: [], - }); - }); + const event = createPushEvent(); - it('apply delta update on added files from push event', async () => { - const schedule = new PersistingTaskRunner(); - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', - }, - }, - }, - }); + await provider.onEvent(event); - const provider = GithubEntityProvider.fromConfig(config, { - logger, - schedule, - })[0]; + expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.refresh).toHaveBeenCalledWith({ + keys: [ + 'url:https://github.com/test-org/test-repo/tree/main/catalog-info.yaml', + 'url:https://github.com/test-org/test-repo/blob/main/catalog-info.yaml', + 'url:https://github.com/test-org/test-repo/tree/main/**/catalog-info.yaml', + ], + }); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); + }); - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - await provider.connect(entityProviderConnection); - - const event: EventParams = { - topic: 'github.push', - metadata: { - 'x-github-event': 'push', - }, - eventPayload: { - ref: 'refs/heads/main', - repository: { - name: 'teste-1', - url: 'https://github.com/test-org/test-repo', - default_branch: 'main', - stargazers: 0, - master_branch: 'main', - organization: 'test-org', - topics: [], - }, - created: true, - deleted: false, - forced: false, - commits: [ - { - added: ['new-file.yaml'], - removed: [], - modified: [], - }, - { - added: ['catalog-info.yaml'], - removed: [], - modified: [], - }, - ], - }, - }; - const url = - 'https://github.com/test-org/test-repo/blob/main/catalog-info.yaml'; - const expectedEntities = [ - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Location', - metadata: { - annotations: { - 'backstage.io/managed-by-location': `url:${url}`, - 'backstage.io/managed-by-origin-location': `url:${url}`, - }, - name: 'generated-8688630f57e421bc85f12b9828ed7dad6aff3bb3', - }, - spec: { - presence: 'optional', - target: `${url}`, - type: 'url', - }, - }, - locationKey: 'github-provider:default', - }, - ]; - - await provider.onEvent(event); - - expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ - type: 'delta', - added: expectedEntities, - removed: [], - }); - }); - - it('apply delta update on removed files from push event', async () => { - const schedule = new PersistingTaskRunner(); - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', - }, - }, - }, - }); - - const provider = GithubEntityProvider.fromConfig(config, { - logger, - schedule, - })[0]; - - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - await provider.connect(entityProviderConnection); - - const event: EventParams = { - topic: 'github.push', - metadata: { - 'x-github-event': 'push', - }, - eventPayload: { - ref: 'refs/heads/main', - repository: { - name: 'teste-1', - url: 'https://github.com/test-org/test-repo', - default_branch: 'main', - stargazers: 0, - master_branch: 'main', - organization: 'test-org', - topics: [], - }, - created: true, - deleted: false, - forced: false, - commits: [ - { - added: ['new-file.yaml'], - removed: [], - modified: [], - }, - { - added: [], - removed: ['catalog-info.yaml'], - modified: [], - }, - ], - }, - }; - const url = - 'https://github.com/test-org/test-repo/blob/main/catalog-info.yaml'; - const expectedEntities = [ - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Location', - metadata: { - annotations: { - 'backstage.io/managed-by-location': `url:${url}`, - 'backstage.io/managed-by-origin-location': `url:${url}`, - }, - name: 'generated-8688630f57e421bc85f12b9828ed7dad6aff3bb3', - }, - spec: { - presence: 'optional', - target: `${url}`, - type: 'url', - }, - }, - locationKey: 'github-provider:default', - }, - ]; - - await provider.onEvent(event); - - expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ - type: 'delta', - added: [], - removed: expectedEntities, - }); - }); - - it('apply refresh call on modified files from push event', async () => { - const schedule = new PersistingTaskRunner(); - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', - }, - }, - }, - }); - - const provider = GithubEntityProvider.fromConfig(config, { - logger, - schedule, - })[0]; - - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - await provider.connect(entityProviderConnection); - - const event: EventParams = { - topic: 'github.push', - metadata: { - 'x-github-event': 'push', - }, - eventPayload: { - ref: 'refs/heads/main', - repository: { - name: 'teste-1', - url: 'https://github.com/test-org/test-repo', - default_branch: 'main', - stargazers: 0, - master_branch: 'main', - organization: 'test-org', - topics: [], - }, - created: true, - deleted: false, - forced: false, - commits: [ - { - added: ['new-file.yaml'], - removed: [], - modified: [], - }, - { - added: [], - removed: [], - modified: ['catalog-info.yaml'], - }, - ], - }, - }; - - await provider.onEvent(event); - - expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(1); - expect(entityProviderConnection.refresh).toHaveBeenCalledWith({ - keys: [ - 'url:https://github.com/test-org/test-repo/tree/main/catalog-info.yaml', - 'url:https://github.com/test-org/test-repo/blob/main/catalog-info.yaml', - ], - }); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); - }); - - it('apply refresh call on modified files from push event when catalogPath contains a glob pattern', async () => { - const schedule = new PersistingTaskRunner(); - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', - catalogPath: '**/catalog-info.yaml', - }, - }, - }, - }); - - const provider = GithubEntityProvider.fromConfig(config, { - logger, - schedule, - })[0]; - - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - await provider.connect(entityProviderConnection); - - const event: EventParams = { - topic: 'github.push', - metadata: { - 'x-github-event': 'push', - }, - eventPayload: { - ref: 'refs/heads/main', - repository: { - name: 'teste-1', - url: 'https://github.com/test-org/test-repo', - default_branch: 'main', - stargazers: 0, - master_branch: 'main', - organization: 'test-org', - topics: [], - }, - created: true, - deleted: false, - forced: false, - commits: [ - { - added: ['new-file.yaml'], - removed: [], - modified: [], - }, - { - added: [], - removed: [], - modified: ['catalog-info.yaml'], - }, - ], - }, - }; - - await provider.onEvent(event); - - expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(1); - expect(entityProviderConnection.refresh).toHaveBeenCalledWith({ - keys: [ - 'url:https://github.com/test-org/test-repo/tree/main/catalog-info.yaml', - 'url:https://github.com/test-org/test-repo/blob/main/catalog-info.yaml', - 'url:https://github.com/test-org/test-repo/tree/main/**/catalog-info.yaml', - ], - }); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); - }); - - it('should process repository when match filters from push event', async () => { - const schedule = new PersistingTaskRunner(); - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', + it('should process repository when match filters', async () => { + const config = createSingleProviderConfig({ + providerConfig: { filters: { branch: 'my-special-branch', repository: 'test-repo', }, }, - }, - }, - }); + }); + const provider = createProviders(config)[0]; - const provider = GithubEntityProvider.fromConfig(config, { - logger, - schedule, - })[0]; + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - await provider.connect(entityProviderConnection); + const event = createPushEvent({ ref: 'refs/heads/my-special-branch' }); - const event: EventParams = { - topic: 'github.push', - metadata: { - 'x-github-event': 'push', - }, - eventPayload: { - ref: 'refs/heads/my-special-branch', - repository: { - name: 'test-repo', - url: 'https://github.com/test-org/test-repo', - default_branch: 'main', - stargazers: 0, - master_branch: 'main', - organization: 'test-org', - topics: [], - }, - created: true, - deleted: false, - forced: false, - commits: [ - { - added: ['new-file.yaml'], - removed: [], - modified: [], - }, - { - added: [], - removed: [], - modified: ['catalog-info.yaml'], - }, - ], - }, - }; + await provider.onEvent(event); - await provider.onEvent(event); + expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.refresh).toHaveBeenCalledWith({ + keys: [ + 'url:https://github.com/test-org/test-repo/tree/my-special-branch/catalog-info.yaml', + 'url:https://github.com/test-org/test-repo/blob/my-special-branch/catalog-info.yaml', + ], + }); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); + }); - expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(1); - expect(entityProviderConnection.refresh).toHaveBeenCalledWith({ - keys: [ - 'url:https://github.com/test-org/test-repo/tree/my-special-branch/catalog-info.yaml', - 'url:https://github.com/test-org/test-repo/blob/my-special-branch/catalog-info.yaml', - ], - }); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); - }); - - it("should skip process when didn't match filters from push event", async () => { - const schedule = new PersistingTaskRunner(); - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', + it("should skip process when didn't match filters", async () => { + const config = createSingleProviderConfig({ + providerConfig: { filters: { repository: 'only-special-repository', }, }, - }, - }, - }); + }); + const provider = createProviders(config)[0]; - const provider = GithubEntityProvider.fromConfig(config, { - logger, - schedule, - })[0]; + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - await provider.connect(entityProviderConnection); + const event = createPushEvent(); - const event: EventParams = { - topic: 'github.push', - metadata: { - 'x-github-event': 'push', - }, - eventPayload: { - ref: 'refs/heads/main', - repository: { - name: 'teste-1', - url: 'https://github.com/test-org/test-repo', - default_branch: 'main', - stargazers: 0, - master_branch: 'main', - organization: 'test-org', - topics: [], - }, - created: true, - deleted: false, - forced: false, - commits: [ - { - added: ['new-file.yaml'], - removed: [], - modified: [], - }, - { - added: [], - removed: [], - modified: ['catalog-info.yaml'], - }, - ], - }, - }; + await provider.onEvent(event); - await provider.onEvent(event); + expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(0); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); + }); - expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(0); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); - }); - - it("should skip process when didn't match org from push event", async () => { - const schedule = new PersistingTaskRunner(); - const config = new ConfigReader({ - catalog: { - providers: { - github: { - organization: 'test-org', + it("should skip process when didn't match org", async () => { + const config = createSingleProviderConfig({ + providerConfig: { filters: { branch: 'my-special-branch', repository: 'test-repo', }, }, - }, - }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createPushEvent({ + ref: 'refs/heads/my-special-branch', + org: 'other-org', + }); + + await provider.onEvent(event); + + expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(0); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); + }); }); - const provider = GithubEntityProvider.fromConfig(config, { - logger, - schedule, - })[0]; - - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - await provider.connect(entityProviderConnection); - - const event: EventParams = { - topic: 'github.push', - metadata: { - 'x-github-event': 'push', - }, - eventPayload: { - ref: 'refs/heads/my-special-branch', - repository: { + describe('on repository event', () => { + const createRepoEvent = ( + action: RepositoryEvent['action'], + ): EventParams => { + const repo = { name: 'test-repo', url: 'https://github.com/test-org/test-repo', default_branch: 'main', - stargazers: 0, master_branch: 'main', - organization: 'other-org', + organization: 'test-org', topics: [], - }, - created: true, - deleted: false, - forced: false, - commits: [ - { - added: ['new-file.yaml'], - removed: [], - modified: [], + archived: action === 'archived', + private: action !== 'publicized', + } as Partial; + + const event = { + action, + repository: repo as RepositoryEvent['repository'], + } as RepositoryEvent; + + if (action === 'renamed') { + (event as RepositoryRenamedEvent).changes = { + repository: { + name: { + from: `old-${event.repository.name}`, + }, + }, + }; + } + + return { + topic: 'github.repository', + metadata: { + 'x-github-event': 'repository', }, - { + eventPayload: event, + }; + }; + + describe('on repository archived event', () => { + it('skip on non-matching org', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + organization: 'other-org', + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('archived'); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 0, + ); + }); + + it('apply delta update removing entities', async () => { + const config = createSingleProviderConfig(); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('archived'); + const expectedEntities = createExpectedEntitiesForEvent(event); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 1, + ); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', added: [], + removed: expectedEntities, + }); + }); + }); + + describe('on repository created event', () => { + it('skip', async () => { + const config = createSingleProviderConfig(); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('created'); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 0, + ); + }); + }); + + describe('on repository deleted event', () => { + it('skip on non-matching org', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + organization: 'other-org', + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('deleted'); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 0, + ); + }); + + it('apply delta update removing entities', async () => { + const config = createSingleProviderConfig(); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('deleted'); + const expectedEntities = createExpectedEntitiesForEvent(event); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 1, + ); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', + added: [], + removed: expectedEntities, + }); + }); + }); + + describe('on repository edited event', () => { + it('skip on non-matching org', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + organization: 'other-org', + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('edited'); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 0, + ); + }); + + it('apply delta update removing entities with non-matching filters', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + filters: { + topic: { + include: ['backstage-include'], + }, + }, + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('edited'); + const expectedEntities = createExpectedEntitiesForEvent(event); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 1, + ); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', + added: [], + removed: expectedEntities, + }); + }); + + it('apply no delta update with matching filters', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + filters: { + topic: { + include: ['backstage-include'], + }, + }, + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('edited'); + event.eventPayload.repository.topics = ['backstage-include']; + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 0, + ); + }); + }); + + describe('on repository privatized event', () => { + it('skip', async () => { + const config = createSingleProviderConfig(); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('privatized'); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 0, + ); + }); + }); + + describe('on repository publicized event', () => { + it('skip', async () => { + const config = createSingleProviderConfig(); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('publicized'); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 0, + ); + }); + }); + + describe('on repository renamed event', () => { + it('skip on non-matching org', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + organization: 'other-org', + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('renamed'); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 0, + ); + }); + + it('apply delta update removing entities with non-matching filters', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + filters: { + repository: 'other-.*', + }, + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent( + 'renamed', + ) as EventParams; + const urlOldRepo = `https://github.com/${event.eventPayload.repository.organization}/${event.eventPayload.changes.repository.name.from}/blob/main/catalog-info.yaml`; + const expectedEntitiesRemoved = + createExpectedEntitiesForUrl(urlOldRepo); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 1, + ); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', + added: [], + removed: expectedEntitiesRemoved, + }); + }); + + it('apply delta update removing and adding entities with matching filters', async () => { + const config = createSingleProviderConfig(); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent( + 'renamed', + ) as EventParams; + const urlOldRepo = `https://github.com/${event.eventPayload.repository.organization}/${event.eventPayload.changes.repository.name.from}/blob/main/catalog-info.yaml`; + const expectedEntitiesRemoved = + createExpectedEntitiesForUrl(urlOldRepo); + const expectedEntitiesAdded = createExpectedEntitiesForEvent(event); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 2, + ); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', + added: [], + removed: expectedEntitiesRemoved, + }); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', + added: expectedEntitiesAdded, removed: [], - modified: ['catalog-info.yaml'], - }, - ], - }, - }; + }); + }); + }); - await provider.onEvent(event); + describe('on repository transferred event', () => { + it('skip on non-matching org', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + organization: 'other-org', + }, + }); + const provider = createProviders(config)[0]; - await provider.onEvent(event); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); - expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(0); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0); + const event = createRepoEvent('transferred'); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 0, + ); + }); + + it('apply no delta update with non-matching filters', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + filters: { + topic: { + include: ['backstage-include'], + }, + }, + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('transferred'); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 0, + ); + }); + + it('apply delta update adding entities with matching filters', async () => { + const config = createSingleProviderConfig(); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('transferred'); + const expectedEntitiesAdded = createExpectedEntitiesForEvent(event); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 1, + ); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', + added: expectedEntitiesAdded, + removed: [], + }); + }); + }); + + describe('on repository unarchived event', () => { + it('skip on non-matching org', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + organization: 'other-org', + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('unarchived'); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 0, + ); + }); + + it('apply no delta update with non-matching filters', async () => { + const config = createSingleProviderConfig({ + providerConfig: { + filters: { + topic: { + include: ['backstage-include'], + }, + }, + }, + }); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('unarchived'); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 0, + ); + }); + + it('apply delta update adding entities with matching filters', async () => { + const config = createSingleProviderConfig(); + const provider = createProviders(config)[0]; + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + await provider.connect(entityProviderConnection); + + const event = createRepoEvent('unarchived'); + const expectedEntitiesAdded = createExpectedEntitiesForEvent(event); + + await provider.onEvent(event); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes( + 1, + ); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'delta', + added: expectedEntitiesAdded, + removed: [], + }); + }); + }); + }); }); }); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index 978d9593c1..daade9660a 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -38,7 +38,11 @@ import { GithubEntityProviderConfig, readProviderConfigs, } from './GithubEntityProviderConfig'; -import { getOrganizationRepositories } from '../lib/github'; +import { + getOrganizationRepositories, + getOrganizationRepository, + RepositoryResponse, +} from '../lib/github'; import { satisfiesForkFilter, satisfiesTopicFilter, @@ -50,11 +54,21 @@ import { EventsService, EventSubscriber, } from '@backstage/plugin-events-node'; -import { Commit, PushEvent } from '@octokit/webhooks-types'; +import { + Commit, + PushEvent, + RepositoryArchivedEvent, + RepositoryDeletedEvent, + RepositoryEditedEvent, + RepositoryEvent, + RepositoryRenamedEvent, + RepositoryTransferredEvent, + RepositoryUnarchivedEvent, +} from '@octokit/webhooks-types'; import { Minimatch } from 'minimatch'; import { LoggerService } from '@backstage/backend-plugin-api'; -const TOPIC_REPO_PUSH = 'github.push'; +const EVENT_TOPICS = ['github.push', 'github.repository']; type Repository = { name: string; @@ -157,7 +171,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { this.connection = connection; await this.events?.subscribe({ id: this.getProviderName(), - topics: [TOPIC_REPO_PUSH], + topics: EVENT_TOPICS, onEvent: params => this.onEvent(params), }); return await this.scheduleFn(); @@ -194,15 +208,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { const targets = await this.findCatalogFiles(); const matchingTargets = this.matchesFilters(targets); - const entities = matchingTargets - .map(repository => this.createLocationUrl(repository)) - .map(GithubEntityProvider.toLocationSpec) - .map(location => { - return { - locationKey: this.getProviderName(), - entity: locationSpecToLocationEntity({ location }), - }; - }); + const entities = this.toDeferredEntitiesFromRepos(matchingTargets); await this.connection.applyMutation({ type: 'full', @@ -214,38 +220,32 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { ); } - // go to the server and get all of the repositories - private async findCatalogFiles(): Promise { + private async createGraphqlClient() { const organization = this.config.organization; const host = this.integration.host; - const catalogPath = this.config.catalogPath; const orgUrl = `https://${host}/${organization}`; const { headers } = await this.githubCredentialsProvider.getCredentials({ url: orgUrl, }); - const client = graphql.defaults({ + return graphql.defaults({ baseUrl: this.integration.apiBaseUrl, headers, }); + } + + // go to the server and get all repositories + private async findCatalogFiles(): Promise { + const organization = this.config.organization; + const catalogPath = this.config.catalogPath; + const client = await this.createGraphqlClient(); const { repositories: repositoriesFromGithub } = await getOrganizationRepositories(client, organization, catalogPath); - const repositories = repositoriesFromGithub.map(r => { - return { - url: r.url, - name: r.name, - defaultBranchRef: r.defaultBranchRef?.name, - repositoryTopics: r.repositoryTopics.nodes.map(t => t.topic.name), - isArchived: r.isArchived, - isFork: r.isFork, - isCatalogInfoFilePresent: - r.catalogInfoFile?.__typename === 'Blob' && - r.catalogInfoFile.text !== '', - visibility: r.visibility, - }; - }); + const repositories = repositoriesFromGithub.map( + this.createRepoFromGithubResponse, + ); if (this.config.validateLocationsExist) { return repositories.filter( @@ -256,13 +256,13 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { return repositories; } - private matchesFilters(repositories: Repository[]) { + private matchesFilters(repositories: Repository[]): Repository[] { const repositoryFilter = this.config.filters?.repository; const topicFilters = this.config.filters?.topic; const allowForks = this.config.filters?.allowForks ?? true; const visibilities = this.config.filters?.visibility ?? []; - const matchingRepositories = repositories.filter(r => { + return repositories.filter(r => { const repoTopics: string[] = r.repositoryTopics; return ( !r.isArchived && @@ -273,7 +273,6 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { r.defaultBranchRef ); }); - return matchingRepositories; } private createLocationUrl(repository: Repository): string { @@ -296,23 +295,34 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { /** {@inheritdoc @backstage/plugin-events-node#EventSubscriber.onEvent} */ async onEvent(params: EventParams): Promise { this.logger.debug(`Received event from ${params.topic}`); - if (params.topic !== TOPIC_REPO_PUSH) { - return; - } + if (EVENT_TOPICS.some(topic => topic === params.topic)) { + if (!this.connection) { + throw new Error('Not initialized'); + } - await this.onRepoPush(params.eventPayload as PushEvent); + switch (params.topic) { + case 'github.push': + await this.onPush(params.eventPayload as PushEvent); + return; + + case 'github.repository': + await this.onRepoChange(params.eventPayload as RepositoryEvent); + return; + + default: // should never be reached + this.logger.warn( + `Missing implementation for event of topic ${params.topic}`, + ); + } + } } /** {@inheritdoc @backstage/plugin-events-node#EventSubscriber.supportsEventTopics} */ supportsEventTopics(): string[] { - return [TOPIC_REPO_PUSH]; + return EVENT_TOPICS; } - private async onRepoPush(event: PushEvent) { - if (!this.connection) { - throw new Error('Not initialized'); - } - + private async onPush(event: PushEvent) { if (this.config.organization !== event.repository.organization) { this.logger.debug( `skipping push event from organization ${event.repository.organization}`, @@ -332,18 +342,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { return; } - const repository: Repository = { - url: event.repository.url, - name: event.repository.name, - defaultBranchRef: event.repository.default_branch, - repositoryTopics: event.repository.topics, - isArchived: event.repository.archived, - isFork: event.repository.fork, - // we can consider this file present because - // only the catalog file will be recovered from the commits - isCatalogInfoFilePresent: true, - visibility: event.repository.visibility, - }; + const repository = this.createRepoFromEvent(event); const matchingTargets = this.matchesFilters([repository]); if (matchingTargets.length === 0) { @@ -378,7 +377,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { ? this.config.catalogPath.substring(1) : this.config.catalogPath; - await this.connection.refresh({ + await this.connection!.refresh({ keys: [ ...new Set([ ...modified.map( @@ -396,7 +395,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } if (added.length > 0 || removed.length > 0) { - await this.connection.applyMutation({ + await this.connection!.applyMutation({ type: 'delta', added: added, removed: removed, @@ -408,6 +407,263 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { ); } + private async onRepoChange(event: RepositoryEvent) { + if (this.config.organization !== event.repository.organization) { + this.logger.debug( + `skipping repository event from organization ${event.repository.organization}`, + ); + return; + } + + const action = event.action; + switch (action) { + case 'archived': + await this.onRepoArchived(event as RepositoryArchivedEvent); + return; + + // A repository was created. + case 'created': + // skip these events + return; + + case 'deleted': + await this.onRepoDeleted(event as RepositoryDeletedEvent); + return; + + case 'edited': + await this.onRepoEdited(event as RepositoryEditedEvent); + return; + + // The visibility of a repository was changed to `private`. + case 'privatized': + // skip these events + return; + + // The visibility of a repository was changed to `public`. + case 'publicized': + // skip these events + return; + + case 'renamed': + await this.onRepoRenamed(event as RepositoryRenamedEvent); + return; + + case 'transferred': + await this.onRepoTransferred(event as RepositoryTransferredEvent); + return; + + case 'unarchived': + await this.onRepoUnarchived(event as RepositoryUnarchivedEvent); + return; + + default: // should never be reached + this.logger.warn( + `Missing implementation for event of topic repository with action ${action}`, + ); + } + } + + /** + * A repository was archived. + * + * Removes all entities associated with the repository. + * + * @param event - The repository archived event. + * @private + */ + private async onRepoArchived(event: RepositoryArchivedEvent) { + const repository = this.createRepoFromEvent(event); + await this.removeEntitiesForRepo(repository); + this.logger.debug( + `Removed entities for archived repository ${repository.name}`, + ); + } + + /** + * A repository was deleted. + * + * Removes all entities associated with the repository. + * + * @param event - The repository deleted event. + * @private + */ + private async onRepoDeleted(event: RepositoryDeletedEvent) { + const repository = this.createRepoFromEvent(event); + await this.removeEntitiesForRepo(repository); + this.logger.debug( + `Removed entities for deleted repository ${repository.name}`, + ); + } + + /** + * The topics, default branch, description, or homepage of a repository was changed. + * + * We are interested in potential topic changes as these can be used as part of the filters. + * + * Removes all entities associated with the repository if the repository no longer matches the filters. + * + * @param event - The repository edited event. + * @private + */ + private async onRepoEdited(event: RepositoryEditedEvent) { + const repository = this.createRepoFromEvent(event); + + const matchingTargets = this.matchesFilters([repository]); + if (matchingTargets.length === 0) { + await this.removeEntitiesForRepo(repository); + } + // else: repository is (still) matching the filters, so we don't need to do anything + } + + /** + * The name of a repository was changed. + * + * Removes all entities associated with the repository's old name. + * Creates new entities for the repository's new name if it still matches the filters. + * + * @param event - The repository renamed event. + * @private + */ + private async onRepoRenamed(event: RepositoryRenamedEvent) { + const repository = this.createRepoFromEvent(event); + const oldRepoName = event.changes.repository.name.from; + const urlParts = event.repository.url.split('/'); + urlParts[urlParts.length - 1] = oldRepoName; + const oldRepoUrl = urlParts.join('/'); + const oldRepository: Repository = { + ...repository, + name: oldRepoName, + url: oldRepoUrl, + }; + await this.removeEntitiesForRepo(oldRepository); + + const matchingTargets = this.matchesFilters([repository]); + if (matchingTargets.length === 0) { + this.logger.debug( + `skipping repository transferred event for repository ${repository.name} because it didn't match provider filters`, + ); + return; + } + + await this.addEntitiesForRepo(repository); + } + + /** + * Ownership of the repository was transferred to a user or organization account. + * This event is only sent to the account where the ownership is transferred. + * To receive the `repository.transferred` event, the new owner account must have the GitHub App installed, + * and the App must be subscribed to "Repository" events. + * + * Creates new entities for the repository if it matches the filters. + * + * @param event - The repository unarchived event. + * @private + */ + private async onRepoTransferred(event: RepositoryTransferredEvent) { + const repository = this.createRepoFromEvent(event); + + const matchingTargets = this.matchesFilters([repository]); + if (matchingTargets.length === 0) { + this.logger.debug( + `skipping repository transferred event for repository ${repository.name} because it didn't match provider filters`, + ); + return; + } + + await this.addEntitiesForRepo(repository); + } + + /** + * A previously archived repository was unarchived. + * + * Creates new entities for the repository if it matches the filters. + * + * @param event - The repository unarchived event. + * @private + */ + private async onRepoUnarchived(event: RepositoryUnarchivedEvent) { + const repository = this.createRepoFromEvent(event); + + const matchingTargets = this.matchesFilters([repository]); + if (matchingTargets.length === 0) { + this.logger.debug( + `skipping repository transferred event for repository ${repository.name} because it didn't match provider filters`, + ); + return; + } + + await this.addEntitiesForRepo(repository); + } + + private async removeEntitiesForRepo(repository: Repository) { + const removed = this.toDeferredEntitiesFromRepos([repository]); + await this.connection!.applyMutation({ + type: 'delta', + added: [], + removed: removed, + }); + } + + private async addEntitiesForRepo(repository: Repository) { + if (this.config.validateLocationsExist) { + const organization = this.config.organization; + const catalogPath = this.config.catalogPath; + const client = await this.createGraphqlClient(); + + const repositoryFromGithub = await getOrganizationRepository( + client, + organization, + repository.name, + catalogPath, + ).then(r => (r ? this.createRepoFromGithubResponse(r) : null)); + + if (!repositoryFromGithub?.isCatalogInfoFilePresent) { + return; + } + } + + const added = this.toDeferredEntitiesFromRepos([repository]); + await this.connection!.applyMutation({ + type: 'delta', + added: added, + removed: [], + }); + } + + private createRepoFromEvent(event: RepositoryEvent | PushEvent): Repository { + return { + url: event.repository.url, + name: event.repository.name, + defaultBranchRef: event.repository.default_branch, + repositoryTopics: event.repository.topics, + isArchived: event.repository.archived, + isFork: event.repository.fork, + // we can consider this file present because + // only the catalog file will be recovered from the commits + isCatalogInfoFilePresent: true, + visibility: event.repository.visibility, + }; + } + + private createRepoFromGithubResponse( + repositoryResponse: RepositoryResponse, + ): Repository { + return { + url: repositoryResponse.url, + name: repositoryResponse.name, + defaultBranchRef: repositoryResponse.defaultBranchRef?.name, + repositoryTopics: repositoryResponse.repositoryTopics.nodes.map( + t => t.topic.name, + ), + isArchived: repositoryResponse.isArchived, + isFork: repositoryResponse.isFork, + isCatalogInfoFilePresent: + repositoryResponse.catalogInfoFile?.__typename === 'Blob' && + repositoryResponse.catalogInfoFile.text !== '', + visibility: repositoryResponse.visibility, + }; + } + private collectDeferredEntitiesFromCommit( repositoryUrl: string, branch: string, @@ -454,6 +710,20 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { }; }); } + + private toDeferredEntitiesFromRepos( + repositories: Repository[], + ): DeferredEntity[] { + return repositories + .map(repository => this.createLocationUrl(repository)) + .map(GithubEntityProvider.toLocationSpec) + .map(location => { + return { + locationKey: this.getProviderName(), + entity: locationSpecToLocationEntity({ location }), + }; + }); + } } /* diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 3ce5fef0b4..8d1c8871ae 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.21 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-catalog-node@1.12.4 + +## 0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.21-next.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.0.4-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 0861d4ec92..16c492d849 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab-org", - "version": "0.0.4-next.0", + "version": "0.0.6-next.0", "description": "The gitlab-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 6fd86bb15d..c4e8f25903 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.3.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.3.21 + +### Patch Changes + +- 8db30ad: The Gitlab configuration supports an additional optional boolean key `catalog.providers.gitlab..restrictUsersToGroup`. Setting this to `true` will make Backstage only import users from the group defined in the `group` key, instead of all users in the organisation (self-hosted) or of the root group (SaaS). It will default to false, keeping the original implementation intact, when not explicitly set. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/integration@1.13.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.3.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.3.20-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 51538cc178..ad5b8e88bd 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", - "version": "0.3.20-next.0", + "version": "0.3.22-next.0", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts index c1608cd386..d7ce338d6d 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts @@ -16,7 +16,7 @@ import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { LocationSpec } from '@backstage/plugin-catalog-node'; @@ -157,7 +157,7 @@ function getProcessor({ } describe('GitlabDiscoveryProcessor', () => { - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeAll(() => { jest.useFakeTimers(); diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts index 7d40eb1204..1eebe7d6e0 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts @@ -16,7 +16,7 @@ import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { readGitLabIntegrationConfig } from '@backstage/integration'; @@ -27,7 +27,7 @@ import { GitLabClient, paginated } from './client'; import { GitLabGroup, GitLabUser } from './types'; const server = setupServer(...handlers); -setupRequestMockHandlers(server); +registerMswTestHooks(server); describe('GitLabClient', () => { describe('isSelfManaged', () => { diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts index e89b378cfe..473e547a57 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts @@ -21,7 +21,7 @@ import { } from '@backstage/backend-tasks'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; @@ -32,7 +32,7 @@ import * as mock from '../__testUtils__/mocks'; import { GitlabDiscoveryEntityProvider } from './GitlabDiscoveryEntityProvider'; const server = setupServer(...handlers); -setupRequestMockHandlers(server); +registerMswTestHooks(server); afterEach(() => jest.clearAllMocks()); class PersistingTaskRunner implements TaskRunner { diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts index ddc0d03570..9706384ff0 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts @@ -21,7 +21,7 @@ import { } from '@backstage/backend-tasks'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; @@ -33,7 +33,7 @@ import { GroupNameTransformerOptions } from '../lib/types'; import { GitlabOrgDiscoveryEntityProvider } from './GitlabOrgDiscoveryEntityProvider'; const server = setupServer(...handlers); -setupRequestMockHandlers(server); +registerMswTestHooks(server); afterEach(() => jest.clearAllMocks()); class PersistingTaskRunner implements TaskRunner { diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index 04da7cc46e..c2b3835940 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.4.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-permission-common@0.8.0 + +## 0.4.27 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-catalog-backend@1.24.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.4.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-permission-common@0.7.14 + ## 0.4.26-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 2941fcd3f4..cabacc7bd6 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", - "version": "0.4.26-next.0", + "version": "0.4.28-next.0", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index b975322d0d..1253404d2d 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,60 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.25 + +## 0.7.0 + +### Minor Changes + +- cb32ca7: **BREAKING**: `readLdapOrg` and the `LdapProviderConfig` type now always accept arrays of user and group configs, not just single items. + + Added support for single ldap catalog provider to provide list and undefined user and group bindings next to standard single one. + +### Patch Changes + +- 083eaf9: Fix bug where ISO durations could no longer be used for schedules +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.7.0-next.1 + +### Minor Changes + +- cb32ca7: **BREAKING**: `readLdapOrg` and the `LdapProviderConfig` type now always accept arrays of user and group configs, not just single items. + + Added support for single ldap catalog provider to provide list and undefined user and group bindings next to standard single one. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + ## 0.6.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/api-report.md b/plugins/catalog-backend-module-ldap/api-report.md index de245b8cd6..2ee3c1ec79 100644 --- a/plugins/catalog-backend-module-ldap/api-report.md +++ b/plugins/catalog-backend-module-ldap/api-report.md @@ -197,8 +197,8 @@ export type LdapProviderConfig = { target: string; tls?: TLSConfig; bind?: BindConfig; - users: UserConfig; - groups: GroupConfig; + users: UserConfig[]; + groups: GroupConfig[]; schedule?: SchedulerServiceTaskScheduleDefinition; }; @@ -223,8 +223,8 @@ export function readLdapLegacyConfig(config: Config): LdapProviderConfig[]; // @public export function readLdapOrg( client: LdapClient, - userConfig: UserConfig, - groupConfig: GroupConfig, + userConfig: UserConfig[], + groupConfig: GroupConfig[], options: { groupTransformer?: GroupTransformer; userTransformer?: UserTransformer; diff --git a/plugins/catalog-backend-module-ldap/config.d.ts b/plugins/catalog-backend-module-ldap/config.d.ts index a585471033..65914fdf74 100644 --- a/plugins/catalog-backend-module-ldap/config.d.ts +++ b/plugins/catalog-backend-module-ldap/config.d.ts @@ -63,180 +63,352 @@ export interface Config { /** * The settings that govern the reading and interpretation of users. */ - users: { - /** - * The DN under which users are stored. - * - * E.g. "ou=people,ou=example,dc=example,dc=net" - */ - dn: string; - /** - * The search options to use. The default is scope "one" and - * attributes "*" and "+". - * - * It is common to want to specify a filter, to narrow down the set - * of matching items. - */ - options: { - scope?: 'base' | 'one' | 'sub'; - filter?: string; - attributes?: string | string[]; - sizeLimit?: number; - timeLimit?: number; - derefAliases?: number; - typesOnly?: boolean; - paged?: - | boolean - | { - pageSize?: number; - pagePause?: boolean; - }; - }; - /** - * JSON paths (on a.b.c form) and hard coded values to set on those - * paths. - * - * This can be useful for example if you want to hard code a - * namespace or similar on the generated entities. - */ - set?: { [key: string]: JsonValue }; - /** - * Mappings from well known entity fields, to LDAP attribute names - */ - map?: { - /** - * The name of the attribute that holds the relative - * distinguished name of each entry. Defaults to "uid". - */ - rdn?: string; - /** - * The name of the attribute that shall be used for the value of - * the metadata.name field of the entity. Defaults to "uid". - */ - name?: string; - /** - * The name of the attribute that shall be used for the value of - * the metadata.description field of the entity. - */ - description?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.profile.displayName field of the entity. Defaults to - * "cn". - */ - displayName?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.profile.email field of the entity. Defaults to - * "mail". - */ - email?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.profile.picture field of the entity. - */ - picture?: string; - /** - * The name of the attribute that shall be used for the values of - * the spec.memberOf field of the entity. Defaults to "memberOf". - */ - memberOf?: string; - }; - }; + users?: + | { + /** + * The DN under which users are stored. + * + * E.g. "ou=people,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The search options to use. The default is scope "one" and + * attributes "*" and "+". + * + * It is common to want to specify a filter, to narrow down the set + * of matching items. + */ + options: { + scope?: 'base' | 'one' | 'sub'; + filter?: string; + attributes?: string | string[]; + sizeLimit?: number; + timeLimit?: number; + derefAliases?: number; + typesOnly?: boolean; + paged?: + | boolean + | { + pageSize?: number; + pagePause?: boolean; + }; + }; + /** + * JSON paths (on a.b.c form) and hard coded values to set on those + * paths. + * + * This can be useful for example if you want to hard code a + * namespace or similar on the generated entities. + */ + set?: { [key: string]: JsonValue }; + /** + * Mappings from well known entity fields, to LDAP attribute names + */ + map?: { + /** + * The name of the attribute that holds the relative + * distinguished name of each entry. Defaults to "uid". + */ + rdn?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.name field of the entity. Defaults to "uid". + */ + name?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.description field of the entity. + */ + description?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.displayName field of the entity. Defaults to + * "cn". + */ + displayName?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.email field of the entity. Defaults to + * "mail". + */ + email?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.picture field of the entity. + */ + picture?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.memberOf field of the entity. Defaults to "memberOf". + */ + memberOf?: string; + }; + } + | Array<{ + /** + * The DN under which users are stored. + * + * E.g. "ou=people,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The search options to use. The default is scope "one" and + * attributes "*" and "+". + * + * It is common to want to specify a filter, to narrow down the set + * of matching items. + */ + options: { + scope?: 'base' | 'one' | 'sub'; + filter?: string; + attributes?: string | string[]; + sizeLimit?: number; + timeLimit?: number; + derefAliases?: number; + typesOnly?: boolean; + paged?: + | boolean + | { + pageSize?: number; + pagePause?: boolean; + }; + }; + /** + * JSON paths (on a.b.c form) and hard coded values to set on those + * paths. + * + * This can be useful for example if you want to hard code a + * namespace or similar on the generated entities. + */ + set?: { [key: string]: JsonValue }; + /** + * Mappings from well known entity fields, to LDAP attribute names + */ + map?: { + /** + * The name of the attribute that holds the relative + * distinguished name of each entry. Defaults to "uid". + */ + rdn?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.name field of the entity. Defaults to "uid". + */ + name?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.description field of the entity. + */ + description?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.displayName field of the entity. Defaults to + * "cn". + */ + displayName?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.email field of the entity. Defaults to + * "mail". + */ + email?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.picture field of the entity. + */ + picture?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.memberOf field of the entity. Defaults to "memberOf". + */ + memberOf?: string; + }; + }>; /** * The settings that govern the reading and interpretation of groups. */ - groups: { - /** - * The DN under which groups are stored. - * - * E.g. "ou=people,ou=example,dc=example,dc=net" - */ - dn: string; - /** - * The search options to use. The default is scope "one" and - * attributes "*" and "+". - * - * It is common to want to specify a filter, to narrow down the set - * of matching items. - */ - options: { - scope?: 'base' | 'one' | 'sub'; - filter?: string; - attributes?: string | string[]; - sizeLimit?: number; - timeLimit?: number; - derefAliases?: number; - typesOnly?: boolean; - paged?: - | boolean - | { - pageSize?: number; - pagePause?: boolean; - }; - }; - /** - * JSON paths (on a.b.c form) and hard coded values to set on those - * paths. - * - * This can be useful for example if you want to hard code a - * namespace or similar on the generated entities. - */ - set?: { [key: string]: JsonValue }; - /** - * Mappings from well known entity fields, to LDAP attribute names - */ - map?: { - /** - * The name of the attribute that holds the relative - * distinguished name of each entry. Defaults to "cn". - */ - rdn?: string; - /** - * The name of the attribute that shall be used for the value of - * the metadata.name field of the entity. Defaults to "cn". - */ - name?: string; - /** - * The name of the attribute that shall be used for the value of - * the metadata.description field of the entity. Defaults to - * "description". - */ - description?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.type field of the entity. Defaults to "groupType". - */ - type?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.profile.displayName field of the entity. Defaults to - * "cn". - */ - displayName?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.profile.email field of the entity. - */ - email?: string; - /** - * The name of the attribute that shall be used for the value of - * the spec.profile.picture field of the entity. - */ - picture?: string; - /** - * The name of the attribute that shall be used for the values of - * the spec.parent field of the entity. Defaults to "memberOf". - */ - memberOf?: string; - /** - * The name of the attribute that shall be used for the values of - * the spec.children field of the entity. Defaults to "member". - */ - members?: string; - }; - }; + groups?: + | { + /** + * The DN under which groups are stored. + * + * E.g. "ou=people,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The search options to use. The default is scope "one" and + * attributes "*" and "+". + * + * It is common to want to specify a filter, to narrow down the set + * of matching items. + */ + options: { + scope?: 'base' | 'one' | 'sub'; + filter?: string; + attributes?: string | string[]; + sizeLimit?: number; + timeLimit?: number; + derefAliases?: number; + typesOnly?: boolean; + paged?: + | boolean + | { + pageSize?: number; + pagePause?: boolean; + }; + }; + /** + * JSON paths (on a.b.c form) and hard coded values to set on those + * paths. + * + * This can be useful for example if you want to hard code a + * namespace or similar on the generated entities. + */ + set?: { [key: string]: JsonValue }; + /** + * Mappings from well known entity fields, to LDAP attribute names + */ + map?: { + /** + * The name of the attribute that holds the relative + * distinguished name of each entry. Defaults to "cn". + */ + rdn?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.name field of the entity. Defaults to "cn". + */ + name?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.description field of the entity. Defaults to + * "description". + */ + description?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.type field of the entity. Defaults to "groupType". + */ + type?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.displayName field of the entity. Defaults to + * "cn". + */ + displayName?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.email field of the entity. + */ + email?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.picture field of the entity. + */ + picture?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.parent field of the entity. Defaults to "memberOf". + */ + memberOf?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.children field of the entity. Defaults to "member". + */ + members?: string; + }; + } + | Array<{ + /** + * The DN under which groups are stored. + * + * E.g. "ou=people,ou=example,dc=example,dc=net" + */ + dn: string; + /** + * The search options to use. The default is scope "one" and + * attributes "*" and "+". + * + * It is common to want to specify a filter, to narrow down the set + * of matching items. + */ + options: { + scope?: 'base' | 'one' | 'sub'; + filter?: string; + attributes?: string | string[]; + sizeLimit?: number; + timeLimit?: number; + derefAliases?: number; + typesOnly?: boolean; + paged?: + | boolean + | { + pageSize?: number; + pagePause?: boolean; + }; + }; + /** + * JSON paths (on a.b.c form) and hard coded values to set on those + * paths. + * + * This can be useful for example if you want to hard code a + * namespace or similar on the generated entities. + */ + set?: { [key: string]: JsonValue }; + /** + * Mappings from well known entity fields, to LDAP attribute names + */ + map?: { + /** + * The name of the attribute that holds the relative + * distinguished name of each entry. Defaults to "cn". + */ + rdn?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.name field of the entity. Defaults to "cn". + */ + name?: string; + /** + * The name of the attribute that shall be used for the value of + * the metadata.description field of the entity. Defaults to + * "description". + */ + description?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.type field of the entity. Defaults to "groupType". + */ + type?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.displayName field of the entity. Defaults to + * "cn". + */ + displayName?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.email field of the entity. + */ + email?: string; + /** + * The name of the attribute that shall be used for the value of + * the spec.profile.picture field of the entity. + */ + picture?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.parent field of the entity. Defaults to "memberOf". + */ + memberOf?: string; + /** + * The name of the attribute that shall be used for the values of + * the spec.children field of the entity. Defaults to "member". + */ + members?: string; + }; + }>; }>; }; diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index f719673682..e5f67d59c6 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "version": "0.6.2-next.0", + "version": "0.7.1-next.0", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts index 4edfaeebea..598bcc8839 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts @@ -42,38 +42,42 @@ describe('readLdapConfig', () => { id: 'default', target: 'target', bind: undefined, - users: { - dn: 'udn', - options: { - scope: 'one', - attributes: ['*', '+'], + users: [ + { + dn: 'udn', + options: { + scope: 'one', + attributes: ['*', '+'], + }, + set: undefined, + map: { + rdn: 'uid', + name: 'uid', + displayName: 'cn', + email: 'mail', + memberOf: 'memberOf', + }, }, - set: undefined, - map: { - rdn: 'uid', - name: 'uid', - displayName: 'cn', - email: 'mail', - memberOf: 'memberOf', + ], + groups: [ + { + dn: 'gdn', + options: { + scope: 'one', + attributes: ['*', '+'], + }, + set: undefined, + map: { + rdn: 'cn', + name: 'cn', + description: 'description', + type: 'groupType', + displayName: 'cn', + memberOf: 'memberOf', + members: 'member', + }, }, - }, - groups: { - dn: 'gdn', - options: { - scope: 'one', - attributes: ['*', '+'], - }, - set: undefined, - map: { - rdn: 'cn', - name: 'cn', - description: 'description', - type: 'groupType', - displayName: 'cn', - memberOf: 'memberOf', - members: 'member', - }, - }, + ], }, ]; expect(actual).toEqual(expected); @@ -111,38 +115,42 @@ describe('readLdapConfig', () => { frequency: { minutes: 3 }, timeout: { minutes: 1 }, }, - users: { - dn: 'udn', - options: { - scope: 'one', - attributes: ['*', '+'], + users: [ + { + dn: 'udn', + options: { + scope: 'one', + attributes: ['*', '+'], + }, + set: undefined, + map: { + rdn: 'uid', + name: 'uid', + displayName: 'cn', + email: 'mail', + memberOf: 'memberOf', + }, }, - set: undefined, - map: { - rdn: 'uid', - name: 'uid', - displayName: 'cn', - email: 'mail', - memberOf: 'memberOf', + ], + groups: [ + { + dn: 'gdn', + options: { + scope: 'one', + attributes: ['*', '+'], + }, + set: undefined, + map: { + rdn: 'cn', + name: 'cn', + description: 'description', + type: 'groupType', + displayName: 'cn', + memberOf: 'memberOf', + members: 'member', + }, }, - }, - groups: { - dn: 'gdn', - options: { - scope: 'one', - attributes: ['*', '+'], - }, - set: undefined, - map: { - rdn: 'cn', - name: 'cn', - description: 'description', - type: 'groupType', - displayName: 'cn', - memberOf: 'memberOf', - members: 'member', - }, - }, + ], }, ]; expect(actual).toEqual(expected); @@ -228,57 +236,61 @@ describe('readLdapConfig', () => { keys: '/tmp/keys.pem', certs: '/tmp/certs.pem', }, - users: { - dn: 'udn', - options: { - scope: 'base', - attributes: ['*'], - filter: 'f', - paged: true, - timeLimit: 42, - sizeLimit: 100, - derefAliases: 0, - typesOnly: false, - }, - set: { p: 'v' }, - map: { - rdn: 'u', - name: 'v', - description: 'd', - displayName: 'c', - email: 'm', - picture: 'p', - memberOf: 'm', - }, - }, - groups: { - dn: 'gdn', - options: { - scope: 'base', - attributes: ['*'], - filter: 'f', - paged: { - pageSize: 7, - pagePause: true, + users: [ + { + dn: 'udn', + options: { + scope: 'base', + attributes: ['*'], + filter: 'f', + paged: true, + timeLimit: 42, + sizeLimit: 100, + derefAliases: 0, + typesOnly: false, + }, + set: { p: 'v' }, + map: { + rdn: 'u', + name: 'v', + description: 'd', + displayName: 'c', + email: 'm', + picture: 'p', + memberOf: 'm', }, - timeLimit: 42, - sizeLimit: 100, - derefAliases: 1, - typesOnly: true, }, - set: { p: 'v' }, - map: { - rdn: 'u', - name: 'v', - description: 'd', - type: 't', - displayName: 'c', - email: 'm', - picture: 'p', - memberOf: 'm', - members: 'n', + ], + groups: [ + { + dn: 'gdn', + options: { + scope: 'base', + attributes: ['*'], + filter: 'f', + paged: { + pageSize: 7, + pagePause: true, + }, + timeLimit: 42, + sizeLimit: 100, + derefAliases: 1, + typesOnly: true, + }, + set: { p: 'v' }, + map: { + rdn: 'u', + name: 'v', + description: 'd', + type: 't', + displayName: 'c', + email: 'm', + picture: 'p', + memberOf: 'm', + members: 'n', + }, }, - }, + ], }, ]; expect(actual).toEqual(expected); @@ -316,7 +328,7 @@ describe('readLdapConfig', () => { const actual = readProviderConfigs(new ConfigReader(config)); const expected = '(|(cn=foo bar)(cn=bar))'; - expect(actual[0].users.options.filter).toEqual(expected); + expect(actual[0].users[0].options.filter).toEqual(expected); }); it('supports a dot nested set structure', () => { @@ -353,7 +365,9 @@ describe('readLdapConfig', () => { }; const actual = readProviderConfigs(new ConfigReader(config)); - expect(actual[0].users.set).toEqual({ 'metadata.annotations': { a: 'b' } }); + expect(actual[0].users[0].set).toEqual({ + 'metadata.annotations': { a: 'b' }, + }); }); it('throws on attempts to modify the set structure', () => { @@ -389,25 +403,77 @@ describe('readLdapConfig', () => { const actual = readProviderConfigs(new ConfigReader(config)); expect(() => { - (actual[0].users.set as any).y = 2; + (actual[0].users[0].set as any).y = 2; }).toThrowErrorMatchingInlineSnapshot( `"Cannot add property y, object is not extensible"`, ); expect(() => { - (actual[0].users.set as any).x.b = 2; + (actual[0].users[0].set as any).x.b = 2; }).toThrowErrorMatchingInlineSnapshot( `"Cannot add property b, object is not extensible"`, ); expect(() => { - (actual[0].groups.set as any).y = 2; + (actual[0].groups[0].set as any).y = 2; }).toThrowErrorMatchingInlineSnapshot( `"Cannot add property y, object is not extensible"`, ); expect(() => { - (actual[0].groups.set as any).x.b = 2; + (actual[0].groups[0].set as any).x.b = 2; }).toThrowErrorMatchingInlineSnapshot( `"Cannot add property b, object is not extensible"`, ); }); + + it('supports users/groups config as list', () => { + const config = { + catalog: { + providers: { + ldapOrg: { + default: { + target: 'target', + users: [ + { + dn: 'udn1', + }, + { + dn: 'udn2', + }, + ], + groups: [ + { + dn: 'gdn1', + }, + { + dn: 'gdn2', + }, + ], + }, + }, + }, + }, + }; + const actual = readProviderConfigs(new ConfigReader(config)); + + expect(actual[0].users).toHaveLength(2); + expect(actual[0].groups).toHaveLength(2); + }); + + it('supports users/groups config as undefined', () => { + const config = { + catalog: { + providers: { + ldapOrg: { + default: { + target: 'target', + }, + }, + }, + }, + }; + const actual = readProviderConfigs(new ConfigReader(config)); + + expect(actual[0].users).toHaveLength(0); + expect(actual[0].groups).toHaveLength(0); + }); }); diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index 7c4795fc46..d7e1035545 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -42,9 +42,9 @@ export type LdapProviderConfig = { // command is not issued. bind?: BindConfig; // The settings that govern the reading and interpretation of users - users: UserConfig; + users: UserConfig[]; // The settings that govern the reading and interpretation of groups - groups: GroupConfig; + groups: GroupConfig[]; // Schedule configuration for refresh tasks. schedule?: SchedulerServiceTaskScheduleDefinition; }; @@ -161,34 +161,33 @@ export type GroupConfig = { }; }; -const defaultConfig = { - users: { - options: { - scope: 'one', - attributes: ['*', '+'], - }, - map: { - rdn: 'uid', - name: 'uid', - displayName: 'cn', - email: 'mail', - memberOf: 'memberOf', - }, +const defaultUserConfig = { + options: { + scope: 'one', + attributes: ['*', '+'], }, - groups: { - options: { - scope: 'one', - attributes: ['*', '+'], - }, - map: { - rdn: 'cn', - name: 'cn', - description: 'description', - displayName: 'cn', - type: 'groupType', - memberOf: 'memberOf', - members: 'member', - }, + map: { + rdn: 'uid', + name: 'uid', + displayName: 'cn', + email: 'mail', + memberOf: 'memberOf', + }, +}; + +const defaultGroupConfig = { + options: { + scope: 'one', + attributes: ['*', '+'], + }, + map: { + rdn: 'cn', + name: 'cn', + description: 'description', + displayName: 'cn', + type: 'groupType', + memberOf: 'memberOf', + members: 'member', }, }; @@ -272,9 +271,7 @@ function readSetConfig( return c.get(); } -function readUserMapConfig( - c: Config | undefined, -): Partial { +function readUserMapConfig(c: Config | undefined): Partial { if (!c) { return {}; } @@ -292,7 +289,7 @@ function readUserMapConfig( function readGroupMapConfig( c: Config | undefined, -): Partial { +): Partial { if (!c) { return {}; } @@ -311,8 +308,18 @@ function readGroupMapConfig( } function readUserConfig( - c: Config, + c: Config | Config[] | undefined, ): RecursivePartial { + if (!c) { + return []; + } + if (Array.isArray(c)) { + return c.map(it => readSingleUserConfig(it)); + } + return [readSingleUserConfig(c)]; +} + +function readSingleUserConfig(c: Config): RecursivePartial { return { dn: c.getString('dn'), options: readOptionsConfig(c.getOptionalConfig('options')), @@ -322,8 +329,18 @@ function readUserConfig( } function readGroupConfig( - c: Config, + c: Config | Config[] | undefined, ): RecursivePartial { + if (!c) { + return []; + } + if (Array.isArray(c)) { + return c.map(it => readSingleGroupConfig(it)); + } + return [readSingleGroupConfig(c)]; +} + +function readSingleGroupConfig(c: Config): RecursivePartial { return { dn: c.getString('dn'), options: readOptionsConfig(c.getOptionalConfig('options')), @@ -352,14 +369,15 @@ export function readLdapLegacyConfig(config: Config): LdapProviderConfig[] { target: trimEnd(c.getString('target'), '/'), tls: readTlsConfig(c.getOptionalConfig('tls')), bind: readBindConfig(c.getOptionalConfig('bind')), - users: readUserConfig(c.getConfig('users')), - groups: readGroupConfig(c.getConfig('groups')), + users: readUserConfig(c.getConfig('users')).map(it => { + return mergeWith({}, defaultUserConfig, it, replaceArraysIfPresent); + }), + groups: readGroupConfig(c.getConfig('groups')).map(it => { + return mergeWith({}, defaultGroupConfig, it, replaceArraysIfPresent); + }), }; - const merged = mergeWith({}, defaultConfig, newConfig, (_into, from) => { - // Replace arrays instead of merging, otherwise default behavior - return Array.isArray(from) ? from : undefined; - }); - return freeze(merged) as LdapProviderConfig; + + return freeze(newConfig) as LdapProviderConfig; }); } @@ -385,19 +403,36 @@ export function readProviderConfigs(config: Config): LdapProviderConfig[] { ) : undefined; + const isUserList = Array.isArray(c.getOptional('users')); + const isGroupList = Array.isArray(c.getOptional('groups')); + const newConfig = { id, target: trimEnd(c.getString('target'), '/'), tls: readTlsConfig(c.getOptionalConfig('tls')), bind: readBindConfig(c.getOptionalConfig('bind')), - users: readUserConfig(c.getConfig('users')), - groups: readGroupConfig(c.getConfig('groups')), + users: readUserConfig( + isUserList + ? c.getOptionalConfigArray('users') + : c.getOptionalConfig('users'), + ).map(it => { + return mergeWith({}, defaultUserConfig, it, replaceArraysIfPresent); + }), + groups: readGroupConfig( + isGroupList + ? c.getOptionalConfigArray('groups') + : c.getOptionalConfig('groups'), + ).map(it => { + return mergeWith({}, defaultGroupConfig, it, replaceArraysIfPresent); + }), schedule, }; - const merged = mergeWith({}, defaultConfig, newConfig, (_into, from) => { - // Replace arrays instead of merging, otherwise default behavior - return Array.isArray(from) ? from : undefined; - }); - return freeze(merged) as LdapProviderConfig; + + return freeze(newConfig) as LdapProviderConfig; }); } + +function replaceArraysIfPresent(_into: any, from: any) { + // Replace arrays instead of merging, otherwise default behavior + return Array.isArray(from) ? from : undefined; +} diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts index fc451fde97..1c3dfb179a 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts @@ -99,19 +99,21 @@ describe('readLdapUsers', () => { }), ); }); - const config: UserConfig = { - dn: 'ddd', - options: {}, - map: { - rdn: 'uid', - name: 'uid', - description: 'description', - displayName: 'cn', - email: 'mail', - picture: 'avatarUrl', - memberOf: 'memberOf', + const config: UserConfig[] = [ + { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + memberOf: 'memberOf', + }, }, - }; + ]; const { users, userMemberOf } = await readLdapUsers(client, config); expect(users).toEqual([ expect.objectContaining({ @@ -160,19 +162,21 @@ describe('readLdapUsers', () => { }), ); }); - const config: UserConfig = { - dn: 'ddd', - options: {}, - map: { - rdn: 'uid', - name: 'uid', - description: 'description', - displayName: 'cn', - email: 'mail', - picture: 'avatarUrl', - memberOf: 'memberOf', + const config: UserConfig[] = [ + { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + memberOf: 'memberOf', + }, }, - }; + ]; const { users, userMemberOf } = await readLdapUsers(client, config); expect(users).toEqual([ expect.objectContaining({ @@ -216,19 +220,21 @@ describe('readLdapUsers', () => { }), ); }); - const config: UserConfig = { - dn: 'ddd', - options: {}, - map: { - rdn: 'uid', - name: 'uid', - description: 'description', - displayName: 'cn', - email: 'mail', - picture: 'avatarUrl', - memberOf: 'memberOf', + const config: UserConfig[] = [ + { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + memberOf: 'memberOf', + }, }, - }; + ]; const { users, userMemberOf } = await readLdapUsers(client, config); expect(users).toEqual([ expect.objectContaining({ @@ -255,6 +261,58 @@ describe('readLdapUsers', () => { new Map([['dn-value', new Set(['x', 'y', 'z'])]]), ); }); + it('can process a list of UserConfigs', async () => { + client.getVendor.mockResolvedValue(DefaultLdapVendor); + client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { + await fn( + searchEntry({ + uid: ['uid-value'], + description: ['description-value'], + cn: ['cn-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + entryDN: ['dn-value'], + entryUUID: ['uuid-value'], + }), + ); + }); + const config: UserConfig[] = [ + { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + memberOf: 'memberOf', + }, + }, + { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + memberOf: 'memberOf', + }, + }, + ]; + const { users } = await readLdapUsers(client, config); + expect(users).toHaveLength(2); + }); + it('can process no UserConfigs', async () => { + const config: UserConfig[] = []; + const { users } = await readLdapUsers(client, config); + expect(users).toHaveLength(0); + }); }); describe('readLdapGroups', () => { @@ -282,21 +340,23 @@ describe('readLdapGroups', () => { }), ); }); - const config: GroupConfig = { - dn: 'ddd', - options: {}, - map: { - rdn: 'cn', - name: 'cn', - description: 'description', - displayName: 'cn', - email: 'mail', - picture: 'avatarUrl', - type: 'tt', - memberOf: 'memberOf', - members: 'member', + const config: GroupConfig[] = [ + { + dn: 'ddd', + options: {}, + map: { + rdn: 'cn', + name: 'cn', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + type: 'tt', + memberOf: 'memberOf', + members: 'member', + }, }, - }; + ]; const { groups, groupMember, groupMemberOf } = await readLdapGroups( client, config, @@ -353,21 +413,23 @@ describe('readLdapGroups', () => { }), ); }); - const config: GroupConfig = { - dn: 'ddd', - options: {}, - map: { - rdn: 'cn', - name: 'cn', - description: 'description', - displayName: 'cn', - email: 'mail', - picture: 'avatarUrl', - type: 'tt', - memberOf: 'memberOf', - members: 'member', + const config: GroupConfig[] = [ + { + dn: 'ddd', + options: {}, + map: { + rdn: 'cn', + name: 'cn', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + type: 'tt', + memberOf: 'memberOf', + members: 'member', + }, }, - }; + ]; const { groups, groupMember, groupMemberOf } = await readLdapGroups( client, config, @@ -401,6 +463,65 @@ describe('readLdapGroups', () => { new Map([['dn-value', new Set(['x', 'y', 'z'])]]), ); }); + + it('can process a list of GroupConfigs', async () => { + client.getVendor.mockResolvedValue(DefaultLdapVendor); + client.searchStreaming.mockImplementation(async (_dn, _opts, fn) => { + await fn( + searchEntry({ + cn: ['cn-value'], + description: ['description-value'], + tt: ['type-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + member: ['e', 'f', 'g'], + entryDN: ['dn-value'], + entryUUID: ['uuid-value'], + }), + ); + }); + const config: GroupConfig[] = [ + { + dn: 'ddd', + options: {}, + map: { + rdn: 'cn', + name: 'cn', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + type: 'tt', + memberOf: 'memberOf', + members: 'member', + }, + }, + { + dn: 'ddd', + options: {}, + map: { + rdn: 'cn', + name: 'cn', + description: 'description', + displayName: 'cn', + email: 'mail', + picture: 'avatarUrl', + type: 'tt', + memberOf: 'memberOf', + members: 'member', + }, + }, + ]; + const { groups } = await readLdapGroups(client, config); + expect(groups).toHaveLength(2); + }); + + it('can process no GroupConfigs', async () => { + const config: GroupConfig[] = []; + const { groups } = await readLdapGroups(client, config); + expect(groups).toHaveLength(0); + }); }); describe('resolveRelations', () => { diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts index 50036b94b0..9e6e11a0bc 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -104,32 +104,36 @@ export async function defaultUserTransformer( */ export async function readLdapUsers( client: LdapClient, - config: UserConfig, + config: UserConfig[], opts?: { transformer?: UserTransformer }, ): Promise<{ users: UserEntity[]; // With all relations empty userMemberOf: Map>; // DN -> DN or UUID of groups }> { - const { dn, options, map } = config; - const vendor = await client.getVendor(); - + if (config.length === 0) { + return { users: [], userMemberOf: new Map() }; + } const entities: UserEntity[] = []; const userMemberOf: Map> = new Map(); + const vendor = await client.getVendor(); const transformer = opts?.transformer ?? defaultUserTransformer; - await client.searchStreaming(dn, options, async user => { - const entity = await transformer(vendor, config, user); + for (const cfg of config) { + const { dn, options, map } = cfg; + await client.searchStreaming(dn, options, async user => { + const entity = await transformer(vendor, cfg, user); - if (!entity) { - return; - } + if (!entity) { + return; + } - mapReferencesAttr(user, vendor, map.memberOf, (myDn, vs) => { - ensureItems(userMemberOf, myDn, vs); + mapReferencesAttr(user, vendor, map.memberOf, (myDn, vs) => { + ensureItems(userMemberOf, myDn, vs); + }); + entities.push(entity); }); - entities.push(entity); - }); + } return { users: entities, userMemberOf }; } @@ -206,7 +210,7 @@ export async function defaultGroupTransformer( */ export async function readLdapGroups( client: LdapClient, - config: GroupConfig, + config: GroupConfig[], opts?: { transformer?: GroupTransformer; }, @@ -215,35 +219,40 @@ export async function readLdapGroups( groupMemberOf: Map>; // DN -> DN or UUID of groups groupMember: Map>; // DN -> DN or UUID of groups & users }> { + if (config.length === 0) { + return { groups: [], groupMemberOf: new Map(), groupMember: new Map() }; + } const groups: GroupEntity[] = []; const groupMemberOf: Map> = new Map(); const groupMember: Map> = new Map(); - const { dn, map, options } = config; const vendor = await client.getVendor(); - const transformer = opts?.transformer ?? defaultGroupTransformer; - await client.searchStreaming(dn, options, async entry => { - if (!entry) { - return; - } + for (const cfg of config) { + const { dn, map, options } = cfg; - const entity = await transformer(vendor, config, entry); + await client.searchStreaming(dn, options, async entry => { + if (!entry) { + return; + } - if (!entity) { - return; - } + const entity = await transformer(vendor, cfg, entry); - mapReferencesAttr(entry, vendor, map.memberOf, (myDn, vs) => { - ensureItems(groupMemberOf, myDn, vs); + if (!entity) { + return; + } + + mapReferencesAttr(entry, vendor, map.memberOf, (myDn, vs) => { + ensureItems(groupMemberOf, myDn, vs); + }); + mapReferencesAttr(entry, vendor, map.members, (myDn, vs) => { + ensureItems(groupMember, myDn, vs); + }); + + groups.push(entity); }); - mapReferencesAttr(entry, vendor, map.members, (myDn, vs) => { - ensureItems(groupMember, myDn, vs); - }); - - groups.push(entity); - }); + } return { groups, @@ -264,8 +273,8 @@ export async function readLdapGroups( */ export async function readLdapOrg( client: LdapClient, - userConfig: UserConfig, - groupConfig: GroupConfig, + userConfig: UserConfig[], + groupConfig: GroupConfig[], options: { groupTransformer?: GroupTransformer; userTransformer?: UserTransformer; diff --git a/plugins/catalog-backend-module-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md index 7e5ae51a99..83053dcf6b 100644 --- a/plugins/catalog-backend-module-logs/CHANGELOG.md +++ b/plugins/catalog-backend-module-logs/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-catalog-backend-module-logs +## 0.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.0.1 + +### Patch Changes + +- 97caf55: Creates a new module to make logging catalog errors simple. This module subscribes to catalog events and logs them. + + See [Backstage documentation](https://backstage.io/docs/features/software-catalog/configuration#subscribing-to-catalog-errors) for details on how to install + and configure the plugin. + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-catalog-backend@1.24.0 + +## 0.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.0.1-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index e0d2e46c78..0c4a78dfa4 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-logs", - "version": "0.0.1-next.0", + "version": "0.0.2-next.0", "description": "A module that subscribes to catalog releated events and logs them.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 0f74e4591b..d9b8da87d2 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.5.31-next.0 + +### Patch Changes + +- 58dff4d: Added option to ingest groups based on their group membership in Azure Entra ID +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.25 + +## 0.5.30 + +### Patch Changes + +- f7bdcea: Adds a dynamic provider for the plugin-catalog-backend-module-msgraph. Configuration is now runtime configurable through the ProviderConfigTransformer. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.5.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + ## 0.5.29-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/api-report-alpha.md b/plugins/catalog-backend-module-msgraph/api-report-alpha.md index 9b769dba1e..84f56a99bf 100644 --- a/plugins/catalog-backend-module-msgraph/api-report-alpha.md +++ b/plugins/catalog-backend-module-msgraph/api-report-alpha.md @@ -7,6 +7,7 @@ import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { GroupTransformer } from '@backstage/plugin-catalog-backend-module-msgraph'; import { OrganizationTransformer } from '@backstage/plugin-catalog-backend-module-msgraph'; +import { ProviderConfigTransformer } from '@backstage/plugin-catalog-backend-module-msgraph'; import { UserTransformer } from '@backstage/plugin-catalog-backend-module-msgraph'; // @alpha @@ -26,6 +27,11 @@ export interface MicrosoftGraphOrgEntityProviderTransformsExtensionPoint { | OrganizationTransformer | Record, ): void; + setProviderConfigTransformer( + transformer: + | ProviderConfigTransformer + | Record, + ): void; setUserTransformer( transformer: UserTransformer | Record, ): void; diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index 86170e2910..57f50fbd18 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -39,10 +39,10 @@ export function defaultUserTransformer( // @public export type GroupMember = | (MicrosoftGraph.Group & { - '@odata.type': '#microsoft.graph.user'; + '@odata.type': '#microsoft.graph.group'; }) | (MicrosoftGraph.User & { - '@odata.type': '#microsoft.graph.group'; + '@odata.type': '#microsoft.graph.user'; }); // @public @@ -126,6 +126,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { userTransformer?: UserTransformer; groupTransformer?: GroupTransformer; organizationTransformer?: OrganizationTransformer; + providerConfigTransformer?: ProviderConfigTransformer; }); // (undocumented) connect(connection: EntityProviderConnection): Promise; @@ -145,6 +146,7 @@ export interface MicrosoftGraphOrgEntityProviderLegacyOptions { id: string; logger: LoggerService; organizationTransformer?: OrganizationTransformer; + providerConfigTransformer?: ProviderConfigTransformer; schedule: 'manual' | TaskRunner; target: string; userTransformer?: UserTransformer; @@ -162,6 +164,9 @@ export type MicrosoftGraphOrgEntityProviderOptions = organizationTransformer?: | OrganizationTransformer | Record; + providerConfigTransformer?: + | ProviderConfigTransformer + | Record; }; // @public @deprecated @@ -210,6 +215,7 @@ export type MicrosoftGraphProviderConfig = { groupFilter?: string; groupSearch?: string; groupSelect?: string[]; + groupIncludeSubGroups?: boolean; queryMode?: 'basic' | 'advanced'; loadUserPhotos?: boolean; schedule?: TaskScheduleDefinition; @@ -233,6 +239,11 @@ export type OrganizationTransformer = ( organization: MicrosoftGraph.Organization, ) => Promise; +// @public +export type ProviderConfigTransformer = ( + provider: MicrosoftGraphProviderConfig, +) => Promise; + // @public @deprecated export function readMicrosoftGraphConfig( config: Config, @@ -253,6 +264,7 @@ export function readMicrosoftGraphOrg( groupSearch?: string; groupFilter?: string; groupSelect?: string[]; + groupIncludeSubGroups?: boolean; queryMode?: 'basic' | 'advanced'; userTransformer?: UserTransformer; groupTransformer?: GroupTransformer; diff --git a/plugins/catalog-backend-module-msgraph/config.d.ts b/plugins/catalog-backend-module-msgraph/config.d.ts index 5a1f56a03f..a470c075f0 100644 --- a/plugins/catalog-backend-module-msgraph/config.d.ts +++ b/plugins/catalog-backend-module-msgraph/config.d.ts @@ -192,6 +192,11 @@ export interface Config { * E.g. ["id", "displayName", "description"] */ select?: string[]; + /** + * Whether to ingest groups that are members of the found/filtered/searched groups. + * Default value is `false`. + */ + includeSubGroups?: boolean; }; userGroupMember?: { diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 7ba2b7e1e6..087d4c4a82 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "version": "0.5.29-next.0", + "version": "0.5.31-next.0", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts index 6d63537b54..624b6e42b0 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts @@ -15,7 +15,7 @@ */ import { TokenCredential } from '@azure/identity'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { MicrosoftGraphClient } from './client'; @@ -27,7 +27,7 @@ describe('MicrosoftGraphClient', () => { let client: MicrosoftGraphClient; const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); beforeEach(() => { tokenCredential.getToken.mockResolvedValue({ diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts index 1233b1e2f9..c190e2a959 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts @@ -64,8 +64,8 @@ export type ODataQuery = { * @public */ export type GroupMember = - | (MicrosoftGraph.Group & { '@odata.type': '#microsoft.graph.user' }) - | (MicrosoftGraph.User & { '@odata.type': '#microsoft.graph.group' }); + | (MicrosoftGraph.Group & { '@odata.type': '#microsoft.graph.group' }) + | (MicrosoftGraph.User & { '@odata.type': '#microsoft.graph.user' }); /** * A HTTP Client that communicates with Microsoft Graph API. diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts index c0e9f61f29..0ec56f134d 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts @@ -174,6 +174,7 @@ describe('readProviderConfigs', () => { expand: 'member', filter: 'securityEnabled eq false', select: ['id', 'displayName', 'description'], + includeSubGroups: true, }, schedule: { frequency: 'PT30M', @@ -202,6 +203,7 @@ describe('readProviderConfigs', () => { groupExpand: 'member', groupSelect: ['id', 'displayName', 'description'], groupFilter: 'securityEnabled eq false', + groupIncludeSubGroups: true, schedule: { frequency: { minutes: 30 }, timeout: { diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts index 1545114e45..257a7d5736 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts @@ -116,6 +116,12 @@ export type MicrosoftGraphProviderConfig = { */ groupSelect?: string[]; + /** + * Whether to ingest groups that are members of the found/filtered/searched groups. + * Default value is `false`. + */ + groupIncludeSubGroups?: boolean; + /** * By default, the Microsoft Graph API only provides the basic feature set * for querying. Certain features are limited to advanced query capabilities @@ -292,6 +298,9 @@ export function readProviderConfig( const groupFilter = config.getOptionalString('group.filter'); const groupSearch = config.getOptionalString('group.search'); const groupSelect = config.getOptionalStringArray('group.select'); + const groupIncludeSubGroups = config.getOptionalBoolean( + 'group.includeSubGroups', + ); const queryMode = config.getOptionalString('queryMode'); if ( @@ -347,6 +356,7 @@ export function readProviderConfig( groupFilter, groupSearch, groupSelect, + groupIncludeSubGroups, queryMode, userGroupMemberFilter, userGroupMemberSearch, diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/index.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/index.ts index 4db3fb69c6..81f6f21ce2 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/index.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/index.ts @@ -39,4 +39,5 @@ export type { GroupTransformer, OrganizationTransformer, UserTransformer, + ProviderConfigTransformer, } from './types'; diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts index 8821ffd2db..b83597653f 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts @@ -90,6 +90,9 @@ describe('read microsoft graph', () => { yield { '@odata.type': '#microsoft.graph.group', id: 'childgroupid', + displayName: 'Child Group Name', + description: 'Child Group Description', + mail: 'childgroup@example.com', }; yield { '@odata.type': '#microsoft.graph.user', @@ -770,6 +773,117 @@ describe('read microsoft graph', () => { top: 999, }); }); + + it('should read groups and their sub groups', async () => { + async function* getExampleGroupMembersForSubGroup(): AsyncIterable { + yield { + '@odata.type': '#microsoft.graph.user', + id: 'userid2', + }; + } + + client.getGroups.mockImplementation(getExampleGroups); + client.getGroupMembers.mockImplementationOnce(getExampleGroupMembers); + client.getGroupMembers.mockImplementationOnce( + getExampleGroupMembersForSubGroup, + ); + client.getOrganization.mockResolvedValue({ + id: 'tenantid', + displayName: 'Organization Name', + }); + client.getGroupPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + const { groups, groupMember, groupMemberOf, rootGroup } = + await readMicrosoftGraphGroups(client, 'tenantid', { + groupIncludeSubGroups: true, + }); + + const expectedRootGroup = group({ + metadata: { + annotations: { + 'graph.microsoft.com/tenant-id': 'tenantid', + }, + name: 'organization_name', + description: 'Organization Name', + }, + spec: { + type: 'root', + profile: { + displayName: 'Organization Name', + }, + children: [], + }, + }); + expect(groups).toEqual([ + expectedRootGroup, + group({ + metadata: { + annotations: { + 'graph.microsoft.com/group-id': 'childgroupid', + }, + name: 'child_group_name', + description: 'Child Group Description', + }, + spec: { + type: 'team', + profile: { + displayName: 'Child Group Name', + email: 'childgroup@example.com', + // TODO: Loading groups photos doesn't work right now as Microsoft + // Graph doesn't allows this yet + /* picture: 'data:image/jpeg;base64,...',*/ + }, + children: [], + }, + }), + group({ + metadata: { + annotations: { + 'graph.microsoft.com/group-id': 'groupid', + }, + name: 'group_name', + description: 'Group Description', + }, + spec: { + type: 'team', + profile: { + displayName: 'Group Name', + email: 'group@example.com', + // TODO: Loading groups photos doesn't work right now as Microsoft + // Graph doesn't allows this yet + /* picture: 'data:image/jpeg;base64,...',*/ + }, + children: [], + }, + }), + ]); + expect(rootGroup).toEqual(expectedRootGroup); + expect(groupMember.get('groupid')).toEqual(new Set(['childgroupid'])); + expect(groupMemberOf.get('userid')).toEqual(new Set(['groupid'])); + expect(groupMemberOf.get('userid2')).toEqual(new Set(['childgroupid'])); + expect(groupMember.get('organization_name')).toEqual(new Set()); + + expect(client.getGroups).toHaveBeenCalledTimes(1); + expect(client.getGroups).toHaveBeenCalledWith( + { + top: 999, + }, + undefined, + ); + expect(client.getGroupMembers).toHaveBeenCalledTimes(2); + expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', { + top: 999, + }); + expect(client.getGroupMembers).toHaveBeenCalledWith('childgroupid', { + top: 999, + }); + // TODO: Loading groups photos doesn't work right now as Microsoft Graph + // doesn't allows this yet + // expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1); + // expect(client.getGroupPhotoWithSizeLimit).toBeCalledWith('groupid', 120); + }); }); describe('resolveRelations', () => { diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 2ca21fca9c..60e5c3d79b 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -178,6 +178,7 @@ export async function readMicrosoftGraphGroups( groupFilter?: string; groupSearch?: string; groupSelect?: string[]; + groupIncludeSubGroups?: boolean; groupTransformer?: GroupTransformer; organizationTransformer?: OrganizationTransformer; }, @@ -244,6 +245,31 @@ export async function readMicrosoftGraphGroups( if (member['@odata.type'] === '#microsoft.graph.group') { ensureItem(groupMember, group.id!, member.id); + + if (options?.groupIncludeSubGroups) { + const groupMemberEntity = await transformer(member); + + if (groupMemberEntity) { + groups.push(groupMemberEntity); + + for await (const subMember of client.getGroupMembers( + member.id!, + { top: PAGE_SIZE }, + )) { + if (!subMember.id) { + continue; + } + + if (subMember['@odata.type'] === '#microsoft.graph.user') { + ensureItem(groupMemberOf, subMember.id, member.id!); + } + + if (subMember['@odata.type'] === '#microsoft.graph.group') { + ensureItem(groupMember, member.id!, subMember.id); + } + } + } + } } } @@ -377,6 +403,7 @@ export async function readMicrosoftGraphOrg( groupSearch?: string; groupFilter?: string; groupSelect?: string[]; + groupIncludeSubGroups?: boolean; queryMode?: 'basic' | 'advanced'; userTransformer?: UserTransformer; groupTransformer?: GroupTransformer; @@ -421,6 +448,7 @@ export async function readMicrosoftGraphOrg( groupFilter: options.groupFilter, groupSearch: options.groupSearch, groupSelect: options.groupSelect, + groupIncludeSubGroups: options.groupIncludeSubGroups, groupTransformer: options.groupTransformer, organizationTransformer: options.organizationTransformer, }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/types.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/types.ts index ef60d7b018..bc9270238b 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/types.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/types.ts @@ -16,7 +16,7 @@ import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; - +import { MicrosoftGraphProviderConfig } from './config'; /** * Customize the ingested User entity * @@ -45,3 +45,15 @@ export type GroupTransformer = ( group: MicrosoftGraph.Group, groupPhoto?: string, ) => Promise; + +/** + * Customize the MSGraph Provider Config Dynamically + * + * Transforming fields that are not used for each scheduled ingestion (e.g., id, schedule) + * will have no effect. + * + * @public + */ +export type ProviderConfigTransformer = ( + provider: MicrosoftGraphProviderConfig, +) => Promise; diff --git a/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.ts index 193f07889f..e1a15a3468 100644 --- a/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.ts @@ -23,6 +23,7 @@ import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/ import { GroupTransformer, OrganizationTransformer, + ProviderConfigTransformer, UserTransformer, } from '@backstage/plugin-catalog-backend-module-msgraph'; import { MicrosoftGraphOrgEntityProvider } from '../processors'; @@ -58,6 +59,18 @@ export interface MicrosoftGraphOrgEntityProviderTransformsExtensionPoint { | OrganizationTransformer | Record, ): void; + + /** + * Set the function that transforms provider config dynamically. + * Optionally, you can pass separate transformers per provider ID. + * Note: adjusting fields that are not used on each scheduled ingestion + * (e.g., id, schedule) will have no effect. + */ + setProviderConfigTransformer( + transformer: + | ProviderConfigTransformer + | Record, + ): void; } /** @@ -94,6 +107,10 @@ export const catalogModuleMicrosoftGraphOrgEntityProvider = createBackendModule( | OrganizationTransformer | Record | undefined; + let providerConfigTransformer: + | ProviderConfigTransformer + | Record + | undefined; env.registerExtensionPoint( microsoftGraphOrgEntityProviderTransformExtensionPoint, @@ -116,6 +133,12 @@ export const catalogModuleMicrosoftGraphOrgEntityProvider = createBackendModule( } organizationTransformer = transformer; }, + setProviderConfigTransformer(transformer) { + if (providerConfigTransformer) { + throw new Error('Provider transformer may only be set once'); + } + providerConfigTransformer = transformer; + }, }, ); @@ -134,6 +157,7 @@ export const catalogModuleMicrosoftGraphOrgEntityProvider = createBackendModule( userTransformer: userTransformer, groupTransformer: groupTransformer, organizationTransformer: organizationTransformer, + providerConfigTransformer: providerConfigTransformer, }), ); }, diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index 3c8b903e69..a019ac79a3 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -34,6 +34,7 @@ import { MICROSOFT_GRAPH_USER_ID_ANNOTATION, MicrosoftGraphClient, MicrosoftGraphProviderConfig, + ProviderConfigTransformer, OrganizationTransformer, readMicrosoftGraphConfig, readMicrosoftGraphOrg, @@ -94,6 +95,13 @@ export type MicrosoftGraphOrgEntityProviderOptions = organizationTransformer?: | OrganizationTransformer | Record; + + /** + * The function that transforms provider config dynamically. + */ + providerConfigTransformer?: + | ProviderConfigTransformer + | Record; }; /** @@ -152,6 +160,11 @@ export interface MicrosoftGraphOrgEntityProviderLegacyOptions { * The function that transforms an organization entry in msgraph to an entity. */ organizationTransformer?: OrganizationTransformer; + + /** + * The function that transforms provider config dynamically. + */ + providerConfigTransformer?: ProviderConfigTransformer; } /** @@ -216,6 +229,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { providerConfig.id, options.organizationTransformer, ), + providerConfigTransformer: getTransformer( + providerConfig.id, + options.providerConfigTransformer, + ), }); if (taskRunner !== 'manual') { @@ -257,6 +274,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { userTransformer: options.userTransformer, groupTransformer: options.groupTransformer, organizationTransformer: options.organizationTransformer, + providerConfigTransformer: options.providerConfigTransformer, logger, provider, }); @@ -276,6 +294,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { userTransformer?: UserTransformer; groupTransformer?: GroupTransformer; organizationTransformer?: OrganizationTransformer; + providerConfigTransformer?: ProviderConfigTransformer; }, ) {} @@ -300,7 +319,9 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { } const logger = options?.logger ?? this.options.logger; - const provider = this.options.provider; + const provider = this.options.providerConfigTransformer + ? await this.options.providerConfigTransformer(this.options.provider) + : this.options.provider; const { markReadComplete } = trackProgress(logger); const client = MicrosoftGraphClient.create(this.options.provider); const { users, groups } = await readMicrosoftGraphOrg( @@ -317,6 +338,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { groupFilter: provider.groupFilter, groupSearch: provider.groupSearch, groupSelect: provider.groupSelect, + groupIncludeSubGroups: provider.groupIncludeSubGroups, queryMode: provider.queryMode, groupTransformer: this.options.groupTransformer, userTransformer: this.options.userTransformer, diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index 8003dc39ac..46d8c1319b 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.25 + +## 0.1.40 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-backend@1.24.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 0.1.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.0-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + ## 0.1.39-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 7016701fc0..1cd250d884 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.1.39-next.0", + "version": "0.1.41-next.0", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index 4e5b25479a..d85ad0ecf2 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.1.28 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + ## 0.1.27-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 1759eebb14..dadba558a5 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", - "version": "0.1.27-next.0", + "version": "0.1.29-next.0", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-puppetdb/src/puppet/read.test.ts b/plugins/catalog-backend-module-puppetdb/src/puppet/read.test.ts index ac28f04503..8ad4c2b850 100644 --- a/plugins/catalog-backend-module-puppetdb/src/puppet/read.test.ts +++ b/plugins/catalog-backend-module-puppetdb/src/puppet/read.test.ts @@ -20,14 +20,14 @@ import { PuppetDbEntityProviderConfig, } from '../providers'; import { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { ANNOTATION_PUPPET_CERTNAME, ENDPOINT_FACTSETS } from './constants'; describe('readPuppetNodes', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); beforeEach(() => { jest.clearAllMocks(); diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md index df4e93de73..1ec7c01c6a 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-scaffolder-common@1.5.4 + +## 0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-scaffolder-common@1.5.4 + - @backstage/catalog-model@1.5.0 + +## 0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-scaffolder-common@1.5.3 + ## 0.1.19-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 a2891d2d4d..09dfa63a91 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model", - "version": "0.1.19-next.0", + "version": "0.1.21-next.0", "description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md index 6940a04db9..2abd4e32a3 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.3 + - @backstage/plugin-permission-common@0.8.0 + +## 0.4.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.3 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## 0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.2 + - @backstage/plugin-permission-common@0.7.14 + ## 0.4.8-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/README.md b/plugins/catalog-backend-module-unprocessed/README.md index 6b350e5ef8..f01347912d 100644 --- a/plugins/catalog-backend-module-unprocessed/README.md +++ b/plugins/catalog-backend-module-unprocessed/README.md @@ -17,7 +17,7 @@ yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-unproce In `packages/backend/src/index.ts` add the module: ```ts title="packages/backend/src/index.ts" -backend.add(catalogModuleUnprocessedEntities()); +backend.add(import('@backstage/plugin-catalog-backend-module-unprocessed')); ``` ### Legacy Backend diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 582be2ffe8..b01fddef04 100644 --- a/plugins/catalog-backend-module-unprocessed/package.json +++ b/plugins/catalog-backend-module-unprocessed/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-unprocessed", - "version": "0.4.8-next.0", + "version": "0.4.10-next.0", "description": "Backstage Catalog module to view unprocessed entities", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 16442df3c1..796611fa90 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,80 @@ # @backstage/plugin-catalog-backend +## 1.24.1-next.0 + +### Patch Changes + +- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-openapi-utils@0.1.16-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.0 + +## 1.24.0 + +### Minor Changes + +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/backend-openapi-utils@0.1.15 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-search-backend-module-catalog@0.1.28 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.24.0-next.1 + +### Minor Changes + +- b9ed1bb: bumped better-sqlite3 from ^9.0.0 to ^11.0.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-openapi-utils@0.1.15-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.28-next.1 + ## 1.23.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index d9bc71ae91..0c1983d1e2 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "1.23.2-next.0", + "version": "1.24.1-next.0", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin", @@ -110,7 +110,7 @@ "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "@types/uuid": "^9.0.0", - "better-sqlite3": "^9.0.0", + "better-sqlite3": "^11.0.0", "luxon": "^3.0.0", "msw": "^1.0.0", "supertest": "^6.1.3", diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts index dd80f8f0c4..7d1578db00 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts @@ -17,7 +17,7 @@ import { UrlReader, UrlReaders } from '@backstage/backend-common'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { rest } from 'msw'; @@ -39,7 +39,7 @@ describe('UrlReaderProcessor', () => { set: jest.fn(), }; const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts index 4c1673b785..aee65bd2e1 100644 --- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts +++ b/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts @@ -18,7 +18,7 @@ import { PluginEndpointDiscovery, TokenManager, } from '@backstage/backend-common'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { Entity } from '@backstage/catalog-model'; import { DefaultCatalogCollator } from './DefaultCatalogCollator'; import { setupServer } from 'msw/node'; @@ -62,7 +62,7 @@ describe('DefaultCatalogCollator', () => { let mockTokenManager: jest.Mocked; let collator: DefaultCatalogCollator; - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeAll(() => { mockDiscoveryApi = { getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007'), diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index aebd969701..269100e586 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -28,6 +28,8 @@ import { catalogPermissionExtensionPoint, CatalogProcessingExtensionPoint, catalogProcessingExtensionPoint, + CatalogLocationsExtensionPoint, + catalogLocationsExtensionPoint, } from '@backstage/plugin-catalog-node/alpha'; import { CatalogProcessor, @@ -41,6 +43,20 @@ import { merge } from 'lodash'; import { Permission } from '@backstage/plugin-permission-common'; import { ForwardedError } from '@backstage/errors'; +class CatalogLocationsExtensionPointImpl + implements CatalogLocationsExtensionPoint +{ + #locationTypes: string[] | undefined; + + setAllowedLocationTypes(locationTypes: Array) { + this.#locationTypes = locationTypes; + } + + get allowedLocationTypes() { + return this.#locationTypes; + } +} + class CatalogProcessingExtensionPointImpl implements CatalogProcessingExtensionPoint { @@ -199,6 +215,12 @@ export const catalogPlugin = createBackendPlugin({ const modelExtensions = new CatalogModelExtensionPointImpl(); env.registerExtensionPoint(catalogModelExtensionPoint, modelExtensions); + const locationTypeExtensions = new CatalogLocationsExtensionPointImpl(); + env.registerExtensionPoint( + catalogLocationsExtensionPoint, + locationTypeExtensions, + ); + env.registerInit({ deps: { logger: coreServices.logger, @@ -271,6 +293,12 @@ export const catalogPlugin = createBackendPlugin({ builder.addPermissionRules(...permissionExtensions.permissionRules); builder.setFieldFormatValidators(modelExtensions.fieldValidators); + if (locationTypeExtensions.allowedLocationTypes) { + builder.setAllowedLocationTypes( + locationTypeExtensions.allowedLocationTypes, + ); + } + const { processingEngine, router } = await builder.build(); lifecycle.addStartupHook(async () => { diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index fd3c714f97..e2859fcc83 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-common +## 1.0.25 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-search-common@1.2.13 + - @backstage/catalog-model@1.5.0 + ## 1.0.24 ### Patch Changes diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 0ea4ce5e3e..865dc02f4c 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-common", - "version": "1.0.24", + "version": "1.0.25", "description": "Common functionalities for the catalog plugin", "backstage": { "role": "common-library", diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index c632769632..5995074b53 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,58 @@ # @backstage/plugin-catalog-graph +## 0.4.8-next.0 + +### Patch Changes + +- fba7537: Memoize entity graph nodes when applying an `entityFilter` to prevent repeated redraws +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## 0.4.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## 0.4.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## 0.4.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/types@1.1.1 + ## 0.4.7-next.0 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 82f3e5d171..51ed923a01 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.4.7-next.0", + "version": "0.4.8-next.0", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-graph", diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts index af2a491ada..07e3085f1f 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { Entity } from '@backstage/catalog-model'; -import { useEffect } from 'react'; +import { useEffect, useMemo } from 'react'; import { useEntityStore } from './useEntityStore'; import { pickBy } from 'lodash'; @@ -104,9 +104,13 @@ export function useEntityRelationGraph({ requestEntities, ]); - const filteredEntities = entityFilter - ? pickBy(entities, (value, _key) => entityFilter(value)) - : entities; + const filteredEntities = useMemo( + () => + entityFilter + ? pickBy(entities, (value, _key) => entityFilter(value)) + : entities, + [entities, entityFilter], + ); return { entities: filteredEntities, diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index bbb56b6f08..484676f245 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,70 @@ # @backstage/plugin-catalog-import +## 0.12.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/plugin-catalog-common@1.0.25 + +## 0.12.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/integration@1.13.0 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/integration-react@1.1.29 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## 0.12.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## 0.12.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/integration@1.13.0-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-catalog-common@1.0.24 + ## 0.12.1-next.0 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 1082f08113..dfc07ddb43 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.12.1-next.0", + "version": "0.12.2-next.0", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/catalog-import/src/api/AzureRepoApiClient.test.ts b/plugins/catalog-import/src/api/AzureRepoApiClient.test.ts index a8fbe9db76..9f058dc744 100644 --- a/plugins/catalog-import/src/api/AzureRepoApiClient.test.ts +++ b/plugins/catalog-import/src/api/AzureRepoApiClient.test.ts @@ -26,7 +26,7 @@ import { RepoApiClient, } from './AzureRepoApiClient'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { registerMswTestHooks } from '@backstage/test-utils'; function mockRepoEndpoint() { return rest.get( @@ -153,7 +153,7 @@ function mockPrEndpoint() { describe('RepoApiClient', () => { const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); const testToken = new Date().toString(); const sut = new RepoApiClient({ project: 'project', diff --git a/plugins/catalog-import/src/api/CatalogImportClient.test.ts b/plugins/catalog-import/src/api/CatalogImportClient.test.ts index 1f16840c33..fa646e3ca4 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.test.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.test.ts @@ -57,7 +57,7 @@ import { ConfigReader, UrlPatternDiscovery } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { ScmAuthApi } from '@backstage/integration-react'; import { CatalogApi } from '@backstage/plugin-catalog-react'; -import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils'; +import { MockFetchApi, registerMswTestHooks } from '@backstage/test-utils'; import { Octokit } from '@octokit/rest'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -70,7 +70,7 @@ import { describe('CatalogImportClient', () => { const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); const mockBaseUrl = 'http://backstage:9191/api/catalog'; const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl); diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index f23bfa9917..3d1dbe07ba 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/plugin-catalog-node +## 1.12.5-next.0 + +### Patch Changes + +- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## 1.12.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.12.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + ## 1.12.3-next.0 ### Patch Changes diff --git a/plugins/catalog-node/api-report-alpha.md b/plugins/catalog-node/api-report-alpha.md index b61d1b1d27..e14f55c196 100644 --- a/plugins/catalog-node/api-report-alpha.md +++ b/plugins/catalog-node/api-report-alpha.md @@ -34,6 +34,14 @@ export interface CatalogAnalysisExtensionPoint { // @alpha (undocumented) export const catalogAnalysisExtensionPoint: ExtensionPoint; +// @alpha (undocumented) +export interface CatalogLocationsExtensionPoint { + setAllowedLocationTypes(locationTypes: Array): void; +} + +// @alpha (undocumented) +export const catalogLocationsExtensionPoint: ExtensionPoint; + // @alpha (undocumented) export interface CatalogModelExtensionPoint { setEntityDataParser(parser: CatalogProcessorParser): void; diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 293b915eea..04a942f7b3 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "1.12.3-next.0", + "version": "1.12.5-next.0", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library", diff --git a/plugins/catalog-node/src/alpha.ts b/plugins/catalog-node/src/alpha.ts index 91b5bb99b6..b75ec48a7f 100644 --- a/plugins/catalog-node/src/alpha.ts +++ b/plugins/catalog-node/src/alpha.ts @@ -15,6 +15,8 @@ */ export { catalogServiceRef } from './catalogService'; +export type { CatalogLocationsExtensionPoint } from './extensions'; +export { catalogLocationsExtensionPoint } from './extensions'; export type { CatalogProcessingExtensionPoint } from './extensions'; export { catalogProcessingExtensionPoint } from './extensions'; export type { CatalogAnalysisExtensionPoint } from './extensions'; diff --git a/plugins/catalog-node/src/extensions.ts b/plugins/catalog-node/src/extensions.ts index 7c1fe6c30d..a971e1588d 100644 --- a/plugins/catalog-node/src/extensions.ts +++ b/plugins/catalog-node/src/extensions.ts @@ -31,6 +31,25 @@ import { } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; +/** + * @alpha + */ +export interface CatalogLocationsExtensionPoint { + /** + * Allows setting custom location types, such as showcased in: https://backstage.io/docs/features/software-catalog/external-integrations/#creating-a-catalog-data-reader-processor + * @param locationTypes - List of location types to allow, default is "url" and "file" + */ + setAllowedLocationTypes(locationTypes: Array): void; +} + +/** + * @alpha + */ +export const catalogLocationsExtensionPoint = + createExtensionPoint({ + id: 'catalog.locations', + }); + /** * @alpha */ diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 892bfe306d..982d3db031 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,74 @@ # @backstage/plugin-catalog-react +## 1.12.3-next.0 + +### Patch Changes + +- 31bfc44: Updated alpha definitions of extension data references. +- 9b89b82: Internal refactor to remove unnecessary `routable` prop in the implementation of the `createEntityContentExtension` alpha export. +- bebd569: Fix extra divider displayed on user list picker component +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-react@0.4.24 + +## 1.12.2 + +### Patch Changes + +- 06c0956: Support i18n for catalog and catalog-react plugins +- 2030962: Make EntityOwnerPicker display metadata.title or spec.profile.displayName for mode=only-owners instead of metadata.name +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/core-components@0.14.9 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/integration-react@1.1.29 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.12.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/integration-react@1.1.29-next.0 + +## 1.12.2-next.1 + +### Patch Changes + +- 06c0956: Support i18n for catalog and catalog-react plugins +- Updated dependencies + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-react@0.4.23 + ## 1.12.2-next.0 ### Patch Changes diff --git a/plugins/catalog-react/api-report-alpha.md b/plugins/catalog-react/api-report-alpha.md index fb807d947d..1ffa67068f 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -13,17 +13,85 @@ import { PortableSchema } 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'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) export const catalogExtensionData: { - entityContentTitle: ConfigurableExtensionDataRef; + entityContentTitle: ConfigurableExtensionDataRef< + 'catalog.entity-content-title', + string, + {} + >; entityFilterFunction: ConfigurableExtensionDataRef< + 'catalog.entity-filter-function', (entity: Entity) => boolean, {} >; - entityFilterExpression: ConfigurableExtensionDataRef; + entityFilterExpression: ConfigurableExtensionDataRef< + 'catalog.entity-filter-expression', + string, + {} + >; }; +// @alpha (undocumented) +export const catalogReactTranslationRef: TranslationRef< + 'catalog-react', + { + readonly 'catalogFilter.title': 'Filters'; + readonly 'catalogFilter.buttonTitle': 'Filters'; + readonly 'entityKindPicker.title': 'Kind'; + readonly 'entityKindPicker.errorMessage': 'Failed to load entity kinds'; + readonly 'entityLifecyclePicker.title': 'Lifecycle'; + readonly 'entityNamespacePicker.title': 'Namespace'; + readonly 'entityOwnerPicker.title': 'Owner'; + readonly 'entityProcessingStatusPicker.title': 'Processing Status'; + readonly 'entityTagPicker.title': 'Tags'; + readonly 'entityPeekAheadPopover.title': 'Drill into the entity to see all of the tags.'; + readonly 'entityPeekAheadPopover.entityCardActionsTitle': 'Show details'; + readonly 'entityPeekAheadPopover.emailCardAction.title': 'Email {{email}}'; + readonly 'entityPeekAheadPopover.emailCardAction.subTitle': 'mailto {{email}}'; + readonly 'entitySearchBar.placeholder': 'Search'; + readonly 'entityTypePicker.title': 'Type'; + readonly 'entityTypePicker.errorMessage': 'Failed to load entity types'; + readonly 'entityTypePicker.optionAllTitle': 'all'; + readonly 'favoriteEntity.addToFavorites': 'Add to favorites'; + readonly 'favoriteEntity.removeFromFavorites': 'Remove from favorites'; + readonly 'inspectEntityDialog.title': 'Entity Inspector'; + readonly 'inspectEntityDialog.closeButtonTitle': 'Close'; + readonly 'inspectEntityDialog.ancestryPage.title': 'Ancestry'; + readonly 'inspectEntityDialog.colocatedPage.title': 'Colocated'; + readonly 'inspectEntityDialog.colocatedPage.description': 'These are the entities that are colocated with this entity - as in, they originated from the same data source (e.g. came from the same YAML file), or from the same origin (e.g. the originally registered URL).'; + readonly 'inspectEntityDialog.colocatedPage.alertNoLocation': 'Entity had no location information.'; + readonly 'inspectEntityDialog.colocatedPage.alertNoEntity': 'There were no other entities on this location.'; + readonly 'inspectEntityDialog.jsonPage.title': 'Entity as JSON'; + readonly 'inspectEntityDialog.jsonPage.description': 'This is the raw entity data as received from the catalog, on JSON form.'; + readonly 'inspectEntityDialog.overviewPage.title': 'Overview'; + readonly 'inspectEntityDialog.yamlPage.title': 'Entity as YAML'; + readonly 'inspectEntityDialog.yamlPage.description': 'This is the raw entity data as received from the catalog, on YAML form.'; + readonly 'unregisterEntityDialog.title': 'Are you sure you want to unregister this entity?'; + readonly 'unregisterEntityDialog.cancelButtonTitle': 'Cancel'; + readonly 'unregisterEntityDialog.deleteButtonTitle': 'Delete Entity'; + readonly 'unregisterEntityDialog.deleteEntitySuccessMessage': 'Removed entity {{entityName}}'; + readonly 'unregisterEntityDialog.onlyDeleteStateTitle': 'This entity does not seem to originate from a registered location. You therefore only have the option to delete it outright from the catalog.'; + readonly 'unregisterEntityDialog.errorStateTitle': 'Internal error: Unknown state'; + readonly 'unregisterEntityDialog.bootstrapState.title': 'You cannot unregister this entity, since it originates from a protected Backstage configuration (location "{{location}}"). If you believe this is in error, please contact the {{appTitle}} integrator.'; + readonly 'unregisterEntityDialog.bootstrapState.advancedDescription': 'You have the option to delete the entity itself from the catalog. Note that this should only be done if you know that the catalog file has been deleted at, or moved from, its origin location. If that is not the case, the entity will reappear shortly as the next refresh round is performed by the catalog.'; + readonly 'unregisterEntityDialog.bootstrapState.advancedOptions': 'Advanced Options'; + readonly 'unregisterEntityDialog.unregisterState.title': 'This action will unregister the following entities:'; + readonly 'unregisterEntityDialog.unregisterState.description': 'To undo, just re-register the entity in {{appTitle}}.'; + readonly 'unregisterEntityDialog.unregisterState.subTitle': 'Located at the following location:'; + readonly 'unregisterEntityDialog.unregisterState.advancedDescription': 'You also have the option to delete the entity itself from the catalog. Note that this should only be done if you know that the catalog file has been deleted at, or moved from, its origin location. If that is not the case, the entity will reappear shortly as the next refresh round is performed by the catalog.'; + readonly 'unregisterEntityDialog.unregisterState.advancedOptions': 'Advanced Options'; + readonly 'unregisterEntityDialog.unregisterState.unregisterButtonTitle': 'Unregister Location'; + readonly 'userListPicker.defaultOrgName': 'Company'; + readonly 'userListPicker.orgFilterAllLabel': 'All'; + readonly 'userListPicker.personalFilter.title': 'Personal'; + readonly 'userListPicker.personalFilter.ownedLabel': 'Owned'; + readonly 'userListPicker.personalFilter.starredLabel': 'Starred'; + } +>; + // @alpha (undocumented) export function createEntityCardExtension< TConfig extends { @@ -47,7 +115,7 @@ export function createEntityCardExtension< config: TConfig; inputs: Expand>; }) => Promise; -}): ExtensionDefinition; +}): ExtensionDefinition; // @alpha (undocumented) export function createEntityContentExtension< @@ -70,11 +138,18 @@ export function createEntityContentExtension< loader: (options: { inputs: Expand>; }) => Promise; -}): ExtensionDefinition<{ - title: string; - path: string; - filter?: string | undefined; -}>; +}): ExtensionDefinition< + { + title: string; + path: string; + filter?: string | undefined; + }, + { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + } +>; // @alpha export function isOwnerOf(owner: Entity, entity: Entity): boolean; diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 8267047b2d..dc5a80d4b7 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "1.12.2-next.0", + "version": "1.12.3-next.0", "description": "A frontend library that helps other Backstage plugins interact with the catalog", "backstage": { "role": "web-library", diff --git a/plugins/catalog-react/src/alpha.tsx b/plugins/catalog-react/src/alpha.tsx index f5f4dfdb74..e81b8e5ebf 100644 --- a/plugins/catalog-react/src/alpha.tsx +++ b/plugins/catalog-react/src/alpha.tsx @@ -32,18 +32,19 @@ import { Expand } from '../../../packages/frontend-plugin-api/src/types'; export { useEntityPermission } from './hooks/useEntityPermission'; export { isOwnerOf } from './utils'; +export * from './translation'; /** @alpha */ export const catalogExtensionData = { - entityContentTitle: createExtensionDataRef( - 'catalog.entity-content-title', - ), - entityFilterFunction: createExtensionDataRef<(entity: Entity) => boolean>( - 'catalog.entity-filter-function', - ), - entityFilterExpression: createExtensionDataRef( - 'catalog.entity-filter-expression', - ), + entityContentTitle: createExtensionDataRef().with({ + id: 'catalog.entity-content-title', + }), + entityFilterFunction: createExtensionDataRef< + (entity: Entity) => boolean + >().with({ id: 'catalog.entity-filter-function' }), + entityFilterExpression: createExtensionDataRef().with({ + id: 'catalog.entity-filter-expression', + }), }; // TODO: Figure out how to merge with provided config schema @@ -165,7 +166,7 @@ export function createEntityContentExtension< title: config.title, routeRef: options.routeRef, element: ( - + ), diff --git a/plugins/catalog-react/src/components/CatalogFilterLayout/CatalogFilterLayout.tsx b/plugins/catalog-react/src/components/CatalogFilterLayout/CatalogFilterLayout.tsx index 7583520412..ee51020cbb 100644 --- a/plugins/catalog-react/src/components/CatalogFilterLayout/CatalogFilterLayout.tsx +++ b/plugins/catalog-react/src/components/CatalogFilterLayout/CatalogFilterLayout.tsx @@ -23,6 +23,8 @@ import Typography from '@material-ui/core/Typography'; import useMediaQuery from '@material-ui/core/useMediaQuery'; import { Theme, useTheme } from '@material-ui/core/styles'; import FilterListIcon from '@material-ui/icons/FilterList'; +import { catalogReactTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export const Filters = (props: { @@ -37,6 +39,7 @@ export const Filters = (props: { ); const theme = useTheme(); const [filterDrawerOpen, setFilterDrawerOpen] = useState(false); + const { t } = useTranslationRef(catalogReactTranslationRef); return isScreenSmallerThanBreakpoint ? ( <> @@ -45,7 +48,7 @@ export const Filters = (props: { onClick={() => setFilterDrawerOpen(true)} startIcon={} > - Filters + {t('catalogFilter.buttonTitle')} - Filters + {t('catalogFilter.title')} {props.children} diff --git a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx index e953f21afb..68d5ecefdb 100644 --- a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx +++ b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx @@ -38,7 +38,9 @@ const useStyles = makeStyles( icon: { marginRight: theme.spacing(0.5), color: theme.palette.text.secondary, - lineHeight: 0, + '& svg': { + verticalAlign: 'middle', + }, }, }), { name: 'CatalogReactEntityDisplayName' }, diff --git a/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx b/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx index 2513f8dca9..612c515d28 100644 --- a/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx @@ -18,7 +18,7 @@ import { GetEntityFacetsResponse } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { ApiProvider } from '@backstage/core-app-api'; import { alertApiRef } from '@backstage/core-plugin-api'; -import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils'; +import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import { fireEvent, waitFor, screen, within } from '@testing-library/react'; import { capitalize } from 'lodash'; import { default as React } from 'react'; @@ -75,7 +75,7 @@ describe('', () => { ); it('renders available entity kinds', async () => { - await renderWithEffects( + await renderInTestApp( ', () => { it('sets the selected kind filter', async () => { const updateFilters = jest.fn(); - await renderWithEffects( + await renderInTestApp( ', () => { it('respects the query parameter filter value', async () => { const updateFilters = jest.fn(); const queryParameters = { kind: 'group' }; - await renderWithEffects( + await renderInTestApp( ', () => { }); it('renders unknown kinds provided in query parameters', async () => { - await renderWithEffects( + await renderInTestApp( ', () => { }); it('limits kinds when allowedKinds is set', async () => { - await renderWithEffects( + await renderInTestApp( @@ -183,7 +183,7 @@ describe('', () => { }); it('renders kind from the query parameter even when not in allowedKinds', async () => { - await renderWithEffects( + await renderInTestApp( { const { allowedKinds, hidden, initialFilter = 'component' } = props; + const { t } = useTranslationRef(catalogReactTranslationRef); const alertApi = useApi(alertApiRef); @@ -106,11 +109,11 @@ export const EntityKindPicker = (props: EntityKindPickerProps) => { useEffect(() => { if (error) { alertApi.post({ - message: `Failed to load entity kinds`, + message: t('entityKindPicker.errorMessage'), severity: 'error', }); } - }, [error, alertApi]); + }, [error, alertApi, t]); if (error) return null; @@ -124,7 +127,7 @@ export const EntityKindPicker = (props: EntityKindPickerProps) => { return hidden ? null : ( 1 ? selectedTypes[0] : undefined) ?? 'all'} onChange={value => diff --git a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx index fd0a656317..8848857976 100644 --- a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx +++ b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx @@ -22,6 +22,8 @@ import Star from '@material-ui/icons/Star'; import StarBorder from '@material-ui/icons/StarBorder'; import React, { ComponentProps } from 'react'; import { useStarredEntity } from '../../hooks/useStarredEntity'; +import { catalogReactTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export type FavoriteEntityProps = ComponentProps & { @@ -43,6 +45,7 @@ export const FavoriteEntity = (props: FavoriteEntityProps) => { const { toggleStarredEntity, isStarredEntity } = useStarredEntity( props.entity, ); + const { t } = useTranslationRef(catalogReactTranslationRef); return ( { onClick={() => toggleStarredEntity()} > {isStarredEntity ? : } diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/InspectEntityDialog.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/InspectEntityDialog.tsx index f155c3f164..644167acbb 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/InspectEntityDialog.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/InspectEntityDialog.tsx @@ -30,6 +30,8 @@ import { ColocatedPage } from './components/ColocatedPage'; import { JsonPage } from './components/JsonPage'; import { OverviewPage } from './components/OverviewPage'; import { YamlPage } from './components/YamlPage'; +import { catalogReactTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const useStyles = makeStyles(theme => ({ fullHeightDialog: { @@ -95,6 +97,7 @@ export function InspectEntityDialog(props: { }) { const classes = useStyles(); const [activeTab, setActiveTab] = React.useState(0); + const { t } = useTranslationRef(catalogReactTranslationRef); useEffect(() => { setActiveTab(0); @@ -114,7 +117,7 @@ export function InspectEntityDialog(props: { PaperProps={{ className: classes.fullHeightDialog }} > - Entity Inspector + {t('inspectEntityDialog.title')}
@@ -152,7 +155,7 @@ export function InspectEntityDialog(props: { diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx index e08bac06e8..98bd27bd77 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx @@ -38,6 +38,8 @@ import { catalogApiRef } from '../../../api'; import { humanizeEntityRef } from '../../EntityRefLink'; import { entityRouteRef } from '../../../routes'; import { EntityKindIcon } from './EntityKindIcon'; +import { catalogReactTranslationRef } from '../../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const useStyles = makeStyles(theme => ({ node: { @@ -198,6 +200,7 @@ function CustomNode({ node }: DependencyGraphTypes.RenderNodeProps) { export function AncestryPage(props: { entity: Entity }) { const { loading, error, nodes, edges } = useAncestry(props.entity); + const { t } = useTranslationRef(catalogReactTranslationRef); if (loading) { return ; } else if (error) { @@ -206,7 +209,9 @@ export function AncestryPage(props: { entity: Entity }) { return ( <> - Ancestry + + {t('inspectEntityDialog.ancestryPage.title')} + This is the ancestry of entities above the current one - as in, the chain(s) of entities down to the current one, where{' '} diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/ColocatedPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/ColocatedPage.tsx index 97bf0f3cb8..6f89ff2c9f 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/ColocatedPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/ColocatedPage.tsx @@ -32,6 +32,8 @@ import useAsync from 'react-use/esm/useAsync'; import { catalogApiRef } from '../../../api'; import { EntityRefLink } from '../../EntityRefLink'; import { KeyValueListItem, ListItemText } from './common'; +import { catalogReactTranslationRef } from '../../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const useStyles = makeStyles({ root: { @@ -95,6 +97,7 @@ function EntityList(props: { entities: Entity[]; header?: [string, string] }) { function Contents(props: { entity: Entity }) { const { entity } = props; + const { t } = useTranslationRef(catalogReactTranslationRef); const { loading, error, location, originLocation, colocatedEntities } = useColocated(entity); @@ -106,12 +109,14 @@ function Contents(props: { entity: Entity }) { if (!location && !originLocation) { return ( - Entity had no location information. + + {t('inspectEntityDialog.colocatedPage.alertNoLocation')} + ); } else if (!colocatedEntities?.length) { return ( - There were no other entities on this location. + {t('inspectEntityDialog.colocatedPage.alertNoEntity')} ); } @@ -148,13 +153,14 @@ function Contents(props: { entity: Entity }) { export function ColocatedPage(props: { entity: Entity }) { const classes = useStyles(); + const { t } = useTranslationRef(catalogReactTranslationRef); return ( <> - Colocated + + {t('inspectEntityDialog.colocatedPage.title')} + - These are the entities that are colocated with this entity - as in, they - originated from the same data source (e.g. came from the same YAML - file), or from the same origin (e.g. the originally registered URL). + {t('inspectEntityDialog.colocatedPage.description')}
diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/JsonPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/JsonPage.tsx index b6bda65229..af8a724729 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/JsonPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/JsonPage.tsx @@ -19,13 +19,18 @@ import { CodeSnippet } from '@backstage/core-components'; import DialogContentText from '@material-ui/core/DialogContentText'; import React from 'react'; import { sortKeys } from './util'; +import { catalogReactTranslationRef } from '../../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export function JsonPage(props: { entity: Entity }) { + const { t } = useTranslationRef(catalogReactTranslationRef); return ( <> - Entity as JSON + + {t('inspectEntityDialog.jsonPage.title')} + - This is the raw entity data as received from the catalog, on JSON form. + {t('inspectEntityDialog.jsonPage.description')}
diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx index ad742459de..361910b2cd 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx @@ -36,6 +36,8 @@ import { } from './common'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { CopyTextButton } from '@backstage/core-components'; +import { catalogReactTranslationRef } from '../../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const useStyles = makeStyles({ root: { @@ -59,11 +61,14 @@ export function OverviewPage(props: { entity: AlphaEntity }) { sortBy(relations, r => r.targetRef), 'type', ); + const { t } = useTranslationRef(catalogReactTranslationRef); const entityRef = stringifyEntityRef(props.entity); return ( <> - Overview + + {t('inspectEntityDialog.overviewPage.title')} +
diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/YamlPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/YamlPage.tsx index ad74611ec5..4e56b9b42a 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/YamlPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/YamlPage.tsx @@ -20,13 +20,18 @@ import DialogContentText from '@material-ui/core/DialogContentText'; import React from 'react'; import YAML from 'yaml'; import { sortKeys } from './util'; +import { catalogReactTranslationRef } from '../../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; export function YamlPage(props: { entity: Entity }) { + const { t } = useTranslationRef(catalogReactTranslationRef); return ( <> - Entity as YAML + + {t('inspectEntityDialog.yamlPage.title')} + - This is the raw entity data as received from the catalog, on YAML form. + {t('inspectEntityDialog.yamlPage.description')}
diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx index 7f41badca5..e2ed9387e1 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx @@ -32,6 +32,8 @@ import { useUnregisterEntityDialogState } from './useUnregisterEntityDialogState import { alertApiRef, configApiRef, useApi } from '@backstage/core-plugin-api'; import { Progress, ResponseErrorPanel } from '@backstage/core-components'; import { assertError } from '@backstage/errors'; +import { catalogReactTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const useStyles = makeStyles({ advancedButton: { @@ -58,6 +60,7 @@ const Contents = ({ const [showDelete, setShowDelete] = useState(false); const [busy, setBusy] = useState(false); const appTitle = configApi.getOptionalString('app.title') ?? 'Backstage'; + const { t } = useTranslationRef(catalogReactTranslationRef); const onUnregister = useCallback( async function onUnregisterFn() { @@ -86,7 +89,9 @@ const Contents = ({ const entityName = entity.metadata.title ?? entity.metadata.name; onConfirm(); alertApi.post({ - message: `Removed entity ${entityName}`, + message: t('unregisterEntityDialog.deleteEntitySuccessMessage', { + entityName, + }), severity: 'success', display: 'transient', }); @@ -98,13 +103,13 @@ const Contents = ({ } } }, - [alertApi, onConfirm, state, entity], + [alertApi, onConfirm, state, entity, t], ); const DialogActionsPanel = () => ( ); @@ -121,10 +126,10 @@ const Contents = ({ return ( <> - You cannot unregister this entity, since it originates from a - protected Backstage configuration (location "{state.location}"). If - you believe this is in error, please contact the {appTitle}{' '} - integrator. + {t('unregisterEntityDialog.bootstrapState.title', { + appTitle, + location: state.location, + })} @@ -137,7 +142,7 @@ const Contents = ({ className={classes.advancedButton} onClick={() => setShowDelete(true)} > - Advanced Options + {t('unregisterEntityDialog.bootstrapState.advancedOptions')} @@ -146,11 +151,7 @@ const Contents = ({ {showDelete && ( <> - You have the option to delete the entity itself from the - catalog. Note that this should only be done if you know that the - catalog file has been deleted at, or moved from, its origin - location. If that is not the case, the entity will reappear - shortly as the next refresh round is performed by the catalog. + {t('unregisterEntityDialog.bootstrapState.advancedDescription')} @@ -172,8 +173,7 @@ const Contents = ({ return ( <> - This entity does not seem to originate from a registered location. You - therefore only have the option to delete it outright from the catalog. + {t('unregisterEntityDialog.onlyDeleteStateTitle')} @@ -192,7 +192,7 @@ const Contents = ({ return ( <> - This action will unregister the following entities: + {t('unregisterEntityDialog.unregisterState.title')} {state.colocatedEntities.map(e => ( @@ -202,13 +202,15 @@ const Contents = ({ ))} - Located at the following location: + {t('unregisterEntityDialog.unregisterState.subTitle')}
  • {state.location}
  • - To undo, just re-register the entity in {appTitle}. + {t('unregisterEntityDialog.unregisterState.description', { + appTitle, + })} {!showDelete && ( @@ -228,7 +230,7 @@ const Contents = ({ className={classes.advancedButton} onClick={() => setShowDelete(true)} > - Advanced Options + {t('unregisterEntityDialog.unregisterState.advancedOptions')} )} @@ -240,11 +242,7 @@ const Contents = ({ - You also have the option to delete the entity itself from the - catalog. Note that this should only be done if you know that the - catalog file has been deleted at, or moved from, its origin - location. If that is not the case, the entity will reappear - shortly as the next refresh round is performed by the catalog. + {t('unregisterEntityDialog.unregisterState.advancedDescription')} )} @@ -260,7 +258,11 @@ const Contents = ({ ); } - return Internal error: Unknown state; + return ( + + {t('unregisterEntityDialog.errorStateTitle')} + + ); }; /** @public */ @@ -274,10 +276,11 @@ export type UnregisterEntityDialogProps = { /** @public */ export const UnregisterEntityDialog = (props: UnregisterEntityDialogProps) => { const { open, onConfirm, onClose, entity } = props; + const { t } = useTranslationRef(catalogReactTranslationRef); return ( - Are you sure you want to unregister this entity? + {t('unregisterEntityDialog.title')} diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx index 5667447503..0b101e8174 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import { fireEvent, render, waitFor, screen } from '@testing-library/react'; +import { fireEvent, waitFor, screen } from '@testing-library/react'; import { UserEntity } from '@backstage/catalog-model'; import { UserListPicker, UserListPickerProps } from './UserListPicker'; import { MockEntityListContextProvider } from '../../testUtils/providers'; @@ -30,7 +30,11 @@ import { QueryEntitiesInitialRequest, } from '@backstage/catalog-client'; import { catalogApiRef } from '../../api'; -import { MockStorageApi, TestApiRegistry } from '@backstage/test-utils'; +import { + MockStorageApi, + TestApiRegistry, + renderInTestApp, +} from '@backstage/test-utils'; import { ApiProvider } from '@backstage/core-app-api'; import { ConfigApi, @@ -145,7 +149,7 @@ describe('', () => { }); it('renders filter groups', async () => { - render( + await renderInTestApp( @@ -164,7 +168,7 @@ describe('', () => { }); it('renders filters', async () => { - render( + await renderInTestApp( ', () => { }); it('respects other frontend filters in counts', async () => { - render( + await renderInTestApp( ', () => { it('respects the query parameter filter value', async () => { const updateFilters = jest.fn(); const queryParameters = { user: 'owned', kind: 'component' }; - render( + await renderInTestApp( ', () => { it('updates user filter when a menuitem is selected', async () => { const updateFilters = jest.fn(); - render( + await renderInTestApp( ', () => { it('responds to external queryParameters changes', async () => { const updateFilters = jest.fn(); - const rendered = render( + const rendered = await renderInTestApp( ', () => { it('does not reset the filter while entities are loading', async () => { mockCatalogApi.queryEntities?.mockReturnValue(new Promise(() => {})); - render(); + await renderInTestApp(); await waitFor(() => expect(mockCatalogApi.queryEntities).toHaveBeenCalled(), @@ -433,7 +437,7 @@ describe('', () => { return mockQueryEntitiesImplementation(request); }); - render(); + await renderInTestApp(); await waitFor(() => expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(3), @@ -444,7 +448,7 @@ describe('', () => { }); it('does not reset the filter when request is empty', async () => { - render(); + await renderInTestApp(); await waitFor(() => { expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(1); @@ -473,7 +477,7 @@ describe('', () => { return mockQueryEntitiesImplementation(request); }); - render(); + await renderInTestApp(); await waitFor(() => expect(updateFilters).toHaveBeenLastCalledWith({ @@ -489,7 +493,7 @@ describe('', () => { () => new Promise(() => {}), ); - render(); + await renderInTestApp(); await waitFor(() => expect(mockCatalogApi.queryEntities).toHaveBeenCalled(), @@ -512,7 +516,7 @@ describe('', () => { return mockQueryEntitiesImplementation(request); }); - render(); + await renderInTestApp(); await waitFor(() => expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(3), @@ -537,7 +541,7 @@ describe('', () => { return mockQueryEntitiesImplementation(request); }); - render(); + await renderInTestApp(); await waitFor(() => expect(updateFilters).toHaveBeenLastCalledWith({ @@ -563,7 +567,7 @@ describe('', () => { return mockQueryEntitiesImplementation(request); }); - render(); + await renderInTestApp(); await waitFor(() => expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(3), @@ -574,7 +578,7 @@ describe('', () => { }); it('does not reset the filter when entities are loaded', async () => { - render(); + await renderInTestApp(); await waitFor(() => expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(3), @@ -588,7 +592,7 @@ describe('', () => { }); it('does not reset the filter when request is empty xxxx', async () => { - render(); + await renderInTestApp(); await waitFor(() => { expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(1); @@ -619,7 +623,7 @@ describe('', () => { return mockQueryEntitiesImplementation(request); }); - render(); + await renderInTestApp(); await waitFor(() => expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(3), @@ -630,7 +634,7 @@ describe('', () => { }); it('does not reset the filter when entities are loaded', async () => { - render(); + await renderInTestApp(); await waitFor(() => expect(mockCatalogApi.queryEntities).toHaveBeenCalledTimes(3), diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index 1e46258885..c6b515aa67 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -36,6 +36,11 @@ import { UserListFilterKind } from '../../types'; import { useOwnedEntitiesCount } from './useOwnedEntitiesCount'; import { useAllEntitiesCount } from './useAllEntitiesCount'; import { useStarredEntitiesCount } from './useStarredEntitiesCount'; +import { + TranslationFunction, + useTranslationRef, +} from '@backstage/core-plugin-api/alpha'; +import { catalogReactTranslationRef } from '../../translation'; /** @public */ export type CatalogReactUserListPickerClassKey = @@ -83,29 +88,32 @@ export type ButtonGroup = { }[]; }; -function getFilterGroups(orgName: string | undefined): ButtonGroup[] { +function getFilterGroups( + orgName: string, + t: TranslationFunction, +): ButtonGroup[] { return [ { - name: 'Personal', + name: t('userListPicker.personalFilter.title'), items: [ { id: 'owned', - label: 'Owned', + label: t('userListPicker.personalFilter.ownedLabel'), icon: SettingsIcon, }, { id: 'starred', - label: 'Starred', + label: t('userListPicker.personalFilter.starredLabel'), icon: StarIcon, }, ], }, { - name: orgName ?? 'Company', + name: orgName, items: [ { id: 'all', - label: 'All', + label: t('userListPicker.orgFilterAllLabel'), }, ], }, @@ -123,7 +131,10 @@ export const UserListPicker = (props: UserListPickerProps) => { const { initialFilter, availableFilters } = props; const classes = useStyles(); const configApi = useApi(configApiRef); - const orgName = configApi.getOptionalString('organization.name') ?? 'Company'; + const { t } = useTranslationRef(catalogReactTranslationRef); + const orgName = + configApi.getOptionalString('organization.name') ?? + t('userListPicker.defaultOrgName'); const { filters, updateFilters, @@ -133,7 +144,7 @@ export const UserListPicker = (props: UserListPickerProps) => { // Remove group items that aren't in availableFilters and exclude // any now-empty groups. const userAndGroupFilterIds = ['starred', 'all']; - const filterGroups = getFilterGroups(orgName) + const filterGroups = getFilterGroups(orgName, t) .map(filterGroup => ({ ...filterGroup, items: filterGroup.items.filter(({ id }) => @@ -236,11 +247,11 @@ export const UserListPicker = (props: UserListPickerProps) => { - {group.items.map(item => ( + {group.items.map((item, index) => ( setSelectedUserFilter(item.id)} selected={item.id === filters.user?.value} className={classes.menuItem} diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index bc8e6b6038..9afa81e2dd 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -20,6 +20,7 @@ import { alertApiRef, ConfigApi, configApiRef, + errorApiRef, IdentityApi, identityApiRef, storageApiRef, @@ -39,6 +40,8 @@ import { } from '../filters'; import { EntityListProvider, useEntityList } from './useEntityListProvider'; import { useMountEffect } from '@react-hookz/web'; +import { translationApiRef } from '@backstage/core-plugin-api/alpha'; +import { MockTranslationApi } from '@backstage/test-utils/alpha'; const entities: Entity[] = [ { @@ -110,6 +113,8 @@ const createWrapper = [storageApiRef, MockStorageApi.create()], [starredEntitiesApiRef, new MockStarredEntitiesApi()], [alertApiRef, { post: jest.fn() }], + [translationApiRef, MockTranslationApi.create()], + [errorApiRef, { error$: jest.fn(), post: jest.fn() }], ]} > diff --git a/plugins/catalog-react/src/translation.ts b/plugins/catalog-react/src/translation.ts new file mode 100644 index 0000000000..7f9661380f --- /dev/null +++ b/plugins/catalog-react/src/translation.ts @@ -0,0 +1,128 @@ +/* + * 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 { createTranslationRef } from '@backstage/core-plugin-api/alpha'; + +/** @alpha */ +export const catalogReactTranslationRef = createTranslationRef({ + id: 'catalog-react', + messages: { + catalogFilter: { + title: 'Filters', + buttonTitle: 'Filters', + }, + entityKindPicker: { + title: 'Kind', + errorMessage: 'Failed to load entity kinds', + }, + entityLifecyclePicker: { + title: 'Lifecycle', + }, + entityNamespacePicker: { + title: 'Namespace', + }, + entityOwnerPicker: { + title: 'Owner', + }, + entityProcessingStatusPicker: { + title: 'Processing Status', + }, + entityTagPicker: { + title: 'Tags', + }, + entityPeekAheadPopover: { + title: 'Drill into the entity to see all of the tags.', + emailCardAction: { + title: 'Email {{email}}', + subTitle: 'mailto {{email}}', + }, + entityCardActionsTitle: 'Show details', + }, + entitySearchBar: { + placeholder: 'Search', + }, + entityTypePicker: { + title: 'Type', + errorMessage: 'Failed to load entity types', + optionAllTitle: 'all', + }, + favoriteEntity: { + addToFavorites: 'Add to favorites', + removeFromFavorites: 'Remove from favorites', + }, + inspectEntityDialog: { + title: 'Entity Inspector', + closeButtonTitle: 'Close', + ancestryPage: { + title: 'Ancestry', + }, + colocatedPage: { + title: 'Colocated', + description: + 'These are the entities that are colocated with this entity - as in, they originated from the same data source (e.g. came from the same YAML file), or from the same origin (e.g. the originally registered URL).', + alertNoLocation: 'Entity had no location information.', + alertNoEntity: 'There were no other entities on this location.', + }, + jsonPage: { + title: 'Entity as JSON', + description: + 'This is the raw entity data as received from the catalog, on JSON form.', + }, + overviewPage: { + title: 'Overview', + }, + yamlPage: { + title: 'Entity as YAML', + description: + 'This is the raw entity data as received from the catalog, on YAML form.', + }, + }, + unregisterEntityDialog: { + title: 'Are you sure you want to unregister this entity?', + cancelButtonTitle: 'Cancel', + deleteButtonTitle: 'Delete Entity', + deleteEntitySuccessMessage: 'Removed entity {{entityName}}', + bootstrapState: { + title: + 'You cannot unregister this entity, since it originates from a protected Backstage configuration (location "{{location}}"). If you believe this is in error, please contact the {{appTitle}} integrator.', + advancedDescription: + 'You have the option to delete the entity itself from the catalog. Note that this should only be done if you know that the catalog file has been deleted at, or moved from, its origin location. If that is not the case, the entity will reappear shortly as the next refresh round is performed by the catalog.', + advancedOptions: 'Advanced Options', + }, + onlyDeleteStateTitle: + 'This entity does not seem to originate from a registered location. You therefore only have the option to delete it outright from the catalog.', + unregisterState: { + title: 'This action will unregister the following entities:', + subTitle: 'Located at the following location:', + description: 'To undo, just re-register the entity in {{appTitle}}.', + unregisterButtonTitle: 'Unregister Location', + advancedOptions: 'Advanced Options', + advancedDescription: + 'You also have the option to delete the entity itself from the catalog. Note that this should only be done if you know that the catalog file has been deleted at, or moved from, its origin location. If that is not the case, the entity will reappear shortly as the next refresh round is performed by the catalog.', + }, + errorStateTitle: 'Internal error: Unknown state', + }, + userListPicker: { + defaultOrgName: 'Company', + personalFilter: { + title: 'Personal', + ownedLabel: 'Owned', + starredLabel: 'Starred', + }, + orgFilterAllLabel: 'All', + }, + }, +}); diff --git a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md index f2f489b6a0..ca38c923bd 100644 --- a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-unprocessed-entities-common +## 0.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + ## 0.0.2 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities-common/package.json b/plugins/catalog-unprocessed-entities-common/package.json index f491d27157..990f75bbea 100644 --- a/plugins/catalog-unprocessed-entities-common/package.json +++ b/plugins/catalog-unprocessed-entities-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities-common", - "version": "0.0.2", + "version": "0.0.3", "description": "Common functionalities for the catalog-unprocessed-entities plugin", "backstage": { "role": "common-library", diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index ab22634257..2c2eb03f5e 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## 0.2.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + ## 0.2.6-next.0 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index f32ff293df..8ce8c8b0d3 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.2.6-next.0", + "version": "0.2.7-next.0", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 9c6031799c..e0b947b965 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,83 @@ # @backstage/plugin-catalog +## 1.21.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/plugin-search-react@1.7.14-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-scaffolder-common@1.5.4 + - @backstage/plugin-search-common@1.2.13 + +## 1.21.1 + +### Patch Changes + +- 06c0956: Support i18n for catalog and catalog-react plugins +- d133eaa: Added small notes to AboutCard to discourage customizability PRs +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/plugin-scaffolder-common@1.5.4 + - @backstage/plugin-search-common@1.2.13 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/integration-react@1.1.29 + - @backstage/plugin-search-react@1.7.13 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.21.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-search-react@1.7.13-next.1 + - @backstage/core-compat-api@0.2.7-next.1 + +## 1.21.1-next.1 + +### Patch Changes + +- 06c0956: Support i18n for catalog and catalog-react plugins +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-search-react@1.7.13-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/integration-react@1.1.29-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.24 + - @backstage/plugin-permission-react@0.4.23 + - @backstage/plugin-scaffolder-common@1.5.3 + - @backstage/plugin-search-common@1.2.12 + ## 1.21.1-next.0 ### Patch Changes diff --git a/plugins/catalog/api-report-alpha.md b/plugins/catalog/api-report-alpha.md index 75d7e53873..a62b047c4c 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -17,22 +17,96 @@ import { TranslationRef } from '@backstage/core-plugin-api/alpha'; export const catalogTranslationRef: TranslationRef< 'catalog', { + readonly 'deleteEntity.description': 'This entity is not referenced by any location and is therefore not receiving updates. Click here to delete.'; + readonly 'deleteEntity.cancelButtonTitle': 'Cancel'; + readonly 'deleteEntity.deleteButtonTitle': 'Delete'; + readonly 'deleteEntity.dialogTitle': 'Are you sure you want to delete this entity?'; readonly 'indexPage.title': '{{orgName}} Catalog'; readonly 'indexPage.createButtonTitle': 'Create'; + readonly 'indexPage.supportButtonContent': 'All your software catalog entities'; + readonly 'aboutCard.title': 'About'; + readonly 'aboutCard.refreshButtonTitle': 'Schedule entity refresh'; + readonly 'aboutCard.editButtonTitle': 'Edit Metadata'; + readonly 'aboutCard.createSimilarButtonTitle': 'Create something similar'; + readonly 'aboutCard.refreshScheduledMessage': 'Refresh scheduled'; + readonly 'aboutCard.launchTemplate': 'Launch Template'; + readonly 'aboutCard.viewTechdocs': 'View TechDocs'; + readonly 'aboutCard.viewSource': 'View Source'; + readonly 'aboutCard.descriptionField.value': 'No description'; + readonly 'aboutCard.descriptionField.label': 'Description'; + readonly 'aboutCard.ownerField.value': 'No Owner'; + readonly 'aboutCard.ownerField.label': 'Owner'; + readonly 'aboutCard.domainField.value': 'No Domain'; + readonly 'aboutCard.domainField.label': 'Domain'; + readonly 'aboutCard.systemField.value': 'No System'; + readonly 'aboutCard.systemField.label': 'System'; + readonly 'aboutCard.parentComponentField.value': 'No Parent Component'; + readonly 'aboutCard.parentComponentField.label': 'Parent Component'; + readonly 'aboutCard.typeField.label': 'Type'; + readonly 'aboutCard.lifecycleField.label': 'Lifecycle'; + readonly 'aboutCard.tagsField.value': 'No Tags'; + readonly 'aboutCard.tagsField.label': 'Tags'; + readonly 'aboutCard.targetsField.label': 'Targets'; + readonly 'searchResultItem.lifecycle': 'Lifecycle'; + readonly 'searchResultItem.Owner': 'Owner'; + readonly 'catalogTable.warningPanelTitle': 'Could not fetch catalog entities.'; + readonly 'catalogTable.viewActionTitle': 'View'; + readonly 'catalogTable.editActionTitle': 'Edit'; + readonly 'catalogTable.starActionTitle': 'Add to favorites'; + readonly 'catalogTable.unStarActionTitle': 'Remove from favorites'; + readonly 'dependencyOfComponentsCard.title': 'Dependency of components'; + readonly 'dependencyOfComponentsCard.emptyMessage': 'No component depends on this component'; + readonly 'dependsOnComponentsCard.title': 'Depends on components'; + readonly 'dependsOnComponentsCard.emptyMessage': 'No component is a dependency of this component'; + readonly 'dependsOnResourcesCard.title': 'Depends on resources'; + readonly 'dependsOnResourcesCard.emptyMessage': 'No resource is a dependency of this component'; + readonly 'entityContextMenu.copiedMessage': 'Copied!'; + readonly 'entityContextMenu.moreButtonTitle': 'More'; + readonly 'entityContextMenu.inspectMenuTitle': 'Inspect entity'; + readonly 'entityContextMenu.copyURLMenuTitle': 'Copy entity URL'; + readonly 'entityContextMenu.unregisterMenuTitle': 'Unregister entity'; + readonly 'entityLabelsCard.title': 'Labels'; + readonly 'entityLabelsCard.emptyDescription': 'No labels defined for this entity. You can add labels to your entity YAML as shown in the highlighted example below:'; + readonly 'entityLabelsCard.readMoreButtonTitle': 'Read more'; + readonly 'entityLabels.warningPanelTitle': 'Entity not found'; + readonly 'entityLabels.ownerLabel': 'Owner'; + readonly 'entityLabels.lifecycleLabel': 'Lifecycle'; + readonly 'entityLinksCard.title': 'Links'; + readonly 'entityLinksCard.emptyDescription': 'No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:'; + readonly 'entityLinksCard.readMoreButtonTitle': 'Read more'; + readonly 'entityNotFound.title': 'Entity was not found'; + readonly 'entityNotFound.description': 'Want to help us build this? Check out our Getting Started documentation.'; + readonly 'entityNotFound.docButtonTitle': 'DOCS'; + readonly entityProcessingErrorsDescription: 'The error below originates from'; + readonly entityRelationWarningDescription: "This entity has relations to other entities, which can't be found in the catalog.\n Entities not found are: "; + readonly 'hasComponentsCard.title': 'Has components'; + readonly 'hasComponentsCard.emptyMessage': 'No component is part of this system'; + readonly 'hasResourcesCard.title': 'Has resources'; + readonly 'hasResourcesCard.emptyMessage': 'No resource is part of this system'; + readonly 'hasSubcomponentsCard.title': 'Has subcomponents'; + readonly 'hasSubcomponentsCard.emptyMessage': 'No subcomponent is part of this component'; + readonly 'hasSystemsCard.title': 'Has systems'; + readonly 'hasSystemsCard.emptyMessage': 'No system is part of this domain'; + readonly 'relatedEntitiesCard.emptyHelpLinkTitle': 'Learn how to change this.'; + readonly 'systemDiagramCard.title': 'System Diagram'; + readonly 'systemDiagramCard.description': 'Use pinch & zoo to move around the diagram.'; + readonly 'systemDiagramCard.edgeLabels.dependsOn': 'depends on'; + readonly 'systemDiagramCard.edgeLabels.partOf': 'part of'; + readonly 'systemDiagramCard.edgeLabels.provides': 'provides'; } >; // @alpha (undocumented) export function createCatalogFilterExtension< TInputs extends AnyExtensionInputMap, - TConfig = never, + TConfig, >(options: { namespace?: string; name?: string; inputs?: TInputs; configSchema?: PortableSchema; loader: (options: { config: TConfig }) => Promise; -}): ExtensionDefinition; +}): ExtensionDefinition; // @alpha (undocumented) const _default: BackstagePlugin< diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 7cafc9cf92..64f6b0d201 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.21.1-next.0", + "version": "1.21.2-next.0", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/catalog/src/alpha.ts b/plugins/catalog/src/alpha.ts index 927d5362b4..b78a5e6225 100644 --- a/plugins/catalog/src/alpha.ts +++ b/plugins/catalog/src/alpha.ts @@ -17,3 +17,4 @@ export * from './alpha/index'; export { default } from './alpha/index'; export { catalogTranslationRef } from './translation'; +export * from './translation'; diff --git a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx b/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx index 48ee647b81..d8a218f8f3 100644 --- a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx +++ b/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx @@ -26,7 +26,7 @@ import { /** @alpha */ export function createCatalogFilterExtension< TInputs extends AnyExtensionInputMap, - TConfig = never, + TConfig, >(options: { namespace?: string; name?: string; diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index ce975f28ad..ff5d7fc801 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -64,6 +64,8 @@ import { catalogEntityRefreshPermission } from '@backstage/plugin-catalog-common import { useSourceTemplateCompoundEntityRef } from './hooks'; import { taskCreatePermission } from '@backstage/plugin-scaffolder-common/alpha'; import { usePermission } from '@backstage/plugin-permission-react'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; @@ -120,6 +122,7 @@ export function AboutCard(props: AboutCardProps) { const { allowed: canRefresh } = useEntityPermission( catalogEntityRefreshPermission, ); + const { t } = useTranslationRef(catalogTranslationRef); const { allowed: canCreateTemplateTask } = usePermission({ permission: taskCreatePermission, @@ -146,13 +149,13 @@ export function AboutCard(props: AboutCardProps) { } const viewInSource: IconLinkVerticalProps = { - label: 'View Source', + label: t('aboutCard.viewSource'), disabled: !entitySourceLocation, icon: , href: entitySourceLocation?.locationTargetUrl, }; const viewInTechDocs: IconLinkVerticalProps = { - label: 'View TechDocs', + label: t('aboutCard.viewTechdocs'), disabled: !( entity.metadata.annotations?.[TECHDOCS_ANNOTATION] || @@ -180,7 +183,7 @@ export function AboutCard(props: AboutCardProps) { const Icon = app.getSystemIcon('scaffolder') ?? CreateComponentIcon; const launchTemplate: IconLinkVerticalProps = { - label: 'Launch Template', + label: t('aboutCard.launchTemplate'), icon: , disabled: !templateRoute || !canCreateTemplateTask, href: @@ -216,25 +219,25 @@ export function AboutCard(props: AboutCardProps) { try { await catalogApi.refreshEntity(stringifyEntityRef(entity)); alertApi.post({ - message: 'Refresh scheduled', + message: t('aboutCard.refreshScheduledMessage'), severity: 'info', display: 'transient', }); } catch (e) { errorApi.post(e); } - }, [catalogApi, alertApi, errorApi, entity]); + }, [catalogApi, entity, alertApi, t, errorApi]); return ( {allowRefresh && canRefresh && ( @@ -244,7 +247,7 @@ export function AboutCard(props: AboutCardProps) { component={Link} aria-label="Edit" disabled={!entityMetadataEditUrl} - title="Edit Metadata" + title={t('aboutCard.editButtonTitle')} to={entityMetadataEditUrl ?? '#'} > @@ -252,7 +255,7 @@ export function AboutCard(props: AboutCardProps) { {sourceTemplateRef && templateRoute && ( - + {ownedByRelations.length > 0 && ( @@ -130,8 +140,8 @@ export function AboutContent(props: AboutContentProps) { {(isSystem || partOfDomainRelations.length > 0) && ( {partOfDomainRelations.length > 0 && ( @@ -147,8 +157,8 @@ export function AboutContent(props: AboutContentProps) { isResource || partOfSystemRelations.length > 0) && ( {partOfSystemRelations.length > 0 && ( @@ -161,8 +171,8 @@ export function AboutContent(props: AboutContentProps) { )} {isComponent && partOfComponentRelations.length > 0 && ( @@ -188,22 +198,25 @@ export function AboutContent(props: AboutContentProps) { isComponent || typeof entity?.spec?.lifecycle === 'string') && ( )} - {(entity?.metadata?.tags || []).map(t => ( - + {(entity?.metadata?.tags || []).map(tag => ( + ))} {isLocation && (entity?.spec?.targets || entity?.spec?.target) && ( - + )} - All your software catalog entities + {t('indexPage.supportButtonContent')} diff --git a/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx index 32c86f38b7..1a596f8b9b 100644 --- a/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx +++ b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx @@ -27,6 +27,8 @@ import { ResultHighlight, } from '@backstage/plugin-search-common'; import { HighlightedSearchResultText } from '@backstage/plugin-search-react'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; const useStyles = makeStyles( { @@ -66,6 +68,7 @@ export function CatalogSearchResultListItem( const highlight = props.highlight as ResultHighlight; const classes = useStyles(); + const { t } = useTranslationRef(catalogTranslationRef); if (!result) return null; @@ -121,10 +124,16 @@ export function CatalogSearchResultListItem( {result.kind && } {result.type && } {result.lifecycle && ( - + )} {result.owner && ( - + )}
    diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index b1590bdff3..e18467249a 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -48,6 +48,8 @@ import { columnFactories } from './columns'; import { CatalogTableColumnsFunc, CatalogTableRow } from './types'; import { PaginatedCatalogTable } from './PaginatedCatalogTable'; import { defaultCatalogTableColumnsFunc } from './defaultCatalogTableColumnsFunc'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { catalogTranslationRef } from '../../translation'; /** * Props for {@link CatalogTable}. @@ -96,13 +98,14 @@ export const CatalogTable = (props: CatalogTableProps) => { typeof columns === 'function' ? columns(entityListContext) : columns, [columns, entityListContext], ); + const { t } = useTranslationRef(catalogTranslationRef); if (error) { return (
    @@ -113,7 +116,7 @@ export const CatalogTable = (props: CatalogTableProps) => { const defaultActions: TableProps['actions'] = [ ({ entity }) => { const url = entity.metadata.annotations?.[ANNOTATION_VIEW_URL]; - const title = 'View'; + const title = t('catalogTable.viewActionTitle'); return { icon: () => ( @@ -132,7 +135,7 @@ export const CatalogTable = (props: CatalogTableProps) => { }, ({ entity }) => { const url = entity.metadata.annotations?.[ANNOTATION_EDIT_URL]; - const title = 'Edit'; + const title = t('catalogTable.editActionTitle'); return { icon: () => ( @@ -151,7 +154,9 @@ export const CatalogTable = (props: CatalogTableProps) => { }, ({ entity }) => { const isStarred = isStarredEntity(entity); - const title = isStarred ? 'Remove from favorites' : 'Add to favorites'; + const title = isStarred + ? t('catalogTable.unStarActionTitle') + : t('catalogTable.starActionTitle'); return { cellStyle: { paddingLeft: '1em' }, diff --git a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx index 976cdff667..c96fe0e067 100644 --- a/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/PaginatedCatalogTable.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React, { ReactNode } from 'react'; -import { fireEvent, render, waitFor } from '@testing-library/react'; +import { fireEvent, waitFor } from '@testing-library/react'; import { PaginatedCatalogTable } from './PaginatedCatalogTable'; import { screen } from '@testing-library/react'; import { CatalogTableRow } from './types'; @@ -63,8 +63,8 @@ describe('PaginatedCatalogTable', () => { ); }; - it('should display all the items', () => { - render( + it('should display all the items', async () => { + await renderInTestApp( wrapInContext(), ); @@ -74,7 +74,7 @@ describe('PaginatedCatalogTable', () => { }); it('should display and invoke the next button', async () => { - const { rerender } = render( + const { rerender } = await renderInTestApp( wrapInContext( { }); it('should display and invoke the prev button', async () => { - const { rerender } = render( + const { rerender } = await renderInTestApp( wrapInContext( diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx index f4ea850b9b..fe56ea582b 100644 --- a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx +++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx @@ -27,6 +27,8 @@ import { componentEntityHelpLink, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface DependsOnComponentsCardProps { @@ -37,9 +39,10 @@ export interface DependsOnComponentsCardProps { } export function DependsOnComponentsCard(props: DependsOnComponentsCardProps) { + const { t } = useTranslationRef(catalogTranslationRef); const { variant = 'gridItem', - title = 'Depends on components', + title = t('dependsOnComponentsCard.title'), columns = componentEntityColumns, tableOptions = {}, } = props; @@ -50,7 +53,7 @@ export function DependsOnComponentsCard(props: DependsOnComponentsCardProps) { entityKind="Component" relationType={RELATION_DEPENDS_ON} columns={columns} - emptyMessage="No component is a dependency of this component" + emptyMessage={t('dependsOnComponentsCard.emptyMessage')} emptyHelpLink={componentEntityHelpLink} asRenderableEntities={asComponentEntities} tableOptions={tableOptions} diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx index d99a9df387..281bfd8766 100644 --- a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx +++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx @@ -27,6 +27,8 @@ import { RelatedEntitiesCard, resourceEntityColumns, } from '../RelatedEntitiesCard'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface DependsOnResourcesCardProps { @@ -37,9 +39,10 @@ export interface DependsOnResourcesCardProps { } export function DependsOnResourcesCard(props: DependsOnResourcesCardProps) { + const { t } = useTranslationRef(catalogTranslationRef); const { variant = 'gridItem', - title = 'Depends on resources', + title = t('dependsOnResourcesCard.title'), columns = resourceEntityColumns, tableOptions = {}, } = props; @@ -50,7 +53,7 @@ export function DependsOnResourcesCard(props: DependsOnResourcesCardProps) { entityKind="Resource" relationType={RELATION_DEPENDS_ON} columns={columns} - emptyMessage="No resource is a dependency of this component" + emptyMessage={t('dependsOnResourcesCard.emptyMessage')} emptyHelpLink={componentEntityHelpLink} asRenderableEntities={asResourceEntities} tableOptions={tableOptions} diff --git a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx index a173773e35..5b740038a1 100644 --- a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx +++ b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx @@ -33,6 +33,8 @@ import { catalogEntityDeletePermission } from '@backstage/plugin-catalog-common/ import { UnregisterEntity, UnregisterEntityOptions } from './UnregisterEntity'; import { useApi, alertApiRef } from '@backstage/core-plugin-api'; import useCopyToClipboard from 'react-use/esm/useCopyToClipboard'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export type EntityContextMenuClassKey = 'button'; @@ -70,6 +72,7 @@ export function EntityContextMenu(props: EntityContextMenuProps) { onUnregisterEntity, onInspectEntity, } = props; + const { t } = useTranslationRef(catalogTranslationRef); const [anchorEl, setAnchorEl] = useState(); const classes = useStyles(); const unregisterPermission = useEntityPermission( @@ -90,12 +93,12 @@ export function EntityContextMenu(props: EntityContextMenuProps) { useEffect(() => { if (!copyState.error && copyState.value) { alertApi.post({ - message: 'Copied!', + message: t('entityContextMenu.copiedMessage'), severity: 'info', display: 'transient', }); } - }, [copyState, alertApi]); + }, [copyState, alertApi, t]); const extraItems = UNSTABLE_extraContextMenuItems && [ ...UNSTABLE_extraContextMenuItems.map(item => ( @@ -117,7 +120,7 @@ export function EntityContextMenu(props: EntityContextMenuProps) { return ( <> - + - + { @@ -168,7 +171,7 @@ export function EntityContextMenu(props: EntityContextMenuProps) { - + diff --git a/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx b/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx index 3de890097b..08496d4819 100644 --- a/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx +++ b/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx @@ -19,6 +19,8 @@ import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import MenuItem from '@material-ui/core/MenuItem'; import CancelIcon from '@material-ui/icons/Cancel'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; type VisibleType = 'visible' | 'hidden' | 'disable'; @@ -40,6 +42,7 @@ export function UnregisterEntity(props: UnregisterEntityProps) { onUnregisterEntity, onClose, } = props; + const { t } = useTranslationRef(catalogTranslationRef); const isBoolean = typeof unregisterEntityOptions?.disableUnregister === 'boolean'; @@ -65,7 +68,7 @@ export function UnregisterEntity(props: UnregisterEntityProps) { - + ); } diff --git a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx index 0b1448bb1f..d48b45f054 100644 --- a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx +++ b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx @@ -25,6 +25,8 @@ import { import { EntityLabelsEmptyState } from './EntityLabelsEmptyState'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface EntityLabelsCardProps { @@ -42,6 +44,7 @@ export const EntityLabelsCard = (props: EntityLabelsCardProps) => { const { variant, title } = props; const { entity } = useEntity(); const classes = useStyles(); + const { t } = useTranslationRef(catalogTranslationRef); const columns: TableColumn<{ key: string; value: string }>[] = [ { @@ -61,7 +64,7 @@ export const EntityLabelsCard = (props: EntityLabelsCardProps) => { const labels = entity?.metadata?.labels; return ( - + {!labels || Object.keys(labels).length === 0 ? ( ) : ( diff --git a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx index 5f5db0baa5..16d0a3a897 100644 --- a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx +++ b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx @@ -19,6 +19,8 @@ import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { CodeSnippet } from '@backstage/core-components'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const ENTITY_YAML = `metadata: name: example @@ -39,12 +41,12 @@ const useStyles = makeStyles( export function EntityLabelsEmptyState() { const classes = useStyles(); + const { t } = useTranslationRef(catalogTranslationRef); return ( <> - No labels defined for this entity. You can add labels to your entity - YAML as shown in the highlighted example below: + {t('entityLabelsCard.emptyDescription')}
    - Read more + {t('entityLabelsCard.readMoreButtonTitle')} ); diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx index 274972283f..bbc7792acb 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx @@ -53,6 +53,8 @@ import React, { useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu'; import { rootRouteRef, unregisterRedirectRouteRef } from '../../routes'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export type EntityLayoutRouteProps = { @@ -117,11 +119,12 @@ function headerProps( function EntityLabels(props: { entity: Entity }) { const { entity } = props; const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); + const { t } = useTranslationRef(catalogTranslationRef); return ( <> {ownedByRelations.length > 0 && ( )} @@ -234,6 +237,7 @@ export const EntityLayout = (props: EntityLayoutProps) => { const navigate = useNavigate(); const catalogRoute = useRouteRef(rootRouteRef); const unregisterRedirectRoute = useRouteRef(unregisterRedirectRouteRef); + const { t } = useTranslationRef(catalogTranslationRef); const cleanUpAfterRemoval = async () => { setConfirmationDialogOpen(false); @@ -286,7 +290,7 @@ export const EntityLayout = (props: EntityLayoutProps) => { {NotFoundComponent ? ( NotFoundComponent ) : ( - + There is no {kind} with the requested{' '} kind, namespace, and name diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx index ebc551c29f..b5d8269a0b 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx @@ -22,6 +22,8 @@ import { LinksGridList } from './LinksGridList'; import { ColumnBreakpoints } from './types'; import { IconComponent, useApp } from '@backstage/core-plugin-api'; import { InfoCard, InfoCardVariants } from '@backstage/core-components'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { catalogTranslationRef } from '../../translation'; /** @public */ export interface EntityLinksCardProps { @@ -33,6 +35,7 @@ export const EntityLinksCard = (props: EntityLinksCardProps) => { const { cols = undefined, variant } = props; const { entity } = useEntity(); const app = useApp(); + const { t } = useTranslationRef(catalogTranslationRef); const iconResolver = (key?: string): IconComponent => key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon; @@ -40,7 +43,7 @@ export const EntityLinksCard = (props: EntityLinksCardProps) => { const links = entity?.metadata?.links; return ( - + {!links || links.length === 0 ? ( ) : ( diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx index 81009c5393..09362385f9 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx @@ -19,6 +19,8 @@ import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { CodeSnippet } from '@backstage/core-components'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { catalogTranslationRef } from '../../translation'; const ENTITY_YAML = `metadata: name: example @@ -44,12 +46,12 @@ const useStyles = makeStyles( export function EntityLinksEmptyState() { const classes = useStyles(); + const { t } = useTranslationRef(catalogTranslationRef); return ( <> - No links defined for this entity. You can add links to your entity YAML - as shown in the highlighted example below: + {t('entityLinksCard.emptyDescription')}
    - Read more + {t('entityLinksCard.readMoreButtonTitle')} ); diff --git a/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx b/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx index 479edf3984..252005dff5 100644 --- a/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx +++ b/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx @@ -20,6 +20,8 @@ import Button from '@material-ui/core/Button'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import { Illo } from './Illo'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const useStyles = makeStyles(theme => ({ container: { @@ -45,24 +47,24 @@ const useStyles = makeStyles(theme => ({ export function EntityNotFound() { const classes = useStyles(); + const { t } = useTranslationRef(catalogTranslationRef); return ( - Entity was not found + {t('entityNotFound.title')} - Want to help us build this? Check out our Getting Started - documentation. + {t('entityNotFound.description')} diff --git a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx index 603f2040d9..c9c24a14a1 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx @@ -23,6 +23,8 @@ import DialogTitle from '@material-ui/core/DialogTitle'; import React, { useState } from 'react'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; import { assertError } from '@backstage/errors'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; interface DeleteEntityDialogProps { open: boolean; @@ -36,6 +38,7 @@ export function DeleteEntityDialog(props: DeleteEntityDialogProps) { const [busy, setBusy] = useState(false); const catalogApi = useApi(catalogApiRef); const alertApi = useApi(alertApiRef); + const { t } = useTranslationRef(catalogTranslationRef); const onDelete = async () => { setBusy(true); @@ -54,7 +57,7 @@ export function DeleteEntityDialog(props: DeleteEntityDialogProps) { return ( - Are you sure you want to delete this entity? + {t('deleteEntity.dialogTitle')} diff --git a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx index 8a17559681..10a10e54fd 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx @@ -22,6 +22,8 @@ import { useNavigate } from 'react-router-dom'; import { DeleteEntityDialog } from './DeleteEntityDialog'; import { useRouteRef } from '@backstage/core-plugin-api'; import { rootRouteRef } from '../../routes'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** * Returns true if the given entity has the orphan annotation given by the @@ -44,6 +46,7 @@ export function EntityOrphanWarning() { const catalogLink = useRouteRef(rootRouteRef); const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false); const { entity } = useEntity(); + const { t } = useTranslationRef(catalogTranslationRef); const cleanUpAfterRemoval = async () => { setConfirmationDialogOpen(false); @@ -53,8 +56,7 @@ export function EntityOrphanWarning() { return ( <> setConfirmationDialogOpen(true)}> - This entity is not referenced by any location and is therefore not - receiving updates. Click here to delete. + {t('deleteEntity.description')} i.error && @@ -95,6 +97,7 @@ export function EntityProcessingErrorsPanel() { const { loading, error, value } = useAsync(async () => { return getOwnAndAncestorsErrors(entityRef, catalogApi); }, [entityRef, catalogApi]); + const { t } = useTranslationRef(catalogTranslationRef); if (error) { return ( @@ -115,7 +118,7 @@ export function EntityProcessingErrorsPanel() { {stringifyEntityRef(entity) !== stringifyEntityRef(ancestorError.entity) && ( - The error below originates from{' '} + {t('entityProcessingErrorsDescription')}{' '} )} diff --git a/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx b/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx index 13542a32b4..2ce9120bf0 100644 --- a/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx +++ b/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx @@ -26,6 +26,8 @@ import useAsync from 'react-use/esm/useAsync'; import Box from '@material-ui/core/Box'; import { ResponseErrorPanel } from '@backstage/core-components'; import { useApi, ApiHolder } from '@backstage/core-plugin-api'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; async function getRelationWarnings(entity: Entity, catalogApi: CatalogApi) { const entityRefRelations = entity.relations?.map( @@ -80,6 +82,7 @@ export function EntityRelationWarning() { const { loading, error, value } = useAsync(async () => { return getRelationWarnings(entity, catalogApi); }, [entity, catalogApi]); + const { t } = useTranslationRef(catalogTranslationRef); if (error) { return ( @@ -95,10 +98,8 @@ export function EntityRelationWarning() { return ( <> - - This entity has relations to other entities, which can't be found in the - catalog.
    - Entities not found are: {value.join(', ')} + + {t('entityRelationWarningDescription')} {value.join(', ')} ); diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx index 9071dac030..917543dd12 100644 --- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx @@ -23,6 +23,8 @@ import { componentEntityHelpLink, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasComponentsCardProps { @@ -31,7 +33,8 @@ export interface HasComponentsCardProps { } export function HasComponentsCard(props: HasComponentsCardProps) { - const { variant = 'gridItem', title = 'Has components' } = props; + const { t } = useTranslationRef(catalogTranslationRef); + const { variant = 'gridItem', title = t('hasComponentsCard.title') } = props; return ( diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx index 5af7eec258..d11f10c882 100644 --- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx +++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx @@ -23,6 +23,8 @@ import { resourceEntityColumns, resourceEntityHelpLink, } from '../RelatedEntitiesCard'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasResourcesCardProps { @@ -31,7 +33,8 @@ export interface HasResourcesCardProps { } export function HasResourcesCard(props: HasResourcesCardProps) { - const { variant = 'gridItem', title = 'Has resources' } = props; + const { t } = useTranslationRef(catalogTranslationRef); + const { variant = 'gridItem', title = t('hasResourcesCard.title') } = props; return ( ); diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index ac40c82435..d81349794a 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -22,6 +22,8 @@ import { componentEntityColumns, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasSubcomponentsCardProps { @@ -31,10 +33,11 @@ export interface HasSubcomponentsCardProps { } export function HasSubcomponentsCard(props: HasSubcomponentsCardProps) { + const { t } = useTranslationRef(catalogTranslationRef); const { variant = 'gridItem', tableOptions = {}, - title = 'Has subcomponents', + title = t('hasSubcomponentsCard.title'), } = props; return ( diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx index d6da465ffa..cee45a95c5 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -23,6 +23,8 @@ import { systemEntityColumns, systemEntityHelpLink, } from '../RelatedEntitiesCard'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasSystemsCardProps { @@ -31,7 +33,8 @@ export interface HasSystemsCardProps { } export function HasSystemsCard(props: HasSystemsCardProps) { - const { variant = 'gridItem', title = 'Has systems' } = props; + const { t } = useTranslationRef(catalogTranslationRef); + const { variant = 'gridItem', title = t('hasSystemsCard.title') } = props; return ( ); diff --git a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx index eb37973c27..dd1359482d 100644 --- a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx +++ b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx @@ -31,6 +31,8 @@ import { TableColumn, TableOptions, } from '@backstage/core-components'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export type RelatedEntitiesCardProps = { @@ -72,6 +74,7 @@ export function RelatedEntitiesCard( tableOptions = {}, } = props; + const { t } = useTranslationRef(catalogTranslationRef); const { entity } = useEntity(); const { entities, loading, error } = useRelatedEntities(entity, { type: relationType, @@ -102,7 +105,9 @@ export function RelatedEntitiesCard(
    {emptyMessage} - Learn how to change this. + + {t('relatedEntitiesCard.emptyHelpLinkTitle')} +
    } diff --git a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx index 22466b1464..5e485d672b 100644 --- a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx +++ b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx @@ -46,6 +46,8 @@ import { } from '@backstage/core-components'; import { useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { catalogTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export type SystemDiagramCardClassKey = @@ -164,6 +166,7 @@ function RenderNode(props: DependencyGraphTypes.RenderNodeProps) { export function SystemDiagramCard() { const { entity } = useEntity(); const theme = useTheme(); + const { t } = useTranslationRef(catalogTranslationRef); const currentSystemName = entity.metadata.name; const currentSystemNode = stringifyEntityRef(entity); const systemNodes = new Array<{ id: string; kind: string; name: string }>(); @@ -211,7 +214,7 @@ export function SystemDiagramCard() { systemEdges.push({ from: currentSystemNode, to: stringifyEntityRef(foundDomain), - label: 'part of', + label: t('systemDiagramCard.edgeLabels.partOf'), }), ); @@ -235,7 +238,7 @@ export function SystemDiagramCard() { systemEdges.push({ from: stringifyEntityRef(catalogItem), to: stringifyEntityRef(foundRelation), - label: 'part of', + label: t('systemDiagramCard.edgeLabels.partOf'), }), ); @@ -247,7 +250,7 @@ export function SystemDiagramCard() { systemEdges.push({ from: stringifyEntityRef(catalogItem), to: stringifyEntityRef(foundRelation), - label: 'provides', + label: t('systemDiagramCard.edgeLabels.provides'), }), ); @@ -259,7 +262,7 @@ export function SystemDiagramCard() { systemEdges.push({ from: stringifyEntityRef(catalogItem), to: stringifyEntityRef(foundRelation), - label: 'depends on', + label: t('systemDiagramCard.edgeLabels.dependsOn'), }), ); } @@ -272,7 +275,7 @@ export function SystemDiagramCard() { } return ( - + - Use pinch & zoom - to move around the diagram. + + {t('systemDiagramCard.description')} ); diff --git a/plugins/catalog/src/translation.ts b/plugins/catalog/src/translation.ts index bd27069fde..2699b58542 100644 --- a/plugins/catalog/src/translation.ts +++ b/plugins/catalog/src/translation.ts @@ -23,6 +23,141 @@ export const catalogTranslationRef = createTranslationRef({ indexPage: { title: `{{orgName}} Catalog`, createButtonTitle: 'Create', + supportButtonContent: 'All your software catalog entities', + }, + aboutCard: { + title: 'About', + refreshButtonTitle: 'Schedule entity refresh', + editButtonTitle: 'Edit Metadata', + createSimilarButtonTitle: 'Create something similar', + refreshScheduledMessage: 'Refresh scheduled', + launchTemplate: 'Launch Template', + viewTechdocs: 'View TechDocs', + viewSource: 'View Source', + descriptionField: { + label: 'Description', + value: 'No description', + }, + ownerField: { + label: 'Owner', + value: 'No Owner', + }, + domainField: { + label: 'Domain', + value: 'No Domain', + }, + systemField: { + label: 'System', + value: 'No System', + }, + parentComponentField: { + label: 'Parent Component', + value: 'No Parent Component', + }, + typeField: { + label: 'Type', + }, + lifecycleField: { + label: 'Lifecycle', + }, + tagsField: { + label: 'Tags', + value: 'No Tags', + }, + targetsField: { + label: 'Targets', + }, + }, + searchResultItem: { + lifecycle: 'Lifecycle', + Owner: 'Owner', + }, + catalogTable: { + warningPanelTitle: 'Could not fetch catalog entities.', + viewActionTitle: 'View', + editActionTitle: 'Edit', + starActionTitle: 'Add to favorites', + unStarActionTitle: 'Remove from favorites', + }, + dependencyOfComponentsCard: { + title: 'Dependency of components', + emptyMessage: 'No component depends on this component', + }, + dependsOnComponentsCard: { + title: 'Depends on components', + emptyMessage: 'No component is a dependency of this component', + }, + dependsOnResourcesCard: { + title: 'Depends on resources', + emptyMessage: 'No resource is a dependency of this component', + }, + entityContextMenu: { + copiedMessage: 'Copied!', + moreButtonTitle: 'More', + inspectMenuTitle: 'Inspect entity', + copyURLMenuTitle: 'Copy entity URL', + unregisterMenuTitle: 'Unregister entity', + }, + entityLabelsCard: { + title: 'Labels', + emptyDescription: + 'No labels defined for this entity. You can add labels to your entity YAML as shown in the highlighted example below:', + readMoreButtonTitle: 'Read more', + }, + entityLabels: { + warningPanelTitle: 'Entity not found', + ownerLabel: 'Owner', + lifecycleLabel: 'Lifecycle', + }, + entityLinksCard: { + title: 'Links', + emptyDescription: + 'No links defined for this entity. You can add links to your entity YAML as shown in the highlighted example below:', + readMoreButtonTitle: 'Read more', + }, + entityNotFound: { + title: 'Entity was not found', + description: + 'Want to help us build this? Check out our Getting Started documentation.', + docButtonTitle: 'DOCS', + }, + deleteEntity: { + dialogTitle: 'Are you sure you want to delete this entity?', + deleteButtonTitle: 'Delete', + cancelButtonTitle: 'Cancel', + description: + 'This entity is not referenced by any location and is therefore not receiving updates. Click here to delete.', + }, + entityProcessingErrorsDescription: 'The error below originates from', + entityRelationWarningDescription: + "This entity has relations to other entities, which can't be found in the catalog.\n Entities not found are: ", + hasComponentsCard: { + title: 'Has components', + emptyMessage: 'No component is part of this system', + }, + hasResourcesCard: { + title: 'Has resources', + emptyMessage: 'No resource is part of this system', + }, + hasSubcomponentsCard: { + title: 'Has subcomponents', + emptyMessage: 'No subcomponent is part of this component', + }, + hasSystemsCard: { + title: 'Has systems', + emptyMessage: 'No system is part of this domain', + }, + relatedEntitiesCard: { + emptyHelpLinkTitle: 'Learn how to change this.', + }, + systemDiagramCard: { + title: 'System Diagram', + description: 'Use pinch & zoo to move around the diagram.', + edgeLabels: { + partOf: 'part of', + provides: 'provides', + dependsOn: 'depends on', + }, }, }, }); diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 76472b69b4..c11499f589 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-config-schema +## 0.1.58-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.1.57 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.1.57-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + ## 0.1.57-next.0 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 38db6a21d5..ad98b78b98 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-config-schema", - "version": "0.1.57-next.0", + "version": "0.1.58-next.0", "description": "A Backstage plugin that lets you browse the configuration schema of your app", "backstage": { "role": "frontend-plugin", diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index 94c827d2f3..d415b1bf65 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/plugin-devtools-backend +## 0.3.9-next.0 + +### Patch Changes + +- 32a38e1: Removed unused code for lockfile analysis. +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/config-loader@1.8.2-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.11 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## 0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/config-loader@1.8.1 + - @backstage/plugin-devtools-common@0.1.11 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.10 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + ## 0.3.7-next.0 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index f15e205bca..1fe3330698 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.3.7-next.0", + "version": "0.3.9-next.0", "backstage": { "role": "backend-plugin", "pluginId": "devtools", diff --git a/plugins/devtools-backend/src/util/Lockfile.ts b/plugins/devtools-backend/src/util/Lockfile.ts index 52dbb847bc..7f5497e948 100644 --- a/plugins/devtools-backend/src/util/Lockfile.ts +++ b/plugins/devtools-backend/src/util/Lockfile.ts @@ -15,7 +15,6 @@ */ import fs from 'fs-extra'; -import semver from 'semver'; import { parseSyml, stringifySyml } from '@yarnpkg/parsers'; import { stringify as legacyStringifyLockfile } from '@yarnpkg/lockfile'; @@ -35,35 +34,6 @@ type LockfileQueryEntry = { version: string; }; -/** Entries that have an invalid version range, for example an npm tag */ -type AnalyzeResultInvalidRange = { - name: string; - range: string; -}; - -/** Entries that can be deduplicated by bumping to an existing higher version */ -type AnalyzeResultNewVersion = { - name: string; - range: string; - oldVersion: string; - newVersion: string; -}; - -/** Entries that would need a dependency update in package.json to be deduplicated */ -type AnalyzeResultNewRange = { - name: string; - oldRange: string; - newRange: string; - oldVersion: string; - newVersion: string; -}; - -type AnalyzeResult = { - invalidRanges: AnalyzeResultInvalidRange[]; - newVersions: AnalyzeResultNewVersion[]; - newRanges: AnalyzeResultNewRange[]; -}; - function parseLockfile(lockfileContents: string) { try { return { @@ -135,11 +105,10 @@ export class Lockfile { queries.push({ range, version: value.version }); } - return new Lockfile(path, packages, data, legacy); + return new Lockfile(packages, data, legacy); } private constructor( - private readonly path: string, private readonly packages: Map, private readonly data: LockfileData, private readonly legacy: boolean = false, @@ -155,162 +124,6 @@ export class Lockfile { return this.packages.keys(); } - /** Analyzes the lockfile to identify possible actions and warnings for the entries */ - analyze(options?: { filter?: (name: string) => boolean }): AnalyzeResult { - const { filter } = options ?? {}; - const result: AnalyzeResult = { - invalidRanges: [], - newVersions: [], - newRanges: [], - }; - - for (const [name, allEntries] of this.packages) { - if (filter && !filter(name)) { - continue; - } - - // Get rid of and signal any invalid ranges upfront - const invalid = allEntries.filter(e => !semver.validRange(e.range)); - result.invalidRanges.push( - ...invalid.map(({ range }) => ({ name, range })), - ); - - // Grab all valid entries, if there aren't at least 2 different valid ones we're done - const entries = allEntries.filter(e => semver.validRange(e.range)); - if (entries.length < 2) { - continue; - } - - // Find all versions currently in use - const versions = Array.from(new Set(entries.map(e => e.version))).sort( - (v1, v2) => semver.rcompare(v1, v2), - ); - - // If we're not using at least 2 different versions we're done - if (versions.length < 2) { - continue; - } - - const acceptedVersions = new Set(); - for (const { version, range } of entries) { - // Finds the highest matching version from the the known versions - // TODO(Rugvip): We may want to select the version that satisfies the most ranges rather than the highest one - const acceptedVersion = versions.find(v => semver.satisfies(v, range)); - if (!acceptedVersion) { - throw new Error( - `No existing version was accepted for range ${range}, searching through ${versions}, for package ${name}`, - ); - } - - if (acceptedVersion !== version) { - result.newVersions.push({ - name, - range, - newVersion: acceptedVersion, - oldVersion: version, - }); - } - - acceptedVersions.add(acceptedVersion); - } - - // If all ranges were able to accept the same version, we're done - if (acceptedVersions.size === 1) { - continue; - } - - // Find the max version that we may want bump older packages to - const maxVersion = Array.from(acceptedVersions).sort(semver.rcompare)[0]; - // Find all existing ranges that satisfy the new max version, and pick the one that - // results in the highest minimum allowed version, usually being the more specific one - const maxEntry = entries - .filter(e => semver.satisfies(maxVersion, e.range)) - .map(e => ({ e, min: semver.minVersion(e.range) })) - .filter(p => p.min) - .sort((a, b) => semver.rcompare(a.min!, b.min!))[0]?.e; - if (!maxEntry) { - throw new Error( - `No entry found that satisfies max version '${maxVersion}'`, - ); - } - - // Find all entries that don't satisfy the max version - for (const { version, range } of entries) { - if (semver.satisfies(maxVersion, range)) { - continue; - } - - result.newRanges.push({ - name, - oldRange: range, - newRange: maxEntry.range, - oldVersion: version, - newVersion: maxVersion, - }); - } - } - - return result; - } - - remove(name: string, range: string): boolean { - const query = `${name}@${range}`; - const existed = Boolean(this.data[query]); - delete this.data[query]; - - const newEntries = this.packages.get(name)?.filter(e => e.range !== range); - if (newEntries) { - this.packages.set(name, newEntries); - } - - return existed; - } - - /** Modifies the lockfile by bumping packages to the suggested versions */ - replaceVersions(results: AnalyzeResultNewVersion[]) { - for (const { name, range, oldVersion, newVersion } of results) { - const query = `${name}@${range}`; - - // Update the backing data - const entryData = this.data[query]; - if (!entryData) { - throw new Error(`No entry data for ${query}`); - } - if (entryData.version !== oldVersion) { - throw new Error( - `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`, - ); - } - - // Modifying the data in the entry is not enough, we need to reference an existing version object - const matchingEntry = Object.entries(this.data).find( - ([q, e]) => q.startsWith(`${name}@`) && e.version === newVersion, - ); - if (!matchingEntry) { - throw new Error( - `No matching entry found for ${name} at version ${newVersion}`, - ); - } - this.data[query] = matchingEntry[1]; - - // Update our internal data structure - const entry = this.packages.get(name)?.find(e => e.range === range); - if (!entry) { - throw new Error(`No entry data for ${query}`); - } - if (entry.version !== oldVersion) { - throw new Error( - `Expected existing version data for ${query} to be ${oldVersion}, was ${entryData.version}`, - ); - } - entry.version = newVersion; - } - } - - async save() { - await fs.writeFile(this.path, this.toString(), 'utf8'); - } - toString() { return stringifyLockfile(this.data, this.legacy); } diff --git a/plugins/devtools-common/CHANGELOG.md b/plugins/devtools-common/CHANGELOG.md index 3e060571ad..37923e3fc7 100644 --- a/plugins/devtools-common/CHANGELOG.md +++ b/plugins/devtools-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-devtools-common +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/types@1.1.1 + ## 0.1.10 ### Patch Changes diff --git a/plugins/devtools-common/package.json b/plugins/devtools-common/package.json index 24b17d6742..f476580ac0 100644 --- a/plugins/devtools-common/package.json +++ b/plugins/devtools-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-common", - "version": "0.1.10", + "version": "0.1.11", "description": "Common functionalities for the devtools plugin", "backstage": { "role": "common-library", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index 977e9cd73f..b7db4a95bc 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-devtools +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-devtools-common@0.1.11 + - @backstage/plugin-permission-react@0.4.24 + +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-devtools-common@0.1.11 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## 0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/core-compat-api@0.2.7-next.1 + ## 0.1.16-next.0 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 2749bbeef2..2827110d4a 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.16-next.0", + "version": "0.1.17-next.0", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 677cdddcfc..5e954c38ca 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.3.9-next.0 + +### Patch Changes + +- ba8571e: Setup user agent header for AWS sdk clients, this enables users to better track API calls made from Backstage to AWS APIs through things like CloudTrail. +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.3.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-events-node@0.3.8 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.3.7-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index 9cba8e76e3..0135594bd6 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.3.7-next.0", + "version": "0.3.9-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", 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 26eba88c18..de55dee017 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts @@ -76,6 +76,7 @@ export class AwsSqsConsumingEventPublisher { }; this.sqs = new SQSClient({ + customUserAgent: 'backstage-aws-events-sqs-publisher', region: config.region, endpoint: config.endpoint, }); diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index cea3fab2b6..27060d63e4 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-events-node@0.3.8 + +## 0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.2.7-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 74573116d7..eacae60bc2 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.2.7-next.0", + "version": "0.2.9-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index eac1919f41..a411dcecaa 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-events-node@0.3.8 + +## 0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.2.7-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 471ecb4ae2..084b3cb512 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.2.7-next.0", + "version": "0.2.9-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index cb5ac5662f..3cc3f4f3bf 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-events-node@0.3.8 + +## 0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.2.7-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index dbff36987a..99a1cba796 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.2.7-next.0", + "version": "0.2.9-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index e2511f76ba..3ba0e5de31 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-events-backend-module-github +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/config@1.2.0 + +## 0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.2.7-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 126da9f29c..bad39f8080 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.2.7-next.0", + "version": "0.2.9-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md index 89d2f47c10..39fd274342 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.2.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.2.8 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-events-node@0.3.8 + - @backstage/config@1.2.0 + +## 0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.2.7-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 45a9637a24..f2d76a5193 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.2.7-next.0", + "version": "0.2.9-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index e0f42a6f24..be1e0f023c 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.1.32 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.8 + +## 0.1.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.1.31-next.0 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index 931cb89248..f6914c81c5 100644 --- a/plugins/events-backend-test-utils/package.json +++ b/plugins/events-backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-test-utils", - "version": "0.1.31-next.0", + "version": "0.1.33-next.0", "description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node", "backstage": { "role": "node-library", diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index a0e1f91a23..6a6a17c13f 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-events-backend +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.0 + +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-events-node@0.3.8 + - @backstage/config@1.2.0 + +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.8-next.1 + ## 0.3.8-next.0 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index b176044bf9..accb240c22 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.3.8-next.0", + "version": "0.3.10-next.0", "backstage": { "role": "backend-plugin", "pluginId": "events", diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 1c5025b788..a3dfd8682d 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-events-node +## 0.3.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + +## 0.3.8 + +### Patch Changes + +- b05e1e1: Service factories exported by this package have been updated to use the new service factory format that doesn't use a callback. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + +## 0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + ## 0.3.7-next.0 ### Patch Changes diff --git a/plugins/events-node/api-report.md b/plugins/events-node/api-report.md index 46646d2ce8..5b45b4419e 100644 --- a/plugins/events-node/api-report.md +++ b/plugins/events-node/api-report.md @@ -4,7 +4,7 @@ ```ts import { LoggerService } from '@backstage/backend-plugin-api'; -import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceFactoryCompat } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; // @public @@ -63,9 +63,10 @@ export interface EventsService { export type EventsServiceEventHandler = (params: EventParams) => Promise; // @public (undocumented) -export const eventsServiceFactory: () => ServiceFactory< +export const eventsServiceFactory: ServiceFactoryCompat< EventsService, - 'plugin' + 'plugin', + undefined >; // @public diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 58790c4e02..7387294d24 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-node", - "version": "0.3.7-next.0", + "version": "0.3.9-next.0", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library", diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index f42ba59723..3d3c9fd1bb 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,35 @@ # @internal/plugin-todo-list-backend +## 1.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + +## 1.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/errors@1.2.4 + +## 1.0.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + ## 1.0.29-next.0 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index ec37ce2843..1ea02728de 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.29-next.0", + "version": "1.0.30-next.0", "backstage": { "role": "backend-plugin", "pluginId": "todo-list", diff --git a/plugins/example-todo-list-common/CHANGELOG.md b/plugins/example-todo-list-common/CHANGELOG.md index 3761ead6a3..e30cb653df 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list-common +## 1.0.20 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + ## 1.0.19 ### Patch Changes diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 317cfa991d..efccce611f 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.19", + "version": "1.0.20", "backstage": { "role": "common-library", "pluginId": "todo-list", diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index 2f392e6c33..51e2b93ef9 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,28 @@ # @internal/plugin-todo-list +## 1.0.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## 1.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-plugin-api@1.9.3 + +## 1.0.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + ## 1.0.29-next.0 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index c0ef80b3de..d950d8c406 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.29-next.0", + "version": "1.0.30-next.0", "backstage": { "role": "frontend-plugin", "pluginId": "todo-list", diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index 62f3d98623..d810195e7f 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-home-react +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## 0.1.15 + +### Patch Changes + +- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`. + Updated dependency `@rjsf/core` to `5.18.5`. + Updated dependency `@rjsf/material-ui` to `5.18.5`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.5`. +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-plugin-api@1.9.3 + +## 0.1.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## 0.1.15-next.1 + +### Patch Changes + +- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`. + Updated dependency `@rjsf/core` to `5.18.5`. + Updated dependency `@rjsf/material-ui` to `5.18.5`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.5`. +- Updated dependencies + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + ## 0.1.15-next.0 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 819c87e20d..00b0f29fca 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-react", - "version": "0.1.15-next.0", + "version": "0.1.16-next.0", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library", @@ -46,7 +46,7 @@ "@backstage/core-plugin-api": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@rjsf/utils": "5.18.4", + "@rjsf/utils": "5.18.5", "@types/react": "^16.13.1 || ^17.0.0" }, "devDependencies": { diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index b6ff8061f2..75c786c4de 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,77 @@ # @backstage/plugin-home +## 0.7.8-next.0 + +### Patch Changes + +- 31bfc44: Updated alpha definitions of extension data references. +- fdcc059: Fixed a bug on the WelcomeTitle component where the welcome message wasn't correct when the language was set to Spanish +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-app-api@1.14.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-home-react@0.1.16-next.0 + +## 0.7.7 + +### Patch Changes + +- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`. + Updated dependency `@rjsf/core` to `5.18.5`. + Updated dependency `@rjsf/material-ui` to `5.18.5`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.5`. +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/core-app-api@1.14.0 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-home-react@0.1.15 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## 0.7.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/plugin-home-react@0.1.15-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## 0.7.7-next.1 + +### Patch Changes + +- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`. + Updated dependency `@rjsf/core` to `5.18.5`. + Updated dependency `@rjsf/material-ui` to `5.18.5`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.5`. +- Updated dependencies + - @backstage/core-app-api@1.13.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/plugin-home-react@0.1.15-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/theme@0.5.6 + ## 0.7.6-next.0 ### Patch Changes diff --git a/plugins/home/api-report-alpha.md b/plugins/home/api-report-alpha.md index 35ce0bb86c..1dd3da96be 100644 --- a/plugins/home/api-report-alpha.md +++ b/plugins/home/api-report-alpha.md @@ -11,7 +11,11 @@ const _default: BackstagePlugin<{}, {}>; export default _default; // @alpha (undocumented) -export const titleExtensionDataRef: ConfigurableExtensionDataRef; +export const titleExtensionDataRef: ConfigurableExtensionDataRef< + 'title', + string, + {} +>; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/home/package.json b/plugins/home/package.json index d6fbc13d29..d2f0c43a11 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.7.6-next.0", + "version": "0.7.8-next.0", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin", @@ -70,10 +70,10 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", - "@rjsf/core": "5.18.4", - "@rjsf/material-ui": "5.18.4", - "@rjsf/utils": "5.18.4", - "@rjsf/validator-ajv8": "5.18.4", + "@rjsf/core": "5.18.5", + "@rjsf/material-ui": "5.18.5", + "@rjsf/utils": "5.18.5", + "@rjsf/validator-ajv8": "5.18.5", "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", "lodash": "^4.17.21", "luxon": "^3.4.3", diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index e9ff26c9e1..b13c2a2e0e 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -31,7 +31,9 @@ const rootRouteRef = createRouteRef(); /** * @alpha */ -export const titleExtensionDataRef = createExtensionDataRef('title'); +export const titleExtensionDataRef = createExtensionDataRef().with({ + id: 'title', +}); const homePage = createPageExtension({ defaultPath: '/home', diff --git a/plugins/home/src/homePageComponents/WelcomeTitle/locales/goodAfternoon.locales.json b/plugins/home/src/homePageComponents/WelcomeTitle/locales/goodAfternoon.locales.json index 9010ba4359..ddb51654fa 100644 --- a/plugins/home/src/homePageComponents/WelcomeTitle/locales/goodAfternoon.locales.json +++ b/plugins/home/src/homePageComponents/WelcomeTitle/locales/goodAfternoon.locales.json @@ -103,6 +103,7 @@ "Sinhala": "සුභ දවාලක්", "Slovenian": "Dober dan", "Somali": "Galab wanaagsan", + "Spanish": "Buenas tardes", "Southern Sámi": "Buerie biejjie", "Sumerian": "𒄭𒌓𒁀", "Swahili": "Habari ya mchana", diff --git a/plugins/home/src/homePageComponents/WelcomeTitle/locales/goodEvening.locales.json b/plugins/home/src/homePageComponents/WelcomeTitle/locales/goodEvening.locales.json index 5a98abafaa..c8d8c7334e 100644 --- a/plugins/home/src/homePageComponents/WelcomeTitle/locales/goodEvening.locales.json +++ b/plugins/home/src/homePageComponents/WelcomeTitle/locales/goodEvening.locales.json @@ -85,7 +85,7 @@ "Slovak": "Dober vecher", "Slovenian": "Dober vecher", "Somali": "Habeen wanaagsan", - "Spanish": "Buenas tardes", + "Spanish": "Buenas noches", "Sumerian": "𒄭𒈪", "Swedish": "God afton", "Tagalog": "Magandang gabi", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index ea2049d73c..d749d8579e 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,65 @@ # @backstage/plugin-kubernetes-backend +## 0.18.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/plugin-kubernetes-node@0.1.17-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## 0.18.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-kubernetes-node@0.1.16 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## 0.18.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-kubernetes-common@0.8.0 + - @backstage/plugin-kubernetes-node@0.1.16-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + ## 0.18.2-next.0 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index d0de031d9d..eb135d4f0f 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.18.2-next.0", + "version": "0.18.4-next.0", "description": "A Backstage backend plugin that integrates towards Kubernetes", "backstage": { "role": "backend-plugin", diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index 1ac653bb58..5edb8845d4 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -38,7 +38,7 @@ import { ServiceMock, mockCredentials, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, startTestBackend, } from '@backstage/backend-test-utils'; import { rest } from 'msw'; @@ -508,7 +508,7 @@ describe('API integration tests', () => { describe('/proxy', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); beforeEach(() => { worker.use( diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index a433374a2c..3491268d39 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -28,7 +28,7 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { FetchResponse, @@ -1128,7 +1128,7 @@ describe('KubernetesFanOutHandler', () => { describe('with a real fetcher', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); it('fetch error short-circuits requests to a single cluster, recovering across the fleet', async () => { const pods = [{ metadata: { name: 'pod-name' } }]; diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index dafb073bb1..8666e0ef16 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -28,7 +28,7 @@ import { setupServer } from 'msw/node'; import { createMockDirectory, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { Config } from '@kubernetes/client-node'; @@ -71,7 +71,7 @@ const POD_METRICS_FIXTURE = [ describe('KubernetesFetcher', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); const labels = (req: MockedRequest): object => { const selectorParam = req.url.searchParams.get('labelSelector'); diff --git a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts index 02eeb8c7ed..fe4feaee11 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesProxy.test.ts @@ -20,7 +20,7 @@ import { errorHandler } from '@backstage/backend-common'; import { createMockDirectory, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { NotFoundError } from '@backstage/errors'; import { @@ -90,7 +90,7 @@ describe('KubernetesProxy', () => { getExternalBaseUrl: jest.fn(), }; - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); const buildMockRequest = (clusterName: any, path: string): Request => { const req = getMockReq({ diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index cef2c059d1..cd5b6772de 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/plugin-kubernetes-react@0.4.2-next.0 + +## 0.0.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-kubernetes-react@0.4.1 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + +## 0.0.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-kubernetes-react@0.4.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + +## 0.0.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-react@0.4.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-kubernetes-common@0.8.0 + ## 0.0.13-next.0 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index ce1fa2bc63..5549c8dcc5 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.13-next.0", + "version": "0.0.14-next.0", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index e4de39bfd9..a9cabafdd0 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-kubernetes-common +## 0.8.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + ## 0.8.0 ### Minor Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index 9005c5ffe9..db94d165e4 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-common", - "version": "0.8.0", + "version": "0.8.1", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", "backstage": { "role": "common-library", diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index 6f6306cda6..58c458da9c 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-kubernetes-node +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.1 + +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## 0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.0 + ## 0.1.15-next.0 ### Patch Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index 2b4d061ba7..6d1d6d73f8 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-node", - "version": "0.1.15-next.0", + "version": "0.1.17-next.0", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library", diff --git a/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts b/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts index fdfea030a5..24cd459fde 100644 --- a/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts +++ b/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts @@ -18,7 +18,7 @@ import { ExtendedHttpServer } from '@backstage/backend-app-api'; import { ClusterDetails } from '../types'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, startTestBackend, } from '@backstage/backend-test-utils'; import { createBackendModule } from '@backstage/backend-plugin-api'; @@ -43,7 +43,7 @@ describe('Pinniped - tokenCredentialRequest', () => { const logger = loggerToWinstonLogger(mockServices.logger.mock()); let httpsRequest: jest.SpyInstance; const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); beforeAll(() => { httpsRequest = jest.spyOn( diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index 81b5c62fad..c84b5c01e3 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-kubernetes-react +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.1 + +## 0.4.1 + +### Patch Changes + +- e3cb6ab: Add a namespace label to RolloutDrawer +- 2414d86: Fix the `HTML` markup of the `FixDialog` component, `ul` and `li` are not allowed inside a `p` tag. +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.1-next.1 + +### Patch Changes + +- e3cb6ab: Add a namespace label to RolloutDrawer +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + ## 0.4.1-next.0 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 0cc34da7db..2f4ddd7415 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-react", - "version": "0.4.1-next.0", + "version": "0.4.2-next.0", "description": "Web library for the kubernetes-react plugin", "backstage": { "role": "web-library", diff --git a/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts b/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts index 82ac7c84e2..4698f01dfe 100644 --- a/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts +++ b/plugins/kubernetes-react/src/api/KubernetesBackendClient.test.ts @@ -19,7 +19,7 @@ import { KubernetesBackendClient } from './KubernetesBackendClient'; import { rest } from 'msw'; import { UrlPatternDiscovery } from '@backstage/core-app-api'; import { setupServer } from 'msw/node'; -import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils'; +import { MockFetchApi, registerMswTestHooks } from '@backstage/test-utils'; import { CustomObjectsByEntityRequest, KubernetesRequestBody, @@ -36,7 +36,7 @@ describe('KubernetesBackendClient', () => { }; let mockResponse: ObjectsByEntityResponse; const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); const identityApi = { getCredentials: jest.fn(), diff --git a/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.test.tsx b/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.test.tsx index 00063afa99..a4748cdae7 100644 --- a/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.test.tsx +++ b/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/Rollout.test.tsx @@ -35,6 +35,7 @@ describe('Rollout', () => { expect(screen.getByText('dice-roller')).toBeInTheDocument(); expect(screen.getByText('Rollout')).toBeInTheDocument(); + expect(screen.getByText('namespace: default')).toBeInTheDocument(); expect(screen.getByText('2 pods')).toBeInTheDocument(); expect(screen.getByText('No pods with errors')).toBeInTheDocument(); expect(screen.queryByText('Paused')).toBeNull(); @@ -52,6 +53,7 @@ describe('Rollout', () => { expect(screen.getByText('dice-roller')).toBeInTheDocument(); expect(screen.getByText('Rollout')).toBeInTheDocument(); + expect(screen.getByText('namespace: default')).toBeInTheDocument(); expect(screen.getByText('2 pods')).toBeInTheDocument(); expect(screen.getByText('1 pod with errors')).toBeInTheDocument(); expect(screen.queryByText('Paused')).toBeNull(); @@ -71,6 +73,7 @@ describe('Rollout', () => { expect(screen.getByText('dice-roller')).toBeInTheDocument(); expect(screen.getByText('Rollout')).toBeInTheDocument(); + expect(screen.getByText('namespace: default')).toBeInTheDocument(); expect(screen.getByText('2 pods')).toBeInTheDocument(); expect(screen.getByText('No pods with errors')).toBeInTheDocument(); expect(screen.getByText('Paused (45 minutes ago)')).toBeInTheDocument(); @@ -90,6 +93,7 @@ describe('Rollout', () => { expect(screen.getByText('dice-roller')).toBeInTheDocument(); expect(screen.getByText('Rollout')).toBeInTheDocument(); + expect(screen.getByText('namespace: default')).toBeInTheDocument(); expect(screen.getByText('2 pods')).toBeInTheDocument(); expect(screen.getByText('No pods with errors')).toBeInTheDocument(); expect(screen.queryByText('Paused')).toBeNull(); diff --git a/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/RolloutDrawer.tsx b/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/RolloutDrawer.tsx index 304c8bdf78..65d4840bf0 100644 --- a/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/RolloutDrawer.tsx +++ b/plugins/kubernetes-react/src/components/CustomResources/ArgoRollouts/RolloutDrawer.tsx @@ -18,6 +18,7 @@ import React from 'react'; import { KubernetesStructuredMetadataTableDrawer } from '../../KubernetesDrawer'; import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; +import Chip from '@material-ui/core/Chip'; export const RolloutDrawer = ({ rollout, @@ -26,6 +27,7 @@ export const RolloutDrawer = ({ rollout: any; expanded?: boolean; }) => { + const namespace = rollout.metadata?.namespace; return ( + {namespace && ( + + + + )} ); diff --git a/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.tsx b/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.tsx index fd7440870f..99386b3d40 100644 --- a/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.tsx +++ b/plugins/kubernetes-react/src/components/Pods/FixDialog/FixDialog.tsx @@ -33,6 +33,8 @@ import { DetectedError } from '@backstage/plugin-kubernetes-common'; import { PodLogs } from '../PodLogs'; import { Events } from '../Events'; import { LinkButton } from '@backstage/core-components'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -96,17 +98,15 @@ export const FixDialog: React.FC = ({ Fix: - -
      - {(error.proposedFix?.actions ?? []).map((fix, i) => { - return ( -
    • - {fix} -
    • - ); - })} -
    -
    + + {(error.proposedFix?.actions ?? []).map((fix, i) => { + return ( + + {fix} + + ); + })} +
    {pf && pf.type === 'logs' && ( diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 5c8c4d6d4b..26fbfffbdb 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-kubernetes +## 0.11.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/plugin-kubernetes-react@0.4.2-next.0 + +## 0.11.12 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-kubernetes-react@0.4.1 + - @backstage/plugin-kubernetes-common@0.8.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + +## 0.11.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-kubernetes-react@0.4.1-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + +## 0.11.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-react@0.4.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-kubernetes-common@0.8.0 + ## 0.11.12-next.0 ### Patch Changes diff --git a/plugins/kubernetes/README.md b/plugins/kubernetes/README.md index 15dfd16b17..4ffde719f2 100644 --- a/plugins/kubernetes/README.md +++ b/plugins/kubernetes/README.md @@ -26,8 +26,38 @@ For more information, see the [formal documentation about the Kubernetes feature ## Getting started -Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/kubernetes](http://localhost:3000/kubernetes). +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/kubernetes](http://localhost:3000/catalog/default/component/:component-name/kubernetes). You can also serve the plugin in isolation by running `yarn start` in the plugin directory. This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. + +### Integrating with `EntityPage` (New Frontend System) + +Follow this section if you are using Backstage's [new frontend system](https://backstage.io/docs/frontend-system/). + +1. Import `kubernetesPlugin` in your `App.tsx` and add it to your app's `features` array: + +```typescript +import kubernetesPlugin from '@backstage/plugin-kubernetes/alpha'; + +// ... + +export const app = createApp({ + features: [ + // ... + kubernetesPlugin, + // ... + ], +}); +``` + +2. Next, enable your desired extensions in `app-config.yaml` + +```yaml +app: + extensions: + - entity-content:kubernetes/kubernetes +``` + +Now, the extension appears on your entity page as one of the tabs, which is called `KUBERNETES` diff --git a/plugins/kubernetes/api-report-alpha.md b/plugins/kubernetes/api-report-alpha.md new file mode 100644 index 0000000000..8bd6e98baa --- /dev/null +++ b/plugins/kubernetes/api-report-alpha.md @@ -0,0 +1,19 @@ +## API Report File for "@backstage/plugin-kubernetes" + +> 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'; + +// @public (undocumented) +const _default: BackstagePlugin< + { + kubernetes: RouteRef; + }, + {} +>; +export default _default; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/kubernetes/dev/index.tsx b/plugins/kubernetes/dev/index.tsx index fbc666ae13..2de5a17f25 100644 --- a/plugins/kubernetes/dev/index.tsx +++ b/plugins/kubernetes/dev/index.tsx @@ -33,6 +33,7 @@ import fixture1 from '../src/__fixtures__/1-deployments.json'; import fixture2 from '../src/__fixtures__/2-deployments.json'; import fixture3 from '../src/__fixtures__/1-cronjobs.json'; import fixture4 from '../src/__fixtures__/2-cronjobs.json'; +import fixture5 from '../src/__fixtures__/1-rollouts.json'; import { TestApiProvider } from '@backstage/test-utils'; const mockEntity: Entity = { @@ -186,5 +187,18 @@ createDevApp() ), }) + .addPage({ + path: '/fixture-5', + title: 'Fixture 5', + element: ( + + + + + + ), + }) .registerPlugin(kubernetesPlugin) .render(); diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 998bae3626..d44d175211 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.11.12-next.0", + "version": "0.11.13-next.0", "description": "A Backstage plugin that integrates towards Kubernetes", "backstage": { "role": "frontend-plugin", @@ -14,9 +14,7 @@ ] }, "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "keywords": [ "backstage", @@ -30,8 +28,23 @@ }, "license": "Apache-2.0", "sideEffects": false, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, "main": "src/index.ts", "types": "src/index.ts", + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "files": [ "dist" ], @@ -46,8 +59,10 @@ }, "dependencies": { "@backstage/catalog-model": "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-kubernetes-common": "workspace:^", "@backstage/plugin-kubernetes-react": "workspace:^", diff --git a/plugins/kubernetes/src/__fixtures__/1-rollouts.json b/plugins/kubernetes/src/__fixtures__/1-rollouts.json new file mode 100644 index 0000000000..66929b9129 --- /dev/null +++ b/plugins/kubernetes/src/__fixtures__/1-rollouts.json @@ -0,0 +1,2908 @@ +{ + "pods": [ + { + "metadata": { + "creationTimestamp": "2020-09-25T09:58:50.000Z", + "generateName": "dice-roller-6c8646bfd-", + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller", + "pod-template-hash": "6c8646bfd" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"5126c354-4310-4e23-a9e4-c9b87cb69792\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2020-09-25T09:58:50.000Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"172.17.0.11\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "manager": "kubelet", + "operation": "Update", + "time": "2020-09-25T09:58:55.000Z" + } + ], + "name": "dice-roller-6c8646bfd-2m5hv", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "dice-roller-6c8646bfd", + "uid": "5126c354-4310-4e23-a9e4-c9b87cb69792" + } + ], + "resourceVersion": "593216", + "selfLink": "/api/v1/namespaces/default/pods/dice-roller-6c8646bfd-2m5hv", + "uid": "aadb71c0-36fa-43e3-b38a-162f134d4359" + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-5gctn", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "default-token-5gctn", + "secret": { + "defaultMode": 420, + "secretName": "default-token-5gctn" + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:55.000Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:55.000Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://aa4489297c34c48bb33c18474a8d2b33854a82ed42155680b259f635f556ce70", + "image": "nginx:1.14.2", + "imageID": "docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d", + "lastState": {}, + "name": "nginx", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2020-09-25T09:58:53.000Z" + } + } + } + ], + "hostIP": "192.168.64.2", + "phase": "Running", + "podIP": "172.17.0.11", + "podIPs": [ + { + "ip": "172.17.0.11" + } + ], + "qosClass": "BestEffort", + "startTime": "2020-09-25T09:58:50.000Z" + } + }, + { + "metadata": { + "creationTimestamp": "2020-09-25T09:58:50.000Z", + "generateName": "dice-roller-6c8646bfd-", + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller", + "pod-template-hash": "6c8646bfd" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"5126c354-4310-4e23-a9e4-c9b87cb69792\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2020-09-25T09:58:50.000Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"172.17.0.7\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "manager": "kubelet", + "operation": "Update", + "time": "2020-09-25T09:58:55.000Z" + } + ], + "name": "dice-roller-6c8646bfd-4v6s8", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "dice-roller-6c8646bfd", + "uid": "5126c354-4310-4e23-a9e4-c9b87cb69792" + } + ], + "resourceVersion": "593221", + "selfLink": "/api/v1/namespaces/default/pods/dice-roller-6c8646bfd-4v6s8", + "uid": "32e56490-6f20-4757-991f-a0c7fb407358" + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-5gctn", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "default-token-5gctn", + "secret": { + "defaultMode": 420, + "secretName": "default-token-5gctn" + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:55.000Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:55.000Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://d91cc76c41249b8d3dfcf538d180b471b2a7966aae0d17a818307c8a9b4af897", + "image": "nginx:1.14.2", + "imageID": "docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d", + "lastState": {}, + "name": "nginx", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2020-09-25T09:58:53.000Z" + } + } + } + ], + "hostIP": "192.168.64.2", + "phase": "Running", + "podIP": "172.17.0.7", + "podIPs": [ + { + "ip": "172.17.0.7" + } + ], + "qosClass": "BestEffort", + "startTime": "2020-09-25T09:58:50.000Z" + } + }, + { + "metadata": { + "creationTimestamp": "2020-09-24T11:39:26.000Z", + "generateName": "dice-roller-6c8646bfd-", + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller", + "pod-template-hash": "6c8646bfd" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"5126c354-4310-4e23-a9e4-c9b87cb69792\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2020-09-24T11:39:26.000Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"172.17.0.6\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "manager": "kubelet", + "operation": "Update", + "time": "2020-09-24T11:39:27.000Z" + } + ], + "name": "dice-roller-6c8646bfd-b9zt5", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "dice-roller-6c8646bfd", + "uid": "5126c354-4310-4e23-a9e4-c9b87cb69792" + } + ], + "resourceVersion": "503886", + "selfLink": "/api/v1/namespaces/default/pods/dice-roller-6c8646bfd-b9zt5", + "uid": "8b6601b6-469e-4e89-8fd0-abb2f1a567e4" + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-5gctn", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "default-token-5gctn", + "secret": { + "defaultMode": 420, + "secretName": "default-token-5gctn" + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-24T11:39:26.000Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-24T11:39:27.000Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-24T11:39:27.000Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-24T11:39:26.000Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://c50e0d0fa96f09a2ed5df7dd5a7f7de88e6b7c7addb8f002f299d6afc52d82ce", + "image": "nginx:1.14.2", + "imageID": "docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d", + "lastState": {}, + "name": "nginx", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2020-09-24T11:39:27.000Z" + } + } + } + ], + "hostIP": "192.168.64.2", + "phase": "Running", + "podIP": "172.17.0.6", + "podIPs": [ + { + "ip": "172.17.0.6" + } + ], + "qosClass": "BestEffort", + "startTime": "2020-09-24T11:39:26.000Z" + } + }, + { + "metadata": { + "creationTimestamp": "2020-09-25T09:58:50.000Z", + "generateName": "dice-roller-6c8646bfd-", + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller", + "pod-template-hash": "6c8646bfd" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"5126c354-4310-4e23-a9e4-c9b87cb69792\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2020-09-25T09:58:50.000Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"172.17.0.13\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "manager": "kubelet", + "operation": "Update", + "time": "2020-09-25T09:58:55.000Z" + } + ], + "name": "dice-roller-6c8646bfd-cfxqc", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "dice-roller-6c8646bfd", + "uid": "5126c354-4310-4e23-a9e4-c9b87cb69792" + } + ], + "resourceVersion": "593211", + "selfLink": "/api/v1/namespaces/default/pods/dice-roller-6c8646bfd-cfxqc", + "uid": "e87e1776-0ca7-41fb-aeea-e1f648387545" + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-5gctn", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "default-token-5gctn", + "secret": { + "defaultMode": 420, + "secretName": "default-token-5gctn" + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:51.000Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:55.000Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:55.000Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://c1641d986aae424429b7c2c1117baeb17d3794f73206bd57292cdf8264f929b2", + "image": "nginx:1.14.2", + "imageID": "docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d", + "lastState": {}, + "name": "nginx", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2020-09-25T09:58:53.000Z" + } + } + } + ], + "hostIP": "192.168.64.2", + "phase": "Running", + "podIP": "172.17.0.13", + "podIPs": [ + { + "ip": "172.17.0.13" + } + ], + "qosClass": "BestEffort", + "startTime": "2020-09-25T09:58:51.000Z" + } + }, + { + "metadata": { + "creationTimestamp": "2020-09-25T09:58:50.000Z", + "generateName": "dice-roller-6c8646bfd-", + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller", + "pod-template-hash": "6c8646bfd" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"5126c354-4310-4e23-a9e4-c9b87cb69792\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2020-09-25T09:58:50.000Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"172.17.0.15\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "manager": "kubelet", + "operation": "Update", + "time": "2020-09-25T09:58:54.000Z" + } + ], + "name": "dice-roller-6c8646bfd-dtv5z", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "dice-roller-6c8646bfd", + "uid": "5126c354-4310-4e23-a9e4-c9b87cb69792" + } + ], + "resourceVersion": "593190", + "selfLink": "/api/v1/namespaces/default/pods/dice-roller-6c8646bfd-dtv5z", + "uid": "1638a41b-e47e-4c17-a1f7-7b447df248b6" + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-5gctn", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "default-token-5gctn", + "secret": { + "defaultMode": 420, + "secretName": "default-token-5gctn" + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:51.000Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:53.000Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:53.000Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://e62c32db58dbfe0a74b2d2cba0e0a97b7f222693c68e930037ac8738c4758ca3", + "image": "nginx:1.14.2", + "imageID": "docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d", + "lastState": {}, + "name": "nginx", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2020-09-25T09:58:53.000Z" + } + } + } + ], + "hostIP": "192.168.64.2", + "phase": "Running", + "podIP": "172.17.0.15", + "podIPs": [ + { + "ip": "172.17.0.15" + } + ], + "qosClass": "BestEffort", + "startTime": "2020-09-25T09:58:51.000Z" + } + }, + { + "metadata": { + "creationTimestamp": "2020-09-25T09:58:50.000Z", + "generateName": "dice-roller-6c8646bfd-", + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller", + "pod-template-hash": "6c8646bfd" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"5126c354-4310-4e23-a9e4-c9b87cb69792\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2020-09-25T09:58:50.000Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"172.17.0.12\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "manager": "kubelet", + "operation": "Update", + "time": "2020-09-25T09:58:53.000Z" + } + ], + "name": "dice-roller-6c8646bfd-hhts2", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "dice-roller-6c8646bfd", + "uid": "5126c354-4310-4e23-a9e4-c9b87cb69792" + } + ], + "resourceVersion": "593186", + "selfLink": "/api/v1/namespaces/default/pods/dice-roller-6c8646bfd-hhts2", + "uid": "ea0b147a-c56a-46e6-9445-7a0b1b0ed3c0" + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-5gctn", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "default-token-5gctn", + "secret": { + "defaultMode": 420, + "secretName": "default-token-5gctn" + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:53.000Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:53.000Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://bcdbc153630f90556d57ff0d62f04d847ca63a5f0a7e5d9230683623d33d4b8d", + "image": "nginx:1.14.2", + "imageID": "docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d", + "lastState": {}, + "name": "nginx", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2020-09-25T09:58:53.000Z" + } + } + } + ], + "hostIP": "192.168.64.2", + "phase": "Running", + "podIP": "172.17.0.12", + "podIPs": [ + { + "ip": "172.17.0.12" + } + ], + "qosClass": "BestEffort", + "startTime": "2020-09-25T09:58:50.000Z" + } + }, + { + "metadata": { + "creationTimestamp": "2020-09-25T09:58:50.000Z", + "generateName": "dice-roller-6c8646bfd-", + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller", + "pod-template-hash": "6c8646bfd" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"5126c354-4310-4e23-a9e4-c9b87cb69792\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2020-09-25T09:58:50.000Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"172.17.0.14\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "manager": "kubelet", + "operation": "Update", + "time": "2020-09-25T09:58:55.000Z" + } + ], + "name": "dice-roller-6c8646bfd-j68lm", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "dice-roller-6c8646bfd", + "uid": "5126c354-4310-4e23-a9e4-c9b87cb69792" + } + ], + "resourceVersion": "593226", + "selfLink": "/api/v1/namespaces/default/pods/dice-roller-6c8646bfd-j68lm", + "uid": "b230ff1d-7a13-479b-9c7b-77c26bd79f62" + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-5gctn", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "default-token-5gctn", + "secret": { + "defaultMode": 420, + "secretName": "default-token-5gctn" + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:55.000Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:55.000Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://30fa8a3429f86ab8e9a4cec472b79d74266609082703f48950f4aae021e9d6a2", + "image": "nginx:1.14.2", + "imageID": "docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d", + "lastState": {}, + "name": "nginx", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2020-09-25T09:58:53.000Z" + } + } + } + ], + "hostIP": "192.168.64.2", + "phase": "Running", + "podIP": "172.17.0.14", + "podIPs": [ + { + "ip": "172.17.0.14" + } + ], + "qosClass": "BestEffort", + "startTime": "2020-09-25T09:58:50.000Z" + } + }, + { + "metadata": { + "creationTimestamp": "2020-09-25T09:58:50.000Z", + "generateName": "dice-roller-6c8646bfd-", + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller", + "pod-template-hash": "6c8646bfd" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"5126c354-4310-4e23-a9e4-c9b87cb69792\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2020-09-25T09:58:50.000Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"172.17.0.9\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "manager": "kubelet", + "operation": "Update", + "time": "2020-09-25T09:58:53.000Z" + } + ], + "name": "dice-roller-6c8646bfd-m6f9w", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "dice-roller-6c8646bfd", + "uid": "5126c354-4310-4e23-a9e4-c9b87cb69792" + } + ], + "resourceVersion": "593181", + "selfLink": "/api/v1/namespaces/default/pods/dice-roller-6c8646bfd-m6f9w", + "uid": "9b0079f6-ff29-4619-bf6e-71cc10199ac5" + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-5gctn", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "default-token-5gctn", + "secret": { + "defaultMode": 420, + "secretName": "default-token-5gctn" + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:53.000Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:53.000Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://f78767ee90eedffe46ff64bfe76d7f69426800dd89f3df012daf0baf3a9c5bdd", + "image": "nginx:1.14.2", + "imageID": "docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d", + "lastState": {}, + "name": "nginx", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2020-09-25T09:58:53.000Z" + } + } + } + ], + "hostIP": "192.168.64.2", + "phase": "Running", + "podIP": "172.17.0.9", + "podIPs": [ + { + "ip": "172.17.0.9" + } + ], + "qosClass": "BestEffort", + "startTime": "2020-09-25T09:58:50.000Z" + } + }, + { + "metadata": { + "creationTimestamp": "2020-09-24T11:39:27.000Z", + "generateName": "dice-roller-6c8646bfd-", + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller", + "pod-template-hash": "6c8646bfd" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"5126c354-4310-4e23-a9e4-c9b87cb69792\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2020-09-24T11:39:27.000Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"172.17.0.4\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "manager": "kubelet", + "operation": "Update", + "time": "2020-09-24T11:39:28.000Z" + } + ], + "name": "dice-roller-6c8646bfd-nv9pk", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "dice-roller-6c8646bfd", + "uid": "5126c354-4310-4e23-a9e4-c9b87cb69792" + } + ], + "resourceVersion": "503918", + "selfLink": "/api/v1/namespaces/default/pods/dice-roller-6c8646bfd-nv9pk", + "uid": "acf7f77f-78c6-4d4b-bc73-637211770ffc" + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-5gctn", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "default-token-5gctn", + "secret": { + "defaultMode": 420, + "secretName": "default-token-5gctn" + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-24T11:39:27.000Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-24T11:39:28.000Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-24T11:39:28.000Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-24T11:39:27.000Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://63dec9b32942c4b09ae43d09d6978261f674a3ee623823b8f1d20da8044c12ac", + "image": "nginx:1.14.2", + "imageID": "docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d", + "lastState": {}, + "name": "nginx", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2020-09-24T11:39:28.000Z" + } + } + } + ], + "hostIP": "192.168.64.2", + "phase": "Running", + "podIP": "172.17.0.4", + "podIPs": [ + { + "ip": "172.17.0.4" + } + ], + "qosClass": "BestEffort", + "startTime": "2020-09-24T11:39:27.000Z" + } + }, + { + "metadata": { + "creationTimestamp": "2020-09-25T09:58:50.000Z", + "generateName": "dice-roller-6c8646bfd-", + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller", + "pod-template-hash": "6c8646bfd" + }, + "managedFields": [ + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:generateName": {}, + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"5126c354-4310-4e23-a9e4-c9b87cb69792\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2020-09-25T09:58:50.000Z" + }, + { + "apiVersion": "v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:conditions": { + "k:{\"type\":\"ContainersReady\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Initialized\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Ready\"}": { + ".": {}, + "f:lastProbeTime": {}, + "f:lastTransitionTime": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:containerStatuses": {}, + "f:hostIP": {}, + "f:phase": {}, + "f:podIP": {}, + "f:podIPs": { + ".": {}, + "k:{\"ip\":\"172.17.0.10\"}": { + ".": {}, + "f:ip": {} + } + }, + "f:startTime": {} + } + }, + "manager": "kubelet", + "operation": "Update", + "time": "2020-09-25T09:58:55.000Z" + } + ], + "name": "dice-roller-6c8646bfd-pjhfj", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "ReplicaSet", + "name": "dice-roller-6c8646bfd", + "uid": "5126c354-4310-4e23-a9e4-c9b87cb69792" + } + ], + "resourceVersion": "593205", + "selfLink": "/api/v1/namespaces/default/pods/dice-roller-6c8646bfd-pjhfj", + "uid": "78775c3a-7af2-4ebe-a676-bc2e5dfa2bcc" + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "volumeMounts": [ + { + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", + "name": "default-token-5gctn", + "readOnly": true + } + ] + } + ], + "dnsPolicy": "ClusterFirst", + "enableServiceLinks": true, + "nodeName": "minikube", + "priority": 0, + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "serviceAccount": "default", + "serviceAccountName": "default", + "terminationGracePeriodSeconds": 30, + "tolerations": [ + { + "effect": "NoExecute", + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "tolerationSeconds": 300 + }, + { + "effect": "NoExecute", + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "tolerationSeconds": 300 + } + ], + "volumes": [ + { + "name": "default-token-5gctn", + "secret": { + "defaultMode": 420, + "secretName": "default-token-5gctn" + } + } + ] + }, + "status": { + "conditions": [ + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "Initialized" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:55.000Z", + "status": "True", + "type": "Ready" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:55.000Z", + "status": "True", + "type": "ContainersReady" + }, + { + "lastProbeTime": null, + "lastTransitionTime": "2020-09-25T09:58:50.000Z", + "status": "True", + "type": "PodScheduled" + } + ], + "containerStatuses": [ + { + "containerID": "docker://cbb83dda250db74285dcbe0b81bd0896acf402bac9710af090311f7360f824aa", + "image": "nginx:1.14.2", + "imageID": "docker-pullable://nginx@sha256:f7988fb6c02e0ce69257d9bd9cf37ae20a60f1df7563c3a2a6abe24160306b8d", + "lastState": {}, + "name": "nginx", + "ready": true, + "restartCount": 0, + "started": true, + "state": { + "running": { + "startedAt": "2020-09-25T09:58:53.000Z" + } + } + } + ], + "hostIP": "192.168.64.2", + "phase": "Running", + "podIP": "172.17.0.10", + "podIPs": [ + { + "ip": "172.17.0.10" + } + ], + "qosClass": "BestEffort", + "startTime": "2020-09-25T09:58:50.000Z" + } + } + ], + "replicaSets": [ + { + "metadata": { + "annotations": { + "deployment.kubernetes.io/desired-replicas": "10", + "deployment.kubernetes.io/max-replicas": "13", + "deployment.kubernetes.io/revision": "2" + }, + "creationTimestamp": "2020-09-24T11:39:26.000Z", + "generation": 3, + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller", + "pod-template-hash": "6c8646bfd" + }, + "managedFields": [ + { + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:deployment.kubernetes.io/desired-replicas": {}, + "f:deployment.kubernetes.io/max-replicas": {}, + "f:deployment.kubernetes.io/revision": {} + }, + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {}, + "f:pod-template-hash": {} + }, + "f:ownerReferences": { + ".": {}, + "k:{\"uid\":\"7551e949-42d1-4061-83c5-9da107186e47\"}": { + ".": {}, + "f:apiVersion": {}, + "f:blockOwnerDeletion": {}, + "f:controller": {}, + "f:kind": {}, + "f:name": {}, + "f:uid": {} + } + } + }, + "f:spec": { + "f:replicas": {}, + "f:selector": { + "f:matchLabels": { + ".": {}, + "f:app": {}, + "f:pod-template-hash": {} + } + }, + "f:template": { + "f:metadata": { + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {}, + "f:pod-template-hash": {} + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + } + }, + "f:status": { + "f:availableReplicas": {}, + "f:fullyLabeledReplicas": {}, + "f:observedGeneration": {}, + "f:readyReplicas": {}, + "f:replicas": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2020-09-25T09:58:55.000Z" + } + ], + "name": "dice-roller-6c8646bfd", + "namespace": "default", + "ownerReferences": [ + { + "apiVersion": "apps/v1", + "blockOwnerDeletion": true, + "controller": true, + "kind": "Deployment", + "name": "dice-roller", + "uid": "7551e949-42d1-4061-83c5-9da107186e47" + } + ], + "resourceVersion": "593228", + "selfLink": "/apis/apps/v1/namespaces/default/replicasets/dice-roller-6c8646bfd", + "uid": "5126c354-4310-4e23-a9e4-c9b87cb69792" + }, + "spec": { + "replicas": 10, + "selector": { + "matchLabels": { + "app": "dice-roller", + "pod-template-hash": "6c8646bfd" + } + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller", + "pod-template-hash": "6c8646bfd" + } + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + } + ], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30 + } + } + }, + "status": { + "availableReplicas": 10, + "fullyLabeledReplicas": 10, + "observedGeneration": 3, + "readyReplicas": 10, + "replicas": 10 + } + } + ], + "customResources": [ + { + "apiVersion": "argoproj.io/v1alpha1", + "kind": "Rollout", + "metadata": { + "annotations": { + "deployment.kubernetes.io/revision": "2", + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apps/v1\",\"kind\":\"Deployment\",\"metadata\":{\"annotations\":{},\"labels\":{\"backstage.io/kubernetes-id\":\"dice-roller\"},\"name\":\"dice-roller\",\"namespace\":\"default\"},\"spec\":{\"replicas\":10,\"selector\":{\"matchLabels\":{\"app\":\"dice-roller\"}},\"template\":{\"metadata\":{\"labels\":{\"app\":\"dice-roller\",\"backstage.io/kubernetes-id\":\"dice-roller\"}},\"spec\":{\"containers\":[{\"image\":\"nginx:1.14.2\",\"name\":\"nginx\",\"ports\":[{\"containerPort\":80}]}]}}}}\n" + }, + "creationTimestamp": "2020-09-23T12:00:55.000Z", + "generation": 3, + "labels": { + "backstage.io/kubernetes-id": "dice-roller" + }, + "managedFields": [ + { + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {} + }, + "f:labels": { + ".": {}, + "f:backstage.io/kubernetes-id": {} + } + }, + "f:spec": { + "f:progressDeadlineSeconds": {}, + "f:replicas": {}, + "f:revisionHistoryLimit": {}, + "f:selector": { + "f:matchLabels": { + ".": {}, + "f:app": {} + } + }, + "f:strategy": { + "f:rollingUpdate": { + ".": {}, + "f:maxSurge": {}, + "f:maxUnavailable": {} + }, + "f:type": {} + }, + "f:template": { + "f:metadata": { + "f:labels": { + ".": {}, + "f:app": {}, + "f:backstage.io/kubernetes-id": {} + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:ports": { + ".": {}, + "k:{\"containerPort\":80,\"protocol\":\"TCP\"}": { + ".": {}, + "f:containerPort": {}, + "f:protocol": {} + } + }, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + } + } + }, + "manager": "kubectl", + "operation": "Update", + "time": "2020-09-25T09:58:50.000Z" + }, + { + "apiVersion": "apps/v1", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + "f:deployment.kubernetes.io/revision": {} + } + }, + "f:status": { + "f:availableReplicas": {}, + "f:conditions": { + ".": {}, + "k:{\"type\":\"Available\"}": { + ".": {}, + "f:lastTransitionTime": {}, + "f:lastUpdateTime": {}, + "f:message": {}, + "f:reason": {}, + "f:status": {}, + "f:type": {} + }, + "k:{\"type\":\"Progressing\"}": { + ".": {}, + "f:lastTransitionTime": {}, + "f:lastUpdateTime": {}, + "f:message": {}, + "f:reason": {}, + "f:status": {}, + "f:type": {} + } + }, + "f:observedGeneration": {}, + "f:readyReplicas": {}, + "f:replicas": {}, + "f:updatedReplicas": {} + } + }, + "manager": "kube-controller-manager", + "operation": "Update", + "time": "2020-09-25T09:58:55.000Z" + } + ], + "name": "dice-roller", + "namespace": "default", + "resourceVersion": "593230", + "selfLink": "/apis/apps/v1/namespaces/default/deployments/dice-roller", + "uid": "7551e949-42d1-4061-83c5-9da107186e47" + }, + "spec": { + "progressDeadlineSeconds": 600, + "replicas": 10, + "revisionHistoryLimit": 10, + "selector": { + "matchLabels": { + "app": "dice-roller" + } + }, + "strategy": { + "rollingUpdate": { + "maxSurge": "25%", + "maxUnavailable": "25%" + }, + "type": "RollingUpdate" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "app": "dice-roller", + "backstage.io/kubernetes-id": "dice-roller" + } + }, + "spec": { + "containers": [ + { + "image": "nginx:1.14.2", + "imagePullPolicy": "IfNotPresent", + "name": "nginx", + "ports": [ + { + "containerPort": 80, + "protocol": "TCP" + } + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File" + } + ], + "dnsPolicy": "ClusterFirst", + "restartPolicy": "Always", + "schedulerName": "default-scheduler", + "securityContext": {}, + "terminationGracePeriodSeconds": 30 + } + } + }, + "status": { + "availableReplicas": 10, + "conditions": [ + { + "lastTransitionTime": "2020-09-23T12:00:55.000Z", + "lastUpdateTime": "2020-09-24T11:39:28.000Z", + "message": "ReplicaSet \"dice-roller-6c8646bfd\" has successfully progressed.", + "reason": "NewReplicaSetAvailable", + "status": "True", + "type": "Progressing" + }, + { + "lastTransitionTime": "2020-09-25T09:58:55.000Z", + "lastUpdateTime": "2020-09-25T09:58:55.000Z", + "message": "Deployment has minimum availability.", + "reason": "MinimumReplicasAvailable", + "status": "True", + "type": "Available" + } + ], + "observedGeneration": 3, + "readyReplicas": 10, + "replicas": 10, + "updatedReplicas": 10 + } + } + ], + "horizontalPodAutoscalers": [ + { + "apiVersion": "autoscaling/v2", + "kind": "HorizontalPodAutoscaler", + "metadata": { + "annotations": {}, + "creationTimestamp": "2024-02-13T20:13:52Z", + "labels": { + "backstage.io/kubernetes-id": "dice-roller" + }, + "name": "dice-roller", + "namespace": "default", + "resourceVersion": "6717s736", + "uid": "a34c90e1-8e8f-407f-b4c5-b4543bd56c1b" + }, + "spec": { + "maxReplicas": 2, + "metrics": [ + { + "resource": { + "name": "cpu", + "target": { + "averageUtilization": 50, + "type": "Utilization" + } + }, + "type": "Resource" + } + ], + "minReplicas": 1, + "scaleTargetRef": { + "apiVersion": "apps/v1", + "kind": "Deployment", + "name": "dice-roller" + } + }, + "status": { + "conditions": [ + { + "lastTransitionTime": "2024-05-17T19:50:35Z", + "message": "recent recommendations were higher than current one, applying the highest recent recommendation", + "reason": "ScaleDownStabilized", + "status": "True", + "type": "AbleToScale" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the HPA was able to successfully calculate a replica count from cpu resource utilization (percentage of request)", + "reason": "ValidMetricFound", + "status": "True", + "type": "ScalingActive" + }, + { + "lastTransitionTime": "2024-05-16T06:21:01Z", + "message": "the desired replica count is more than the maximum replica count", + "reason": "TooManyReplicas", + "status": "True", + "type": "ScalingLimited" + } + ], + "currentMetrics": [ + { + "resource": { + "current": { + "averageUtilization": 100, + "averageValue": "50m", + "value": "100m" + }, + "name": "cpu" + }, + "type": "Resource" + } + ], + "currentReplicas": 2, + "desiredReplicas": 2, + "lastScaleTime": "2024-02-13T20:14:23Z" + } + } + ] +} diff --git a/plugins/kubernetes/src/alpha.ts b/plugins/kubernetes/src/alpha.ts new file mode 100644 index 0000000000..e80f131817 --- /dev/null +++ b/plugins/kubernetes/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/kubernetes/src/alpha/KubernetesContentPage.tsx b/plugins/kubernetes/src/alpha/KubernetesContentPage.tsx new file mode 100644 index 0000000000..948a492e63 --- /dev/null +++ b/plugins/kubernetes/src/alpha/KubernetesContentPage.tsx @@ -0,0 +1,24 @@ +/* + * 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 { useEntity } from '@backstage/plugin-catalog-react'; +import { KubernetesContent } from '../KubernetesContent'; +import React from 'react'; + +export function KubernetesContentPage() { + const { entity } = useEntity(); + return ; +} diff --git a/plugins/kubernetes/src/alpha/apis.tsx b/plugins/kubernetes/src/alpha/apis.tsx new file mode 100644 index 0000000000..6c880d2f53 --- /dev/null +++ b/plugins/kubernetes/src/alpha/apis.tsx @@ -0,0 +1,119 @@ +/* + * 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 { + createApiExtension, + createApiFactory, + discoveryApiRef, + fetchApiRef, +} from '@backstage/frontend-plugin-api'; +import { + KubernetesBackendClient, + kubernetesApiRef, + kubernetesProxyApiRef, + kubernetesAuthProvidersApiRef, + KubernetesAuthProviders, + KubernetesProxyClient, + kubernetesClusterLinkFormatterApiRef, + getDefaultFormatters, + KubernetesClusterLinkFormatter, + DEFAULT_FORMATTER_NAME, +} from '@backstage/plugin-kubernetes-react'; +import { + gitlabAuthApiRef, + googleAuthApiRef, + microsoftAuthApiRef, + oktaAuthApiRef, + oneloginAuthApiRef, +} from '@backstage/core-plugin-api'; + +export const kubernetesApiExtension = createApiExtension({ + factory: createApiFactory({ + api: kubernetesApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + kubernetesAuthProvidersApi: kubernetesAuthProvidersApiRef, + }, + factory: ({ discoveryApi, fetchApi, kubernetesAuthProvidersApi }) => + new KubernetesBackendClient({ + discoveryApi, + fetchApi, + kubernetesAuthProvidersApi, + }), + }), +}); + +export const kubernetesProxyApi = createApiExtension({ + factory: createApiFactory({ + api: kubernetesProxyApiRef, + deps: { + kubernetesApi: kubernetesApiRef, + }, + factory: ({ kubernetesApi }) => + new KubernetesProxyClient({ + kubernetesApi, + }), + }), +}); + +export const kubernetesAuthProvidersApi = createApiExtension({ + factory: createApiFactory({ + api: kubernetesAuthProvidersApiRef, + deps: { + gitlabAuthApi: gitlabAuthApiRef, + googleAuthApi: googleAuthApiRef, + microsoftAuthApi: microsoftAuthApiRef, + oktaAuthApi: oktaAuthApiRef, + oneloginAuthApi: oneloginAuthApiRef, + }, + factory: ({ + gitlabAuthApi, + googleAuthApi, + microsoftAuthApi, + oktaAuthApi, + oneloginAuthApi, + }) => { + const oidcProviders = { + gitlab: gitlabAuthApi, + google: googleAuthApi, + microsoft: microsoftAuthApi, + okta: oktaAuthApi, + onelogin: oneloginAuthApi, + }; + + return new KubernetesAuthProviders({ + microsoftAuthApi, + googleAuthApi, + oidcProviders, + }); + }, + }), +}); + +export const kubernetesClusterLinkFormatterApi = createApiExtension({ + factory: createApiFactory({ + api: kubernetesClusterLinkFormatterApiRef, + deps: { googleAuthApi: googleAuthApiRef }, + factory: deps => { + const formatters = getDefaultFormatters(deps); + return new KubernetesClusterLinkFormatter({ + formatters, + defaultFormatterName: DEFAULT_FORMATTER_NAME, + }); + }, + }), +}); diff --git a/plugins/kubernetes/src/alpha/entityContents.tsx b/plugins/kubernetes/src/alpha/entityContents.tsx new file mode 100644 index 0000000000..9ad0b6843f --- /dev/null +++ b/plugins/kubernetes/src/alpha/entityContents.tsx @@ -0,0 +1,34 @@ +/* + * 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 React from 'react'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; +import { createEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; +import { rootCatalogKubernetesRouteRef } from '../plugin'; + +export const entityKubernetesContent = createEntityContentExtension({ + defaultPath: 'kubernetes', + defaultTitle: 'Kubernetes', + name: 'kubernetes', + routeRef: convertLegacyRouteRef(rootCatalogKubernetesRouteRef), + loader: () => + import('./KubernetesContentPage').then(m => + compatWrapper(), + ), +}); diff --git a/plugins/kubernetes/src/alpha/index.ts b/plugins/kubernetes/src/alpha/index.ts new file mode 100644 index 0000000000..2f137f09ee --- /dev/null +++ b/plugins/kubernetes/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/kubernetes/src/alpha/pages.tsx b/plugins/kubernetes/src/alpha/pages.tsx new file mode 100644 index 0000000000..8b5db150bd --- /dev/null +++ b/plugins/kubernetes/src/alpha/pages.tsx @@ -0,0 +1,33 @@ +/* + * 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 React from 'react'; // Add this line to import React + +import { createPageExtension } from '@backstage/frontend-plugin-api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; +import { rootCatalogKubernetesRouteRef } from '../plugin'; + +export const kubernetesPage = createPageExtension({ + defaultPath: '/kubernetes', + // you can reuse the existing routeRef + // by wrapping into the convertLegacyRouteRef. + routeRef: convertLegacyRouteRef(rootCatalogKubernetesRouteRef), + // these inputs usually match the props required by the component. + loader: () => import('../Router').then(m => compatWrapper()), +}); diff --git a/plugins/kubernetes/src/alpha/plugin.tsx b/plugins/kubernetes/src/alpha/plugin.tsx new file mode 100644 index 0000000000..47433bce35 --- /dev/null +++ b/plugins/kubernetes/src/alpha/plugin.tsx @@ -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 { convertLegacyRouteRefs } from '@backstage/core-compat-api'; +import { createPlugin } from '@backstage/frontend-plugin-api'; +import { kubernetesPage } from './pages'; +import { entityKubernetesContent } from './entityContents'; +import { rootCatalogKubernetesRouteRef } from '../plugin'; +import { + kubernetesApiExtension as kubernetesApi, + kubernetesAuthProvidersApi, + kubernetesClusterLinkFormatterApi, + kubernetesProxyApi, +} from './apis'; + +export default createPlugin({ + id: 'kubernetes', + extensions: [ + kubernetesPage, + entityKubernetesContent, + kubernetesApi, + kubernetesProxyApi, + kubernetesAuthProvidersApi, + kubernetesClusterLinkFormatterApi, + ], + routes: convertLegacyRouteRefs({ kubernetes: rootCatalogKubernetesRouteRef }), +}); diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index 60725d15ee..f13ba48616 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/plugin-notifications-backend-module-email +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.0 + +## 0.1.3 + +### Patch Changes + +- 4e4ef2b: Move notification processor filter parsing to common package +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## 0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.4 + - @backstage/plugin-notifications-node@0.2.3-next.1 + ## 0.1.2-next.0 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/api-report.md b/plugins/notifications-backend-module-email/api-report.md index 7e8eac6cb7..b105f15cba 100644 --- a/plugins/notifications-backend-module-email/api-report.md +++ b/plugins/notifications-backend-module-email/api-report.md @@ -23,10 +23,10 @@ export default notificationsModuleEmail; // @public (undocumented) export interface NotificationTemplateRenderer { // (undocumented) - getHtml?(notification: Notification_2): string; + getHtml?(notification: Notification_2): Promise; // (undocumented) - getSubject?(notification: Notification_2): string; + getSubject?(notification: Notification_2): Promise; // (undocumented) - getText?(notification: Notification_2): string; + getText?(notification: Notification_2): Promise; } ``` diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 76bf5432e2..c024e6ad50 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.1.2-next.0", + "version": "0.1.4-next.0", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/notifications-backend-module-email/src/extensions.ts b/plugins/notifications-backend-module-email/src/extensions.ts index 454fe9b384..49795eadcf 100644 --- a/plugins/notifications-backend-module-email/src/extensions.ts +++ b/plugins/notifications-backend-module-email/src/extensions.ts @@ -20,9 +20,9 @@ import { Notification } from '@backstage/plugin-notifications-common'; * @public */ export interface NotificationTemplateRenderer { - getSubject?(notification: Notification): string; - getText?(notification: Notification): string; - getHtml?(notification: Notification): string; + getSubject?(notification: Notification): Promise; + getText?(notification: Notification): Promise; + getHtml?(notification: Notification): Promise; } /** diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index a78ebd3260..3bfccfcc90 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -15,7 +15,6 @@ */ import { NotificationProcessor, - NotificationProcessorFilters, NotificationSendOptions, } from '@backstage/plugin-notifications-node'; import { @@ -30,9 +29,9 @@ import { CatalogClient, } from '@backstage/catalog-client'; import { + getProcessorFiltersFromConfig, Notification, - notificationSeverities, - NotificationSeverity, + NotificationProcessorFilters, } from '@backstage/plugin-notifications-common'; import { createSendmailTransport, @@ -86,30 +85,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { ? durationToMilliseconds(readDurationFromConfig(cacheConfig)) : 3_600_000; this.frontendBaseUrl = config.getString('app.baseUrl'); - this.filter = {}; - const minSeverity = emailProcessorConfig.getOptionalString( - 'filter.minSeverity', - ) as NotificationSeverity; - if (minSeverity) { - if (notificationSeverities.includes(minSeverity)) { - this.filter.minSeverity = minSeverity; - } else { - throw new Error(`Invalid minSeverity: ${minSeverity}`); - } - } - const maxSeverity = emailProcessorConfig.getOptionalString( - 'filter.maxSeverity', - ) as NotificationSeverity; - if (maxSeverity) { - if (notificationSeverities.includes(maxSeverity)) { - this.filter.maxSeverity = maxSeverity; - } else { - throw new Error(`Invalid maxSeverity: ${maxSeverity}`); - } - } - this.filter.excludedTopics = emailProcessorConfig.getOptionalStringArray( - 'filter.excludedTopics', - ); + this.filter = getProcessorFiltersFromConfig(emailProcessorConfig); } private async getTransporter() { @@ -304,10 +280,10 @@ export class NotificationsEmailProcessor implements NotificationProcessor { const mailOptions = { from: this.sender, subject: - this.templateRenderer?.getSubject?.(notification) ?? + (await this.templateRenderer?.getSubject?.(notification)) ?? notification.payload.title, - html: this.templateRenderer?.getHtml?.(notification), - text: this.templateRenderer?.getText?.(notification), + html: await this.templateRenderer?.getHtml?.(notification), + text: await this.templateRenderer?.getText?.(notification), replyTo: this.replyTo, }; diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index c42aa95a2e..ef654d213c 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-notifications-backend +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-events-node@0.3.9-next.0 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.0 + - @backstage/plugin-signals-node@0.1.9-next.0 + +## 0.3.3 + +### Patch Changes + +- d7b8ca5: Added an option to filter notifications by topic +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-events-node@0.3.8 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.3 + - @backstage/plugin-signals-node@0.1.8 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-events-node@0.3.8-next.1 + - @backstage/plugin-notifications-common@0.0.4 + - @backstage/plugin-notifications-node@0.2.3-next.1 + - @backstage/plugin-signals-node@0.1.8-next.1 + ## 0.3.2-next.0 ### Patch Changes diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 63b3b263b1..b59ae301f2 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.3.2-next.0", + "version": "0.3.4-next.0", "backstage": { "role": "backend-plugin", "pluginId": "notifications", diff --git a/plugins/notifications-common/CHANGELOG.md b/plugins/notifications-common/CHANGELOG.md index 17231f0147..6e10272c36 100644 --- a/plugins/notifications-common/CHANGELOG.md +++ b/plugins/notifications-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-notifications-common +## 0.0.5 + +### Patch Changes + +- 4e4ef2b: Move notification processor filter parsing to common package +- Updated dependencies + - @backstage/config@1.2.0 + ## 0.0.4 ### Patch Changes diff --git a/plugins/notifications-common/api-report.md b/plugins/notifications-common/api-report.md index 3ddce24393..0f59a85849 100644 --- a/plugins/notifications-common/api-report.md +++ b/plugins/notifications-common/api-report.md @@ -3,6 +3,13 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { Config } from '@backstage/config'; + +// @public (undocumented) +export const getProcessorFiltersFromConfig: ( + config: Config, +) => NotificationProcessorFilters; + // @public (undocumented) export type NewNotificationSignal = { action: 'new_notification'; @@ -33,6 +40,13 @@ export type NotificationPayload = { icon?: string; }; +// @public (undocumented) +export type NotificationProcessorFilters = { + minSeverity?: NotificationSeverity; + maxSeverity?: NotificationSeverity; + excludedTopics?: string[]; +}; + // @public (undocumented) export type NotificationReadSignal = { action: 'notification_read' | 'notification_unread'; diff --git a/plugins/notifications-common/package.json b/plugins/notifications-common/package.json index 744cf52cba..139681e6c6 100644 --- a/plugins/notifications-common/package.json +++ b/plugins/notifications-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-common", - "version": "0.0.4", + "version": "0.0.5", "description": "Common functionalities for the notifications plugin", "backstage": { "role": "common-library", @@ -39,6 +39,7 @@ "test": "backstage-cli package test" }, "dependencies": { + "@backstage/config": "workspace:^", "@material-ui/icons": "^4.9.1" }, "devDependencies": { diff --git a/plugins/notifications-common/src/filters.ts b/plugins/notifications-common/src/filters.ts new file mode 100644 index 0000000000..725cfcf714 --- /dev/null +++ b/plugins/notifications-common/src/filters.ts @@ -0,0 +1,47 @@ +/* + * 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 { Config } from '@backstage/config'; +import { NotificationProcessorFilters, NotificationSeverity } from './types'; +import { notificationSeverities } from './constants'; + +/** @public */ +export const getProcessorFiltersFromConfig = (config: Config) => { + const filter: NotificationProcessorFilters = {}; + const minSeverity = config.getOptionalString( + 'filter.minSeverity', + ) as NotificationSeverity; + if (minSeverity) { + if (notificationSeverities.includes(minSeverity)) { + filter.minSeverity = minSeverity; + } else { + throw new Error(`Invalid minSeverity: ${minSeverity}`); + } + } + const maxSeverity = config.getOptionalString( + 'filter.maxSeverity', + ) as NotificationSeverity; + if (maxSeverity) { + if (notificationSeverities.includes(maxSeverity)) { + filter.maxSeverity = maxSeverity; + } else { + throw new Error(`Invalid maxSeverity: ${maxSeverity}`); + } + } + filter.excludedTopics = config.getOptionalStringArray( + 'filter.excludedTopics', + ); + return filter; +}; diff --git a/plugins/notifications-common/src/index.ts b/plugins/notifications-common/src/index.ts index 7d74c75be8..9fc7bde2b8 100644 --- a/plugins/notifications-common/src/index.ts +++ b/plugins/notifications-common/src/index.ts @@ -22,3 +22,4 @@ export * from './types'; export * from './constants'; +export * from './filters'; diff --git a/plugins/notifications-common/src/types.ts b/plugins/notifications-common/src/types.ts index b9c9f63489..a4be3e4255 100644 --- a/plugins/notifications-common/src/types.ts +++ b/plugins/notifications-common/src/types.ts @@ -116,3 +116,12 @@ export type NotificationReadSignal = { /** @public */ export type NotificationSignal = NewNotificationSignal | NotificationReadSignal; + +/** + * @public + */ +export type NotificationProcessorFilters = { + minSeverity?: NotificationSeverity; + maxSeverity?: NotificationSeverity; + excludedTopics?: string[]; +}; diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index 041d0c355f..4344467104 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-notifications-node +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.9-next.0 + +## 0.2.3 + +### Patch Changes + +- 4e4ef2b: Move notification processor filter parsing to common package +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.8 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-notifications-common@0.0.4 + - @backstage/plugin-signals-node@0.1.8-next.1 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/notifications-node/api-report.md b/plugins/notifications-node/api-report.md index 27e6422f8a..37f28fec35 100644 --- a/plugins/notifications-node/api-report.md +++ b/plugins/notifications-node/api-report.md @@ -8,7 +8,7 @@ import { DiscoveryService } from '@backstage/backend-plugin-api'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { Notification as Notification_2 } from '@backstage/plugin-notifications-common'; import { NotificationPayload } from '@backstage/plugin-notifications-common'; -import { NotificationSeverity } from '@backstage/plugin-notifications-common'; +import { NotificationProcessorFilters as NotificationProcessorFilters_2 } from '@backstage/plugin-notifications-common'; import { ServiceRef } from '@backstage/backend-plugin-api'; // @public (undocumented) @@ -38,12 +38,8 @@ export interface NotificationProcessor { ): Promise; } -// @public (undocumented) -export type NotificationProcessorFilters = { - minSeverity?: NotificationSeverity; - maxSeverity?: NotificationSeverity; - excludedTopics?: string[]; -}; +// @public @deprecated (undocumented) +export type NotificationProcessorFilters = NotificationProcessorFilters_2; // @public (undocumented) export type NotificationRecipients = diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index e79b457965..b71e5d954f 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.2.2-next.0", + "version": "0.2.4-next.0", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library", diff --git a/plugins/notifications-node/src/extensions.ts b/plugins/notifications-node/src/extensions.ts index 7adb6caf50..03ce47fea6 100644 --- a/plugins/notifications-node/src/extensions.ts +++ b/plugins/notifications-node/src/extensions.ts @@ -16,7 +16,7 @@ import { createExtensionPoint } from '@backstage/backend-plugin-api'; import { Notification, - NotificationSeverity, + NotificationProcessorFilters as NotificationProcessorFiltersCommon, } from '@backstage/plugin-notifications-common'; import { NotificationSendOptions } from './service'; @@ -119,9 +119,6 @@ export const notificationsProcessingExtensionPoint = /** * @public + * @deprecated Please import from `@backstage/plugin-notifications-common` instead */ -export type NotificationProcessorFilters = { - minSeverity?: NotificationSeverity; - maxSeverity?: NotificationSeverity; - excludedTopics?: string[]; -}; +export type NotificationProcessorFilters = NotificationProcessorFiltersCommon; diff --git a/plugins/notifications-node/src/service/DefaultNotificationService.test.ts b/plugins/notifications-node/src/service/DefaultNotificationService.test.ts index d5c327e788..3872f898ee 100644 --- a/plugins/notifications-node/src/service/DefaultNotificationService.test.ts +++ b/plugins/notifications-node/src/service/DefaultNotificationService.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { registerMswTestHooks } from '@backstage/test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { NotificationPayload } from '@backstage/plugin-notifications-common'; @@ -32,7 +32,7 @@ const testNotification: NotificationPayload = { describe('DefaultNotificationService', () => { const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); const discovery = mockServices.discovery.mock({ getBaseUrl: jest.fn().mockResolvedValue('http://example.com'), diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index ebcacf97f6..42c55615b2 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-notifications +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-react@0.0.4 + +## 0.2.3 + +### Patch Changes + +- 3bf0697: The toolbar on the Notifications page is hidden when there are no listed notifications. +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.4 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + +## 0.2.3-next.1 + +### Patch Changes + +- 3bf0697: The toolbar on the Notifications page is hidden when there are no listed notifications. +- Updated dependencies + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.4 + - @backstage/plugin-signals-react@0.0.4 + ## 0.2.3-next.0 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index bc868c56a1..5dbfd83255 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.2.3-next.0", + "version": "0.2.4-next.0", "backstage": { "role": "frontend-plugin", "pluginId": "notifications", diff --git a/plugins/notifications/src/api/NotificationsClient.test.ts b/plugins/notifications/src/api/NotificationsClient.test.ts index e9acd04fc9..f378269e45 100644 --- a/plugins/notifications/src/api/NotificationsClient.test.ts +++ b/plugins/notifications/src/api/NotificationsClient.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { MockFetchApi, setupRequestMockHandlers } from '@backstage/test-utils'; +import { MockFetchApi, registerMswTestHooks } from '@backstage/test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { NotificationsClient } from './NotificationsClient'; @@ -33,7 +33,7 @@ const testNotification: Partial = { }; describe('NotificationsClient', () => { - setupRequestMockHandlers(server); + registerMswTestHooks(server); const mockBaseUrl = 'http://backstage/api/notifications'; const discoveryApi = { getBaseUrl: async () => mockBaseUrl }; const fetchApi = new MockFetchApi(); diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx index 72a27975f6..d7c4c40781 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx @@ -173,12 +173,14 @@ export const NotificationsTable = ({ } }, [notifications, selectedNotifications]); - const compactColumns = React.useMemo( - (): TableColumn[] => [ + const compactColumns = React.useMemo((): TableColumn[] => { + const showToolbar = notifications.length > 0; + + return [ { /* selection column */ width: '1rem', - title: ( + title: showToolbar ? ( - ), + ) : undefined, render: (notification: Notification) => ( - ), + ) : undefined, render: (notification: Notification) => ( ), }, - ], - [ - markAsReadOnLinkOpen, - selectedNotifications, - notifications, - isUnread, - onSwitchReadStatus, - onSwitchSavedStatus, - onMarkAllRead, - onNotificationsSelectChange, - classes.severityItem, - classes.description, - ], - ); + ]; + }, [ + markAsReadOnLinkOpen, + selectedNotifications, + notifications, + isUnread, + onSwitchReadStatus, + onSwitchSavedStatus, + onMarkAllRead, + onNotificationsSelectChange, + classes.severityItem, + classes.description, + ]); return ( diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 8c2ea470da..9d0c722d80 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-org-react +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + +## 0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + +## 0.1.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + +## 0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + ## 0.1.26-next.0 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index 7bde5d9e9a..65d600b96f 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.26-next.0", + "version": "0.1.27-next.0", "backstage": { "role": "web-library", "pluginId": "org", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index fe82d2c948..130a62d56d 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,60 @@ # @backstage/plugin-org +## 0.6.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.8-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.25 + +## 0.6.27 + +### Patch Changes + +- 5132d28: The `useGetEntities` hook could result in requests to `/api/catalog/entities` where the headers exceed the default maximum Node.js header size of 16KB. The hook logic has been adjusted to batch the requests. +- c307ef4: Added `relationType` property to EntityMembersListCard component that allows for display users related to a group via some other relationship aside from `memberOf`. + + Also, as a side effect, the `relationsType` property has been deprecated in favor of a more accurately named `relationAggregation` property. + +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-catalog-common@1.0.25 + - @backstage/frontend-plugin-api@0.6.7 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.7 + - @backstage/core-plugin-api@1.9.3 + +## 0.6.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/frontend-plugin-api@0.6.7-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + - @backstage/core-compat-api@0.2.7-next.1 + +## 0.6.27-next.1 + +### Patch Changes + +- 5132d28: The `useGetEntities` hook could result in requests to `/api/catalog/entities` where the headers exceed the default maximum Node.js header size of 16KB. The hook logic has been adjusted to batch the requests. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-compat-api@0.2.7-next.0 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-model@1.5.0 + - @backstage/frontend-plugin-api@0.6.7-next.0 + - @backstage/plugin-catalog-common@1.0.24 + ## 0.6.27-next.0 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 5b749ac215..a3ed608aeb 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.27-next.0", + "version": "0.6.28-next.0", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin", diff --git a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts index c6cec557c1..65a8dec226 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts @@ -231,6 +231,114 @@ describe('useGetEntities', () => { ); }); }); + + describe('useGetEntities with 500+ relations', () => { + const manyGroups = Array.from({ length: 555 }, (_, i) => + createGroupEntityFromName(`group${i + 1}`), + ); + + const givenUserWithManyGroups = { + kind: 'User', + metadata: { + name: givenUser, + }, + spec: { + memberOf: manyGroups.map( + group => `group:default/${group.metadata.name}`, + ), + }, + } as Partial as Entity; + + beforeEach(() => { + getEntityRelationsMock.mockImplementation(entity => + entity?.kind === 'User' + ? manyGroups.map(group => createGroupRefFromName(group.metadata.name)) + : [], + ); + (catalogApiMock.getEntities as jest.Mock).mockClear(); + }); + + it('should handle 500+ relations without exceeding URL length limits', async () => { + const { result } = renderHook( + ({ entity }) => useGetEntities(entity, 'aggregated'), + { + initialProps: { entity: givenUserWithManyGroups }, + }, + ); + + await waitFor(() => expect(result.current.loading).toBe(false), { + timeout: 5000, + }); + + const callArgs = (catalogApiMock.getEntities as jest.Mock).mock + .calls[0][0]; + + expect( + callArgs.filter[0]['relations.ownedBy'].length, + ).toBeLessThanOrEqual(100); + + const owners = callArgs.filter[0]['relations.ownedBy']; + + expect(Array.isArray(owners)).toBeTruthy(); + expect(owners.length).toBeLessThanOrEqual(100); + }); + }); + + describe('useGetEntities exceeding default 16384 bytes header size', () => { + const largeNumberOfGroups = Array.from({ length: 600 }, (_, i) => + createGroupEntityFromName(`very-long-group-name-${i + 1}`), + ); + + const givenUserWithLargeNumberOfGroups = { + kind: 'User', + metadata: { + name: givenUser, + }, + spec: { + memberOf: largeNumberOfGroups.map( + group => `group:default/${group.metadata.name}`, + ), + }, + } as Partial as Entity; + + beforeEach(() => { + getEntityRelationsMock.mockImplementation(entity => + entity?.kind === 'User' + ? largeNumberOfGroups.map(group => + createGroupRefFromName(group.metadata.name), + ) + : [], + ); + (catalogApiMock.getEntities as jest.Mock).mockClear(); + }); + + it('should batch the request to avoid exceeding header size limits', async () => { + const { result } = renderHook( + ({ entity }) => useGetEntities(entity, 'aggregated'), + { + initialProps: { entity: givenUserWithLargeNumberOfGroups }, + }, + ); + + await waitFor(() => expect(result.current.loading).toBe(false), { + timeout: 5000, + }); + const callArgs = (catalogApiMock.getEntities as jest.Mock).mock + .calls[0][0]; + + const url = new URL( + `http://localhost/api/catalog/entities?${new URLSearchParams( + callArgs as any, + )}`, + ); + const headerSize = url.href.length; + expect(headerSize).toBeLessThanOrEqual(16384); + + const owners = callArgs.filter[0]['relations.ownedBy']; + expect(Array.isArray(owners)).toBeTruthy(); + expect(owners.length).toBeLessThanOrEqual(100); + }); + }); }); function createGroupEntityFromName(name: string): Entity { diff --git a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts index d806bbfef2..a5a5363192 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts @@ -32,7 +32,7 @@ import { useApi } from '@backstage/core-plugin-api'; import useAsync from 'react-use/esm/useAsync'; import qs from 'qs'; import { EntityRelationAggregation } from '../types'; -import { uniq } from 'lodash'; +import { uniq, uniqBy } from 'lodash'; const limiter = limiterFactory(5); @@ -143,26 +143,42 @@ const getOwners = async ( return [stringifyEntityRef(entity)]; }; -const getOwnedEntitiesByOwners = ( +const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +const batchGetOwnedEntitiesByOwners = async ( owners: string[], kinds: string[], catalogApi: CatalogApi, -) => - catalogApi.getEntities({ - filter: [ - { - kind: kinds, - 'relations.ownedBy': owners, - }, - ], - fields: [ - 'kind', - 'metadata.name', - 'metadata.namespace', - 'spec.type', - 'relations', - ], - }); + batchSize: number = 100, + delayMs: number = 100, +) => { + const results = []; + + for (let i = 0; i < owners.length; i += batchSize) { + const batch = owners.slice(i, i + batchSize); + const response = await catalogApi.getEntities({ + filter: [ + { + kind: kinds, + 'relations.ownedBy': batch, + }, + ], + fields: [ + 'kind', + 'metadata.name', + 'metadata.namespace', + 'spec.type', + 'relations', + ], + }); + + results.push(...response.items); + + if (i + batchSize < owners.length) await delay(delayMs); + } + + return uniqBy(results, stringifyEntityRef); +}; export function useGetEntities( entity: Entity, @@ -191,13 +207,13 @@ export function useGetEntities( } = useAsync(async () => { const owners = await getOwners(entity, relationAggregation, catalogApi); - const ownedEntitiesList = await getOwnedEntitiesByOwners( + const ownedEntitiesList = await batchGetOwnedEntitiesByOwners( owners, kinds, catalogApi, ); - const counts = ownedEntitiesList.items.reduce( + const counts = ownedEntitiesList.reduce( (acc: EntityTypeProps[], ownedEntity) => { const match = acc.find( x => x.kind === ownedEntity.kind && x.type === ownedEntity.spec?.type, diff --git a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md index 9192850893..c2e00cf839 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.1.20-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## 0.1.19 + +### Patch Changes + +- ed10fd2: Internal refactor to use new `PolicyQueryUser` type. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + ## 0.1.18-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 62a3f6d101..85becb2b60 100644 --- a/plugins/permission-backend-module-policy-allow-all/package.json +++ b/plugins/permission-backend-module-policy-allow-all/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend-module-allow-all-policy", - "version": "0.1.18-next.0", + "version": "0.1.20-next.0", "description": "Allow all policy backend module for the permission plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/permission-backend-module-policy-allow-all/src/policy.ts b/plugins/permission-backend-module-policy-allow-all/src/policy.ts index da853e47db..90ae21f341 100644 --- a/plugins/permission-backend-module-policy-allow-all/src/policy.ts +++ b/plugins/permission-backend-module-policy-allow-all/src/policy.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; import { AuthorizeResult, PolicyDecision, @@ -22,12 +21,13 @@ import { import { PermissionPolicy, PolicyQuery, + PolicyQueryUser, } from '@backstage/plugin-permission-node'; export class AllowAllPermissionPolicy implements PermissionPolicy { async handle( _request: PolicyQuery, - _user?: BackstageIdentityResponse, + _user?: PolicyQueryUser, ): Promise { return { result: AuthorizeResult.ALLOW, diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index d7190f15ee..c5e9b7675f 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/plugin-permission-backend +## 0.5.47-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + +## 0.5.46 + +### Patch Changes + +- ed10fd2: The `PermissionPolicy` interface has been updated to align with the recent changes to the Backstage auth system. The second argument to the `handle` method is now of the new `PolicyQueryUser` type. This type maintains the old fields from the `BackstageIdentityResponse`, which are now all deprecated. Instead, two new fields have been added, which allows access to the same information: + + - `credentials` - A `BackstageCredentials` object, which is useful for making requests to other services on behalf of the user as part of evaluating the policy. This replaces the deprecated `token` field. See the [Auth Service documentation](https://backstage.io/docs/backend-system/core-services/auth#creating-request-tokens) for information about how to create a token using these credentials. + - `info` - A `BackstageUserInfo` object, which contains the same information as the deprecated `identity`, except for the `type` field that was redundant. + + Most existing policies can be updated by replacing the `BackstageIdentityResponse` type with `PolicyQueryUser`, which is exported from `@backstage/plugin-permission-node`, as well as replacing any occurrences of `user?.identity` with `user?.info`. + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.5.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + ## 0.5.45-next.0 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 24b62d1358..11ad9538be 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.45-next.0", + "version": "0.5.47-next.0", "backstage": { "role": "backend-plugin", "pluginId": "permission", diff --git a/plugins/permission-backend/src/service/router.test.ts b/plugins/permission-backend/src/service/router.test.ts index be90c5f628..f12bacc61d 100644 --- a/plugins/permission-backend/src/service/router.test.ts +++ b/plugins/permission-backend/src/service/router.test.ts @@ -186,6 +186,13 @@ describe('createRouter', () => { mockCredentials.user().principal.userEntityRef, ], }, + info: { + userEntityRef: mockCredentials.user().principal.userEntityRef, + ownershipEntityRefs: [ + mockCredentials.user().principal.userEntityRef, + ], + }, + credentials: mockCredentials.user(), }, ); expect(response.body).toEqual({ diff --git a/plugins/permission-backend/src/service/router.ts b/plugins/permission-backend/src/service/router.ts index cc45d0b41a..fd33f0b629 100644 --- a/plugins/permission-backend/src/service/router.ts +++ b/plugins/permission-backend/src/service/router.ts @@ -22,10 +22,7 @@ import { errorHandler, } from '@backstage/backend-common'; import { InputError } from '@backstage/errors'; -import { - BackstageIdentityResponse, - IdentityApi, -} from '@backstage/plugin-auth-node'; +import { IdentityApi } from '@backstage/plugin-auth-node'; import { AuthorizeResult, EvaluatePermissionRequest, @@ -40,6 +37,7 @@ import { ApplyConditionsRequestEntry, ApplyConditionsResponseEntry, PermissionPolicy, + PolicyQueryUser, } from '@backstage/plugin-permission-node'; import { PermissionIntegrationClient } from './PermissionIntegrationClient'; import { memoize } from 'lodash'; @@ -130,9 +128,9 @@ const handleRequest = async ( ); }); - let user: BackstageIdentityResponse | undefined; + let user: PolicyQueryUser | undefined; if (auth.isPrincipal(credentials, 'user')) { - const { ownershipEntityRefs } = await userInfo.getUserInfo(credentials); + const info = await userInfo.getUserInfo(credentials); const { token } = await auth.getPluginRequestToken({ onBehalfOf: credentials, targetPluginId: 'catalog', // TODO: unknown at this point @@ -141,9 +139,11 @@ const handleRequest = async ( identity: { type: 'user', userEntityRef: credentials.principal.userEntityRef, - ownershipEntityRefs, + ownershipEntityRefs: info.ownershipEntityRefs, }, token, + credentials, + info, }; } diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index e6b637a092..92a33afea2 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-permission-common +## 0.8.0 + +### Minor Changes + +- f4085b8: **BREAKING**: Removed the deprecated and unused `token` option from `EvaluatorRequestOptions`. The `PermissionsClient` now has its own `PermissionClientRequestOptions` type that declares the `token` option instead. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.7.14 ### Patch Changes diff --git a/plugins/permission-common/api-report.md b/plugins/permission-common/api-report.md index 1c2476d20d..e9935c612d 100644 --- a/plugins/permission-common/api-report.md +++ b/plugins/permission-common/api-report.md @@ -94,9 +94,7 @@ export type EvaluatePermissionResponseBatch = PermissionMessageBatch; // @public -export type EvaluatorRequestOptions = { - token?: string; -}; +export interface EvaluatorRequestOptions {} // @public export type IdentifiedPermissionMessage = T & { @@ -162,14 +160,19 @@ export class PermissionClient implements PermissionEvaluator { constructor(options: { discovery: DiscoveryApi; config: Config }); authorize( requests: AuthorizePermissionRequest[], - options?: EvaluatorRequestOptions, + options?: PermissionClientRequestOptions, ): Promise; authorizeConditional( queries: QueryPermissionRequest[], - options?: EvaluatorRequestOptions, + options?: PermissionClientRequestOptions, ): Promise; } +// @public +export type PermissionClientRequestOptions = { + token?: string; +}; + // @public export type PermissionCondition< TResourceType extends string = string, @@ -191,11 +194,15 @@ export type PermissionCriteria = export interface PermissionEvaluator { authorize( requests: AuthorizePermissionRequest[], - options?: EvaluatorRequestOptions, + options?: EvaluatorRequestOptions & { + _ignored?: never; + }, ): Promise; authorizeConditional( requests: QueryPermissionRequest[], - options?: EvaluatorRequestOptions, + options?: EvaluatorRequestOptions & { + _ignored?: never; + }, ): Promise; } diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index ee54459589..8d6f54fac0 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-common", - "version": "0.7.14", + "version": "0.8.0", "description": "Isomorphic types and client for Backstage permissions and authorization", "backstage": { "role": "common-library", diff --git a/plugins/permission-common/src/PermissionClient.ts b/plugins/permission-common/src/PermissionClient.ts index a5d08205d8..2414774775 100644 --- a/plugins/permission-common/src/PermissionClient.ts +++ b/plugins/permission-common/src/PermissionClient.ts @@ -27,7 +27,6 @@ import { PermissionEvaluator, QueryPermissionRequest, AuthorizePermissionRequest, - EvaluatorRequestOptions, AuthorizePermissionResponse, QueryPermissionResponse, } from './types/api'; @@ -93,6 +92,15 @@ const responseSchema = ( ), }); +/** + * Options for {@link PermissionClient} requests. + * + * @public + */ +export type PermissionClientRequestOptions = { + token?: string; +}; + /** * An isomorphic client for requesting authorization for Backstage permissions. * @public @@ -112,7 +120,7 @@ export class PermissionClient implements PermissionEvaluator { */ async authorize( requests: AuthorizePermissionRequest[], - options?: EvaluatorRequestOptions, + options?: PermissionClientRequestOptions, ): Promise { return this.makeRequest( requests, @@ -126,7 +134,7 @@ export class PermissionClient implements PermissionEvaluator { */ async authorizeConditional( queries: QueryPermissionRequest[], - options?: EvaluatorRequestOptions, + options?: PermissionClientRequestOptions, ): Promise { return this.makeRequest(queries, queryPermissionResponseSchema, options); } diff --git a/plugins/permission-common/src/types/api.ts b/plugins/permission-common/src/types/api.ts index e597384a56..072615989b 100644 --- a/plugins/permission-common/src/types/api.ts +++ b/plugins/permission-common/src/types/api.ts @@ -244,7 +244,7 @@ export interface PermissionEvaluator { */ authorize( requests: AuthorizePermissionRequest[], - options?: EvaluatorRequestOptions, + options?: EvaluatorRequestOptions & { _ignored?: never }, // Since the options are empty we add this placeholder to reject all options ): Promise; /** @@ -257,22 +257,20 @@ export interface PermissionEvaluator { */ authorizeConditional( requests: QueryPermissionRequest[], - options?: EvaluatorRequestOptions, + options?: EvaluatorRequestOptions & { _ignored?: never }, // Since the options are empty we add this placeholder to reject all options ): Promise; } +// Note(Rugvip): I kept the below type around in case we want to add new options +// in the future, for example a signal. It also helps out enabling API +// constraints, as without this we can't have the permissions service implement +// the evaluator interface due to the mismatch in parameter count. + /** * Options for {@link PermissionEvaluator} requests. * + * This is currently empty, as there are no longer any common options for the permission evaluator. + * * @public */ -export type EvaluatorRequestOptions = { - /** - * @deprecated Backend plugins should no longer depend on the - * `PermissionEvaluator`, but instead use the `PermissionService` from - * `@backstage/backend-plugin-api`. Frontend plugins should not need to inject - * this token at all, but instead implicitly rely on underlying fetchApi to do - * it for them. - */ - token?: string; -}; +export interface EvaluatorRequestOptions {} diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 7255dbe266..31d2f9145e 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-permission-node +## 0.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-permission-common@0.8.0 + +## 0.8.0 + +### Minor Changes + +- 36f91e8: **BREAKING**: Updated the `ServerPermissionClient` to match the new `PermissionsService` interface, where the deprecated `token` option has been removed and the options are now required. + +### Patch Changes + +- ed10fd2: The `PermissionPolicy` interface has been updated to align with the recent changes to the Backstage auth system. The second argument to the `handle` method is now of the new `PolicyQueryUser` type. This type maintains the old fields from the `BackstageIdentityResponse`, which are now all deprecated. Instead, two new fields have been added, which allows access to the same information: + + - `credentials` - A `BackstageCredentials` object, which is useful for making requests to other services on behalf of the user as part of evaluating the policy. This replaces the deprecated `token` field. See the [Auth Service documentation](https://backstage.io/docs/backend-system/core-services/auth#creating-request-tokens) for information about how to create a token using these credentials. + - `info` - A `BackstageUserInfo` object, which contains the same information as the deprecated `identity`, except for the `type` field that was redundant. + + Most existing policies can be updated by replacing the `BackstageIdentityResponse` type with `PolicyQueryUser`, which is exported from `@backstage/plugin-permission-node`, as well as replacing any occurrences of `user?.identity` with `user?.info`. + +- 28b2cfb: Fix invalid cross-reference in API Reference docs +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.7.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-permission-common@0.7.14 + ## 0.7.32-next.0 ### Patch Changes diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 290c586f2c..81460ef630 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -8,7 +8,9 @@ import { AnyOfCriteria } from '@backstage/plugin-permission-common'; import { AuthorizePermissionRequest } from '@backstage/plugin-permission-common'; import { AuthorizePermissionResponse } from '@backstage/plugin-permission-common'; import { AuthService } from '@backstage/backend-plugin-api'; -import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; +import { BackstageCredentials } from '@backstage/backend-plugin-api'; +import { BackstageUserIdentity } from '@backstage/plugin-auth-node'; +import { BackstageUserInfo } from '@backstage/backend-plugin-api'; import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Config } from '@backstage/config'; import { DefinitivePolicyDecision } from '@backstage/plugin-permission-common'; @@ -246,10 +248,7 @@ export type PermissionIntegrationRouterOptions< // @public export interface PermissionPolicy { // (undocumented) - handle( - request: PolicyQuery, - user?: BackstageIdentityResponse, - ): Promise; + handle(request: PolicyQuery, user?: PolicyQueryUser): Promise; } // @public @@ -272,6 +271,15 @@ export type PolicyQuery = { permission: Permission; }; +// @public +export type PolicyQueryUser = { + token: string; + expiresInSeconds?: number; + identity: BackstageUserIdentity; + credentials: BackstageCredentials; + info: BackstageUserInfo; +}; + // @public export class ServerPermissionClient implements PermissionsService { // (undocumented) diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 695e28f30b..630391f373 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-node", - "version": "0.7.32-next.0", + "version": "0.8.1-next.0", "description": "Common permission and authorization utilities for backend plugins", "backstage": { "role": "node-library", diff --git a/plugins/permission-node/src/ServerPermissionClient.test.ts b/plugins/permission-node/src/ServerPermissionClient.test.ts index 6e99ff71ab..5c6328ae44 100644 --- a/plugins/permission-node/src/ServerPermissionClient.test.ts +++ b/plugins/permission-node/src/ServerPermissionClient.test.ts @@ -25,7 +25,7 @@ import { import { mockCredentials, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { setupServer } from 'msw/node'; @@ -54,7 +54,7 @@ const config = new ConfigReader({ }); describe('ServerPermissionClient', () => { - setupRequestMockHandlers(server); + registerMswTestHooks(server); const discovery = mockServices.discovery.mock(); @@ -83,18 +83,22 @@ describe('ServerPermissionClient', () => { const client = ServerPermissionClient.fromConfig(new ConfigReader({}), { discovery, tokenManager: mockServices.tokenManager.mock(), + auth: mockServices.auth(), }); - await client.authorize([ - { - permission: testBasicPermission, - }, - ]); + await client.authorize( + [ + { + permission: testBasicPermission, + }, + ], + { credentials: mockCredentials.none() }, + ); expect(mockAuthorizeHandler).not.toHaveBeenCalled(); }); - it('should bypass the permission backend if permissions are enabled and request has valid server token', async () => { + it('should bypass the permission backend if permissions are enabled and request has valid server credentials', async () => { const client = ServerPermissionClient.fromConfig(config, { discovery, tokenManager: mockServices.tokenManager.mock(), @@ -102,13 +106,13 @@ describe('ServerPermissionClient', () => { }); await client.authorize([{ permission: testBasicPermission }], { - token: mockCredentials.service.token(), + credentials: mockCredentials.service(), }); expect(mockAuthorizeHandler).not.toHaveBeenCalled(); }); - it('should call the permission backend if permissions are enabled and request does not have valid server token', async () => { + it('should call the permission backend if permissions are enabled and request does not have valid server credentials', async () => { const client = ServerPermissionClient.fromConfig(config, { discovery, tokenManager: mockServices.tokenManager.mock(), @@ -116,10 +120,18 @@ describe('ServerPermissionClient', () => { }); await client.authorize([{ permission: testBasicPermission }], { - token: mockCredentials.user.token(), + credentials: mockCredentials.user(), }); expect(mockAuthorizeHandler).toHaveBeenCalled(); + expect( + mockAuthorizeHandler.mock.calls[0][0].headers.get('authorization'), + ).toBe( + mockCredentials.service.header({ + onBehalfOf: mockCredentials.user(), + targetPluginId: 'permission', + }), + ); }); }); @@ -145,201 +157,59 @@ describe('ServerPermissionClient', () => { const client = ServerPermissionClient.fromConfig(new ConfigReader({}), { discovery, tokenManager: mockServices.tokenManager.mock(), - }); - - await client.authorizeConditional([ - { permission: testResourcePermission }, - ]); - - expect(mockAuthorizeHandler).not.toHaveBeenCalled(); - }); - - it('should bypass the permission backend if permissions are enabled and request has valid server token', async () => { - const client = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager: mockServices.tokenManager.mock(), + auth: mockServices.auth(), }); await client.authorizeConditional( [{ permission: testResourcePermission }], { - token: mockCredentials.service.token(), + credentials: mockCredentials.none(), }, ); expect(mockAuthorizeHandler).not.toHaveBeenCalled(); }); - it('should call the permission backend if permissions are enabled and request does not have valid server token', async () => { - const auth = mockServices.auth(); + it('should bypass the permission backend if permissions are enabled and request has valid server credentials', async () => { const client = ServerPermissionClient.fromConfig(config, { discovery, tokenManager: mockServices.tokenManager.mock(), - auth, + auth: mockServices.auth(), }); await client.authorizeConditional( [{ permission: testResourcePermission }], { - token: mockCredentials.user.token(), + credentials: mockCredentials.service(), + }, + ); + + expect(mockAuthorizeHandler).not.toHaveBeenCalled(); + }); + + it('should call the permission backend if permissions are enabled and request does not have valid server credentials', async () => { + const client = ServerPermissionClient.fromConfig(config, { + discovery, + tokenManager: mockServices.tokenManager.mock(), + auth: mockServices.auth(), + }); + + await client.authorizeConditional( + [{ permission: testResourcePermission }], + { + credentials: mockCredentials.user(), }, ); expect(mockAuthorizeHandler).toHaveBeenCalled(); - }); - }); - - describe('with credentials', () => { - describe('authorize', () => { - let mockAuthorizeHandler: jest.Mock; - - beforeEach(() => { - mockAuthorizeHandler = jest.fn((req, res, { json }: RestContext) => { - const responses = req.body.items.map( - (r: IdentifiedPermissionMessage) => ({ - id: r.id, - result: AuthorizeResult.ALLOW, - }), - ); - - return res(json({ items: responses })); - }); - - server.use(rest.post(`${mockBaseUrl}/authorize`, mockAuthorizeHandler)); - }); - - it('should bypass the permission backend if permissions are disabled', async () => { - const client = ServerPermissionClient.fromConfig(new ConfigReader({}), { - discovery, - tokenManager: mockServices.tokenManager.mock(), - auth: mockServices.auth(), - }); - - await client.authorize( - [ - { - permission: testBasicPermission, - }, - ], - { credentials: mockCredentials.none() }, - ); - - expect(mockAuthorizeHandler).not.toHaveBeenCalled(); - }); - - it('should bypass the permission backend if permissions are enabled and request has valid server token', async () => { - const client = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager: mockServices.tokenManager.mock(), - auth: mockServices.auth(), - }); - - await client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.service(), - }); - - expect(mockAuthorizeHandler).not.toHaveBeenCalled(); - }); - - it('should call the permission backend if permissions are enabled and request does not have valid server token', async () => { - const client = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager: mockServices.tokenManager.mock(), - auth: mockServices.auth(), - }); - - await client.authorize([{ permission: testBasicPermission }], { - credentials: mockCredentials.user(), - }); - - expect(mockAuthorizeHandler).toHaveBeenCalled(); - expect( - mockAuthorizeHandler.mock.calls[0][0].headers.get('authorization'), - ).toBe( - mockCredentials.service.header({ - onBehalfOf: mockCredentials.user(), - targetPluginId: 'permission', - }), - ); - }); - }); - - describe('authorizeConditional', () => { - let mockAuthorizeHandler: jest.Mock; - - beforeEach(() => { - mockAuthorizeHandler = jest.fn((req, res, { json }: RestContext) => { - const responses = req.body.items.map( - (r: IdentifiedPermissionMessage) => ({ - id: r.id, - result: AuthorizeResult.ALLOW, - }), - ); - - return res(json({ items: responses })); - }); - - server.use(rest.post(`${mockBaseUrl}/authorize`, mockAuthorizeHandler)); - }); - - it('should bypass the permission backend if permissions are disabled', async () => { - const client = ServerPermissionClient.fromConfig(new ConfigReader({}), { - discovery, - tokenManager: mockServices.tokenManager.mock(), - auth: mockServices.auth(), - }); - - await client.authorizeConditional( - [{ permission: testResourcePermission }], - { - credentials: mockCredentials.none(), - }, - ); - - expect(mockAuthorizeHandler).not.toHaveBeenCalled(); - }); - - it('should bypass the permission backend if permissions are enabled and request has valid server token', async () => { - const client = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager: mockServices.tokenManager.mock(), - auth: mockServices.auth(), - }); - - await client.authorizeConditional( - [{ permission: testResourcePermission }], - { - credentials: mockCredentials.service(), - }, - ); - - expect(mockAuthorizeHandler).not.toHaveBeenCalled(); - }); - - it('should call the permission backend if permissions are enabled and request does not have valid server token', async () => { - const client = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager: mockServices.tokenManager.mock(), - auth: mockServices.auth(), - }); - - await client.authorizeConditional( - [{ permission: testResourcePermission }], - { - credentials: mockCredentials.user(), - }, - ); - - expect(mockAuthorizeHandler).toHaveBeenCalled(); - expect( - mockAuthorizeHandler.mock.calls[0][0].headers.get('authorization'), - ).toBe( - mockCredentials.service.header({ - onBehalfOf: mockCredentials.user(), - targetPluginId: 'permission', - }), - ); - }); + expect( + mockAuthorizeHandler.mock.calls[0][0].headers.get('authorization'), + ).toBe( + mockCredentials.service.header({ + onBehalfOf: mockCredentials.user(), + targetPluginId: 'permission', + }), + ); }); }); diff --git a/plugins/permission-node/src/ServerPermissionClient.ts b/plugins/permission-node/src/ServerPermissionClient.ts index cf54f3b894..e21067bfa1 100644 --- a/plugins/permission-node/src/ServerPermissionClient.ts +++ b/plugins/permission-node/src/ServerPermissionClient.ts @@ -146,14 +146,6 @@ export class ServerPermissionClient implements PermissionsService { return options.credentials; } - if (options?.token) { - try { - return await this.#auth.authenticate(options.token); - } catch { - // ignore - } - } - return undefined; } diff --git a/plugins/permission-node/src/policy/index.ts b/plugins/permission-node/src/policy/index.ts index f151cfb4fb..fd47ed7e62 100644 --- a/plugins/permission-node/src/policy/index.ts +++ b/plugins/permission-node/src/policy/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export type { PermissionPolicy, PolicyQuery } from './types'; +export type { PermissionPolicy, PolicyQuery, PolicyQueryUser } from './types'; diff --git a/plugins/permission-node/src/policy/types.ts b/plugins/permission-node/src/policy/types.ts index 0f469cfbfe..a7b8b75218 100644 --- a/plugins/permission-node/src/policy/types.ts +++ b/plugins/permission-node/src/policy/types.ts @@ -18,7 +18,11 @@ import { Permission, PolicyDecision, } from '@backstage/plugin-permission-common'; -import { BackstageIdentityResponse } from '@backstage/plugin-auth-node'; +import { BackstageUserIdentity } from '@backstage/plugin-auth-node'; +import { + BackstageCredentials, + BackstageUserInfo, +} from '@backstage/backend-plugin-api'; /** * A query to be evaluated by the {@link PermissionPolicy}. @@ -34,6 +38,44 @@ export type PolicyQuery = { permission: Permission; }; +/** + * The context within which a policy query is evaluated. + * + * @public + */ +export type PolicyQueryUser = { + /** + * The token used to authenticate the user within Backstage. + * + * @deprecated User the `credentials` field in combination with `coreServices.auth` to generate a request token instead. + */ + token: string; + + /** + * The number of seconds until the token expires. If not set, it can be assumed that the token does not expire. + * + * @deprecated This field is deprecated and will be removed in a future release. + */ + expiresInSeconds?: number; + + /** + * A plaintext description of the identity that is encapsulated within the token. + * + * @deprecated Use the `info` field instead. + */ + identity: BackstageUserIdentity; + + /** + * The credentials of the user making the request. + */ + credentials: BackstageCredentials; + + /** + * The information for the user making the request. + */ + info: BackstageUserInfo; +}; + /** * A policy to evaluate authorization requests for any permissioned action performed in Backstage. * @@ -51,8 +93,5 @@ export type PolicyQuery = { * @public */ export interface PermissionPolicy { - handle( - request: PolicyQuery, - user?: BackstageIdentityResponse, - ): Promise; + handle(request: PolicyQuery, user?: PolicyQueryUser): Promise; } diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index 36e30714f6..0a7e06ed91 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -23,7 +23,7 @@ import { NoInfer } from './integration/util'; /** * A conditional rule that can be provided in an - * {@link @backstage/permission-common#AuthorizeDecision} response to an authorization request. + * {@link @backstage/plugin-permission-common#AuthorizeDecision} response to an authorization request. * * @remarks * diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index 1eb951e625..2b19b7e849 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-permission-react +## 0.4.24 + +### Patch Changes + +- 28b2cfb: Improve API Reference documentation +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + ## 0.4.23 ### Patch Changes diff --git a/plugins/permission-react/api-report.md b/plugins/permission-react/api-report.md index 3c1961c92c..4c51d16b80 100644 --- a/plugins/permission-react/api-report.md +++ b/plugins/permission-react/api-report.md @@ -100,6 +100,4 @@ export function usePermission( resourceRef: string | undefined; }, ): AsyncPermissionResult; - -// (No @packageDocumentation comment for this package) ``` diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index 9ebb13e633..c119be8eb9 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.23", + "version": "0.4.24", "backstage": { "role": "web-library", "pluginId": "permission", diff --git a/plugins/permission-react/src/index.ts b/plugins/permission-react/src/index.ts index 57352c5cdd..de6958b3e5 100644 --- a/plugins/permission-react/src/index.ts +++ b/plugins/permission-react/src/index.ts @@ -14,6 +14,11 @@ * limitations under the License. */ +/** + * Components and hooks to help implement permissions in Backstage frontend plugins. + * + * @packageDocumentation + */ export * from './components'; export * from './hooks'; export * from './apis'; diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index d57f4936fb..d906b17327 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-proxy-backend +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 0.5.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + ## 0.5.2-next.0 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 18ad338131..ec3594f985 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.5.2-next.0", + "version": "0.5.4-next.0", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin", diff --git a/plugins/proxy-backend/src/service/router.config.test.ts b/plugins/proxy-backend/src/service/router.config.test.ts index ba81553a4f..be26ddb9bc 100644 --- a/plugins/proxy-backend/src/service/router.config.test.ts +++ b/plugins/proxy-backend/src/service/router.config.test.ts @@ -19,7 +19,7 @@ import { createServiceFactory, } from '@backstage/backend-plugin-api'; import { - setupRequestMockHandlers, + registerMswTestHooks, startTestBackend, } from '@backstage/backend-test-utils'; import { @@ -36,7 +36,7 @@ import fetch from 'node-fetch'; describe('createRouter reloadable configuration', () => { const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); it('should be able to observe the config', async () => { // Grab the subscriber function and use mutable config data to mock a config file change diff --git a/plugins/proxy-backend/src/service/router.credentials.test.ts b/plugins/proxy-backend/src/service/router.credentials.test.ts index 54c865ba11..ac32179ef3 100644 --- a/plugins/proxy-backend/src/service/router.credentials.test.ts +++ b/plugins/proxy-backend/src/service/router.credentials.test.ts @@ -20,7 +20,7 @@ import { } from '@backstage/backend-app-api'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, startTestBackend, } from '@backstage/backend-test-utils'; import { ResponseError } from '@backstage/errors'; @@ -34,7 +34,7 @@ import fetch from 'node-fetch'; describe('credentials', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); it('handles all valid credentials settings', async () => { const config = { diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index 99ff7bd723..bb5b2644ae 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.1.14 + +### Patch Changes + +- 661b354: Fixed a bug where the `RepoUrlPicker` would still require the `owner` field for `azure` +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.1.13-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index 672689ef73..c2b7a39e0c 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.1.13-next.0", + "version": "0.1.15-next.0", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index f4453bab7a..04236c2a4b 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.1.12 + +### Patch Changes + +- b5deed0: Add support for `autocomplete` handler to provide autocomplete options for `RepoUrlPicker` +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/plugin-bitbucket-cloud-common@0.2.21 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.21-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.1.11-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index d4432ee3d5..39080241a0 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud", - "version": "0.1.11-next.0", + "version": "0.1.13-next.0", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.examples.test.ts index 5e95f41751..6b4b77879e 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.examples.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.examples.test.ts @@ -29,7 +29,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { createPublishBitbucketCloudAction } from './bitbucketCloud'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; @@ -66,7 +66,7 @@ describe('publish:bitbucketCloud', () => { }, }); const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.test.ts index 6a079a3f9b..6cac8a0e1c 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.test.ts @@ -29,7 +29,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { createPublishBitbucketCloudAction } from './bitbucketCloud'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; @@ -56,7 +56,7 @@ describe('publish:bitbucketCloud', () => { }, }); const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.examples.test.ts index 472c7a6a03..3e306c4065 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.examples.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.examples.test.ts @@ -21,7 +21,7 @@ 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'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; describe('bitbucket:pipelines:run', () => { @@ -48,7 +48,7 @@ describe('bitbucket:pipelines:run', () => { build_number: 1, }; const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts index 9386246868..edabe2a7ba 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts @@ -16,7 +16,7 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; @@ -48,7 +48,7 @@ describe('bitbucket:pipelines:run', () => { build_number: 1, }; const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); beforeEach(() => { jest.clearAllMocks(); diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index f614b16994..93801a3012 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.1.12 + +### Patch Changes + +- 6a4ad4e: Instead of using hardcoded `targetBranch` now fetch the default branch from Bitbucket repository. + This prevents from errors when no `targetBranch` is provided and the default repository branch is different from `master`, for example: `main`. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.1.11-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index 16ce106570..b995ef57da 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.1.11-next.0", + "version": "0.1.13-next.0", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.examples.test.ts index eae8385822..101379d1e6 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.examples.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.examples.test.ts @@ -29,7 +29,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { createPublishBitbucketServerAction } from './bitbucketServer'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import yaml from 'yaml'; @@ -68,7 +68,7 @@ describe('publish:bitbucketServer', () => { }, }); const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.test.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.test.ts index 1d42e7368c..250b3f9b54 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.test.ts @@ -29,7 +29,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { createPublishBitbucketServerAction } from './bitbucketServer'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; @@ -66,7 +66,7 @@ describe('publish:bitbucketServer', () => { }, }); const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.examples.test.ts index 1d3556fc88..b83ba2be55 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.examples.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.examples.test.ts @@ -29,7 +29,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { createPublishBitbucketServerPullRequestAction } from './bitbucketServerPullRequest'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import yaml from 'yaml'; @@ -191,7 +191,7 @@ describe('publish:bitbucketServer:pull-request', () => { }; const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.test.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.test.ts index 61adf0e3c8..37df535fec 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServerPullRequest.test.ts @@ -29,7 +29,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { createPublishBitbucketServerPullRequestAction } from './bitbucketServerPullRequest'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; @@ -221,7 +221,7 @@ describe('publish:bitbucketServer:pull-request', () => { ]; const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md index dc3a0f1597..b905cc33e1 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.12 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.12 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.12-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.12-next.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.2.11-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index 35b23e39d9..a853f795af 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", - "version": "0.2.11-next.0", + "version": "0.2.13-next.0", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.test.ts index 6633246d70..d452f2e11f 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.examples.test.ts @@ -29,7 +29,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { createPublishBitbucketAction } from './bitbucket'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; @@ -67,7 +67,7 @@ describe('publish:bitbucket', () => { }, }); const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.test.ts index a63d657904..654b0fffcb 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.test.ts @@ -28,7 +28,7 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { createPublishBitbucketAction } from './bitbucket'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; @@ -63,7 +63,7 @@ describe('publish:bitbucket', () => { }, }); const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-bitbucket/src/deprecated.ts b/plugins/scaffolder-backend-module-bitbucket/src/deprecated.ts index a17c88c3a4..a9daa771f0 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/deprecated.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/deprecated.ts @@ -19,25 +19,29 @@ import * as bitbucketServer from '@backstage/plugin-scaffolder-backend-module-bi export { createPublishBitbucketAction } from './actions/bitbucket'; /** - * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-bitbucket-cloud instead + * @public + * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` instead */ export const createPublishBitbucketCloudAction = bitbucketCloud.createPublishBitbucketCloudAction; /** - * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-bitbucket-cloud instead + * @public + * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` instead */ export const createBitbucketPipelinesRunAction = bitbucketCloud.createBitbucketPipelinesRunAction; /** - * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-bitbucket-server instead + * @public + * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-server` instead */ export const createPublishBitbucketServerAction = bitbucketServer.createPublishBitbucketServerAction; /** - * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-bitbucket-server instead + * @public + * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-server` instead */ export const createPublishBitbucketServerPullRequestAction = bitbucketServer.createPublishBitbucketServerPullRequestAction; diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index d24746f67a..29677250a5 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.2.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.2.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.2.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.2.22-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 34e49f740f..39fb5f49b9 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.2.22-next.0", + "version": "0.2.24-next.0", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", 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 da82849c1e..36ae750723 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 @@ -20,7 +20,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { createMockDirectory, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -32,7 +32,7 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test- describe('confluence:transform:markdown examples', () => { const baseUrl = `https://confluence.example.com`; const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); const config = new ConfigReader({ confluence: { 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 b19259b3df..4c3c6b891f 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 @@ -21,7 +21,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { createMockDirectory, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import type { ActionContext } from '@backstage/plugin-scaffolder-node'; import { rest } from 'msw'; @@ -31,7 +31,7 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test- describe('confluence:transform:markdown', () => { const baseUrl = `https://nodomain.confluence.com`; const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); const config = new ConfigReader({ confluence: { diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index dfa740b4a6..865478ea2b 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.47-next.0 + +### Patch Changes + +- dae85df: Add examples for `fetch:cookiecutter` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.2.46 + +### Patch Changes + +- 0ac124b: Updated configuration instructions +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.46-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.2.45-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index ea0a7fad77..2f21b89244 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.2.45-next.0", + "version": "0.2.47-next.0", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module", @@ -53,6 +53,7 @@ "command-exists": "^1.2.9", "fs-extra": "^11.2.0", "winston": "^3.2.1", + "yaml": "^2.0.0", "yn": "^4.0.0" }, "devDependencies": { diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.test.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.test.ts new file mode 100644 index 0000000000..ae46ef1bd6 --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.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 { UrlReader, ContainerRunner } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; +import { ScmIntegrations } from '@backstage/integration'; +import { createMockDirectory } from '@backstage/backend-test-utils'; +import { createFetchCookiecutterAction } from './cookiecutter'; +import { join } from 'path'; +import type { ActionContext } from '@backstage/plugin-scaffolder-node'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { examples } from './cookiecutter.examples'; +import yaml from 'yaml'; + +const executeShellCommand = jest.fn(); +const commandExists = jest.fn(); +const fetchContents = jest.fn(); + +jest.mock('@backstage/plugin-scaffolder-node', () => ({ + ...jest.requireActual('@backstage/plugin-scaffolder-node'), + fetchContents: (...args: any[]) => fetchContents(...args), + executeShellCommand: (...args: any[]) => executeShellCommand(...args), +})); + +jest.mock( + 'command-exists', + () => + (...args: any[]) => + commandExists(...args), +); + +describe('fetch:cookiecutter', () => { + const mockDir = createMockDirectory({ mockOsTmpDir: true }); + const integrations = ScmIntegrations.fromConfig( + new ConfigReader({ + integrations: { + azure: [ + { host: 'dev.azure.com', token: 'tokenlols' }, + { host: 'myazurehostnotoken.com' }, + ], + }, + }), + ); + + const mockTmpDir = mockDir.path; + + let mockContext: ActionContext<{ + url: string; + targetPath?: string; + values: JsonObject; + copyWithoutRender?: string[]; + extensions?: string[]; + imageName?: string; + }>; + + const containerRunner: jest.Mocked = { + runContainer: jest.fn(), + }; + + const mockReader: UrlReader = { + readUrl: jest.fn(), + readTree: jest.fn(), + search: jest.fn(), + }; + + const action = createFetchCookiecutterAction({ + integrations, + containerRunner, + reader: mockReader, + }); + + beforeEach(() => { + jest.resetAllMocks(); + + mockContext = createMockActionContext({ + input: { + url: 'https://google.com/cookie/cutter', + targetPath: 'something', + values: { + help: 'me', + }, + }, + templateInfo: { + entityRef: 'template:default/cookiecutter', + baseUrl: 'somebase', + }, + workspacePath: mockTmpDir, + }); + mockDir.setContent({ template: {} }); + + commandExists.mockResolvedValue(null); + + // Mock when run container is called it creates some new files in the mock filesystem + containerRunner.runContainer.mockImplementation(async () => { + mockDir.setContent({ + 'intermediate/testfile.json': '{}', + }); + }); + + // Mock when executeShellCommand is called it creates some new files in the mock filesystem + executeShellCommand.mockImplementation(async () => { + mockDir.setContent({ + 'intermediate/testfile.json': '{}', + }); + }); + }); + + it(`should ${examples[0].description}`, async () => { + const input = yaml.parse(examples[0].example).steps[0].input; + + commandExists.mockResolvedValue(false); + + fetchContents.mockImplementation(() => Promise.resolve()); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith( + expect.objectContaining({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: mockContext.input.url, + outputPath: join( + mockTmpDir, + 'template', + "{{cookiecutter and 'contents'}}", + ), + }), + ); + expect(containerRunner.runContainer).toHaveBeenCalled(); + }); + + it(`should ${examples[1].description}`, async () => { + const input = yaml.parse(examples[1].example).steps[0].input; + + commandExists.mockResolvedValue(false); + + fetchContents.mockImplementation(() => Promise.resolve()); + + await action.handler({ + ...mockContext, + input: input, + }); + + expect(fetchContents).toHaveBeenCalledWith( + expect.objectContaining({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: mockContext.input.url, + outputPath: join( + mockTmpDir, + 'template', + "{{cookiecutter and 'contents'}}", + ), + }), + ); + expect(containerRunner.runContainer).toHaveBeenCalled(); + }); + + it(`should ${examples[2].description}`, async () => { + const input = yaml.parse(examples[2].example).steps[0].input; + + commandExists.mockResolvedValue(false); + + fetchContents.mockImplementation(() => Promise.resolve()); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith( + expect.objectContaining({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: mockContext.input.url, + outputPath: join( + mockTmpDir, + 'template', + "{{cookiecutter and 'contents'}}", + ), + }), + ); + + expect(containerRunner.runContainer).toHaveBeenCalled(); + }); + + it(`should ${examples[3].description}`, async () => { + const input = yaml.parse(examples[3].example).steps[0].input; + + commandExists.mockResolvedValue(false); + + fetchContents.mockImplementation(() => Promise.resolve()); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith( + expect.objectContaining({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: mockContext.input.url, + outputPath: join( + mockTmpDir, + 'template', + "{{cookiecutter and 'contents'}}", + ), + }), + ); + expect(containerRunner.runContainer).toHaveBeenCalled(); + }); + + it(`should ${examples[4].description}`, async () => { + const input = yaml.parse(examples[4].example).steps[0].input; + + commandExists.mockResolvedValue(false); + + fetchContents.mockImplementation(() => Promise.resolve()); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith( + expect.objectContaining({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: mockContext.input.url, + outputPath: join( + mockTmpDir, + 'template', + "{{cookiecutter and 'contents'}}", + ), + }), + ); + expect(containerRunner.runContainer).toHaveBeenCalled(); + }); +}); diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.ts new file mode 100644 index 0000000000..6ebada50f0 --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.ts @@ -0,0 +1,119 @@ +/* + * 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: 'Fetch and template using cookiecutter with minimal options.', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:cookiecutter', + name: 'Fetch and Template Using Cookiecutter', + input: { + url: 'https://google.com/cookie/cutter', + values: { + help: 'me', + }, + }, + }, + ], + }), + }, + { + description: + 'Fetch and template using cookiecutter with custom target path.', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:cookiecutter', + name: 'Fetch and Template Using Cookiecutter', + input: { + url: 'https://google.com/cookie/cutter', + targetPath: 'custom-path', + values: { + help: 'me', + }, + }, + }, + ], + }), + }, + { + description: + 'Fetch and template using cookiecutter with copyWithoutRender.', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:cookiecutter', + name: 'Fetch and Template Using Cookiecutter', + input: { + url: 'https://google.com/cookie/cutter', + values: { + help: 'me', + }, + copyWithoutRender: ['.github/*', '*.md'], + }, + }, + ], + }), + }, + { + description: + 'Fetch and template using cookiecutter with custom extensions.', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:cookiecutter', + name: 'Fetch and Template Using Cookiecutter', + input: { + url: 'https://google.com/cookie/cutter', + values: { + help: 'me', + }, + extensions: ['jinja2_time.TimeExtension'], + }, + }, + ], + }), + }, + { + description: + 'Fetch and template using cookiecutter with a custom Docker image.', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:cookiecutter', + name: 'Fetch and Template Using Cookiecutter', + input: { + url: 'https://google.com/cookie/cutter', + values: { + help: 'me', + }, + imageName: 'custom/cookiecutter-image', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts index d170acb5d0..efbe0a07cf 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts @@ -28,6 +28,7 @@ import { fetchContents, executeShellCommand, } from '@backstage/plugin-scaffolder-node'; +import { examples } from './cookiecutter.examples'; export class CookiecutterRunner { private readonly containerRunner?: ContainerRunner; @@ -156,6 +157,7 @@ export function createFetchCookiecutterAction(options: { id: 'fetch:cookiecutter', description: 'Downloads a template from the given URL into the workspace, and runs cookiecutter on it.', + examples, schema: { input: { type: 'object', diff --git a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md index 5afcde9965..05843ee288 100644 --- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-scaffolder-backend-module-gcp +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.1.0 + +### Minor Changes + +- 0b52438: Serialization of the scaffolder workspace into GCP bucket + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.1.0-next.2 ### Minor Changes diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index 2f8f130d39..f6355e93f4 100644 --- a/plugins/scaffolder-backend-module-gcp/package.json +++ b/plugins/scaffolder-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gcp", - "version": "0.1.0-next.2", + "version": "0.1.1-next.0", "description": "The GCP Bucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index cc4d9612a1..d95047ba64 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.1.14 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.1.13-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 06c5d063d4..4be6e4c56c 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.1.13-next.0", + "version": "0.1.15-next.0", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.test.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.test.ts index 1f069d3835..c102353209 100644 --- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.test.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.test.ts @@ -30,7 +30,7 @@ import path from 'path'; import { createPublishGerritAction } from './gerrit'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; @@ -61,7 +61,7 @@ describe('publish:gerrit', () => { }, }); const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index 5f26aa6a2d..6bc250f801 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.1.11-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index f6563795cd..22b6ab700f 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.1.11-next.0", + "version": "0.1.13-next.0", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.test.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.test.ts index 8a72265df0..7812ded60a 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.test.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.test.ts @@ -18,7 +18,7 @@ import { ConfigReader } from '@backstage/config'; import { createPublishGiteaAction } from './gitea'; import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; import { rest } from 'msw'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { setupServer } from 'msw/node'; @@ -62,7 +62,7 @@ describe('publish:gitea', () => { }); const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index e3cd0904aa..0399be8de3 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,60 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.4.0 + +### Minor Changes + +- 70c4b36: Adds support for custom tag policies when creating GitHub environments. + +### Patch Changes + +- ccfc9d1: Fixed bug resulting from missing required owner and repo arguments in `getEnvironmentPublicKey` in action `github:environment:create`. + + Adding environment secrets now works as expected. + +- 141f366: Added action to enable GitHub Pages on a repo +- 4410fed: Fixed issue with octokit call missing owner and repo when creating environment variables and secrets using github:environment:create action +- dfaa28d: Adds `requireLastPushApproval` input property to configure Branch Protection Settings in `github:publish` action + + Adds `requireLastPushApproval` input property to configure Branch Protection Settings in `github:repo:push` action + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.4.0-next.1 + +### Patch Changes + +- 141f366: Added action to enable GitHub Pages on a repo +- dfaa28d: Adds `requireLastPushApproval` input property to configure Branch Protection Settings in `github:publish` action + + Adds `requireLastPushApproval` input property to configure Branch Protection Settings in `github:repo:push` action + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.4.0-next.0 ### Minor Changes diff --git a/plugins/scaffolder-backend-module-github/api-report.md b/plugins/scaffolder-backend-module-github/api-report.md index 94b642bdbf..4ec5795dd9 100644 --- a/plugins/scaffolder-backend-module-github/api-report.md +++ b/plugins/scaffolder-backend-module-github/api-report.md @@ -107,6 +107,21 @@ export function createGithubIssuesLabelAction(options: { JsonObject >; +// @public +export function createGithubPagesEnableAction(options: { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; +}): TemplateAction< + { + repoUrl: string; + buildType?: 'legacy' | 'workflow' | undefined; + sourceBranch?: string | undefined; + sourcePath?: '/' | '/docs' | undefined; + token?: string | undefined; + }, + JsonObject +>; + // @public export interface CreateGithubPullRequestActionOptions { clientFactory?: (input: { @@ -257,6 +272,7 @@ export function createGithubRepoPushAction(options: { sourcePath?: string | undefined; token?: string | undefined; requiredCommitSigning?: boolean | undefined; + requireLastPushApproval?: boolean | undefined; }, JsonObject >; @@ -329,6 +345,7 @@ export function createPublishGithubAction(options: { requiredStatusCheckContexts?: string[] | undefined; requireBranchesToBeUpToDate?: boolean | undefined; requiredConversationResolution?: boolean | undefined; + requireLastPushApproval?: boolean | undefined; repoVisibility?: 'internal' | 'private' | 'public' | undefined; collaborators?: | ( diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 1fd404c6f9..a43469d7e5 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.4.0-next.0", + "version": "0.4.1-next.0", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts b/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts index ea92201eec..9989a80724 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts @@ -38,6 +38,7 @@ type BranchProtectionOptions = { }; requireBranchesToBeUpToDate?: boolean; requiredConversationResolution?: boolean; + requireLastPushApproval: boolean; defaultBranch?: string; enforceAdmins?: boolean; dismissStaleReviews?: boolean; @@ -56,6 +57,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ requiredStatusCheckContexts = [], requireBranchesToBeUpToDate = true, requiredConversationResolution = false, + requireLastPushApproval = false, defaultBranch = 'master', enforceAdmins = true, dismissStaleReviews = false, @@ -88,6 +90,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ require_code_owner_reviews: requireCodeOwnerReviews, bypass_pull_request_allowances: bypassPullRequestAllowances, dismiss_stale_reviews: dismissStaleReviews, + require_last_push_approval: requireLastPushApproval, }, required_conversation_resolution: requiredConversationResolution, }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts index 9eda9a2eb0..31ec7ecc90 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts @@ -1010,174 +1010,6 @@ describe('publish:github', () => { ); }); - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requireCodeOwnerReviews', async () => { - mockOctokit.rest.users.getByUsername.mockResolvedValue({ - data: { type: 'User' }, - }); - - mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ - data: { - name: 'repo', - }, - }); - - await action.handler(mockContext); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - requireCodeOwnerReviews: true, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: true, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - requireCodeOwnerReviews: false, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - }); - - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of enforceAdmins', async () => { - mockOctokit.rest.users.getByUsername.mockResolvedValue({ - data: { type: 'User' }, - }); - - mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ - data: { - name: 'repo', - }, - }); - - await action.handler(mockContext); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - protectEnforceAdmins: false, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: false, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - protectEnforceAdmins: true, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - }); - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requiredStatusCheckContexts and requireBranchesToBeUpToDate', async () => { mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, @@ -1204,6 +1036,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -1232,6 +1065,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -1259,6 +1093,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: ['statusCheck'], requireBranchesToBeUpToDate: false, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -1285,6 +1120,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -1346,255 +1182,6 @@ describe('publish:github', () => { }); }); - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of dismissStaleReviews', async () => { - mockOctokit.rest.users.getByUsername.mockResolvedValue({ - data: { type: 'User' }, - }); - - mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ - data: { - name: 'repo', - }, - }); - - await action.handler(mockContext); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - dismissStaleReviews: true, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: true, - requiredCommitSigning: false, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - dismissStaleReviews: false, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - }); - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requiredConversationResolution', async () => { - mockOctokit.rest.users.getByUsername.mockResolvedValue({ - data: { type: 'User' }, - }); - - mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ - data: { - name: 'repo', - }, - }); - - await action.handler(mockContext); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - requiredConversationResolution: true, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: true, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - requiredConversationResolution: false, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - }); - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requiredApprovingReviewCount', async () => { - mockOctokit.rest.users.getByUsername.mockResolvedValue({ - data: { type: 'User' }, - }); - - mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ - data: { - name: 'repo', - }, - }); - - await action.handler(mockContext); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - requiredApprovingReviewCount: 2, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 2, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - requiredApprovingReviewCount: 0, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 0, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - }); it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of restrictions', async () => { mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, @@ -1621,6 +1208,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -1653,6 +1241,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -1685,6 +1274,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -1719,6 +1309,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -1753,6 +1344,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -1787,94 +1379,12 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, }); }); - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requiredCommitSigning', async () => { - mockOctokit.rest.users.getByUsername.mockResolvedValue({ - data: { type: 'User' }, - }); - - mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ - data: { - name: 'repo', - }, - }); - - await action.handler(mockContext); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - requiredCommitSigning: false, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: false, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - requiredCommitSigning: true, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repo', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - restrictions: undefined, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - requiredCommitSigning: true, - }); - }); it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of bypassPullRequestAllowances', async () => { mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, @@ -1901,6 +1411,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -1931,6 +1442,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -1961,6 +1473,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -1991,6 +1504,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -2025,6 +1539,7 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, @@ -2059,9 +1574,145 @@ describe('publish:github', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, requiredCommitSigning: false, }); }); + + it.each([ + { + inputProperty: 'dismissStaleReviews', + defaultValue: false, + overrideValue: true, + }, + { + inputProperty: 'requiredConversationResolution', + defaultValue: false, + overrideValue: true, + }, + { + inputProperty: 'requireLastPushApproval', + defaultValue: false, + overrideValue: true, + }, + { + inputProperty: 'requiredApprovingReviewCount', + defaultValue: 1, + overrideValue: 2, + }, + { + inputProperty: 'requiredCommitSigning', + defaultValue: false, + overrideValue: true, + }, + { + inputProperty: 'protectEnforceAdmins', + defaultValue: true, + overrideValue: false, + octokitParameter: 'enforceAdmins', + }, + { + inputProperty: 'requireCodeOwnerReviews', + defaultValue: false, + overrideValue: true, + }, + ])( + 'should call enableBranchProtectionOnDefaultRepoBranch with the correct values of $inputProperty', + async ({ + inputProperty, + defaultValue, + overrideValue, + octokitParameter, + }) => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'User' }, + }); + + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + data: { + name: 'repo', + }, + }); + + await action.handler(mockContext); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockOctokit, + repoName: 'repo', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + bypassPullRequestAllowances: undefined, + requiredApprovingReviewCount: 1, + restrictions: undefined, + requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, + requiredConversationResolution: false, + requireLastPushApproval: false, + enforceAdmins: true, + dismissStaleReviews: false, + requiredCommitSigning: false, + [octokitParameter || inputProperty]: defaultValue, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + [inputProperty]: overrideValue, + }, + }); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockOctokit, + repoName: 'repo', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + bypassPullRequestAllowances: undefined, + requiredApprovingReviewCount: 1, + restrictions: undefined, + requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, + requiredConversationResolution: false, + requireLastPushApproval: false, + enforceAdmins: true, + dismissStaleReviews: false, + requiredCommitSigning: false, + [octokitParameter || inputProperty]: overrideValue, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + [inputProperty]: defaultValue, + }, + }); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockOctokit, + repoName: 'repo', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + bypassPullRequestAllowances: undefined, + requiredApprovingReviewCount: 1, + restrictions: undefined, + requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, + requiredConversationResolution: false, + requireLastPushApproval: false, + enforceAdmins: true, + dismissStaleReviews: false, + requiredCommitSigning: false, + [octokitParameter || inputProperty]: defaultValue, + }); + }, + ); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.ts b/plugins/scaffolder-backend-module-github/src/actions/github.ts index f32b65bc9a..c8b22de7e7 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.ts @@ -86,6 +86,7 @@ export function createPublishGithubAction(options: { requiredStatusCheckContexts?: string[]; requireBranchesToBeUpToDate?: boolean; requiredConversationResolution?: boolean; + requireLastPushApproval?: boolean; repoVisibility?: 'private' | 'internal' | 'public'; collaborators?: Array< | { @@ -137,6 +138,7 @@ export function createPublishGithubAction(options: { requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, requiredConversationResolution: inputProps.requiredConversationResolution, + requireLastPushApproval: inputProps.requireLastPushApproval, repoVisibility: inputProps.repoVisibility, defaultBranch: inputProps.defaultBranch, protectDefaultBranch: inputProps.protectDefaultBranch, @@ -187,6 +189,7 @@ export function createPublishGithubAction(options: { requiredStatusCheckContexts = [], requireBranchesToBeUpToDate = true, requiredConversationResolution = false, + requireLastPushApproval = false, repoVisibility = 'private', defaultBranch = 'master', protectDefaultBranch = true, @@ -274,6 +277,7 @@ export function createPublishGithubAction(options: { requiredStatusCheckContexts, requireBranchesToBeUpToDate, requiredConversationResolution, + requireLastPushApproval, config, ctx.logger, gitCommitMessage, diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts index c4eae41029..139d8028eb 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts @@ -304,5 +304,16 @@ describe('github:environment:create', () => { key_id: 'keyid', encrypted_value: expect.any(String), }); + expect( + mockOctokit.rest.actions.getEnvironmentPublicKey, + ).toHaveBeenCalledTimes(1); + expect( + mockOctokit.rest.actions.getEnvironmentPublicKey, + ).toHaveBeenCalledWith({ + repository_id: 'repoid', + owner: 'owner', + repo: 'repository', + environment_name: 'envname', + }); }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts index 98e7cea1a9..d3e8d0e5d2 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts @@ -199,6 +199,8 @@ export function createGithubEnvironmentAction(options: { const publicKeyResponse = await client.rest.actions.getEnvironmentPublicKey({ repository_id: repository.data.id, + owner: owner, + repo: repo, environment_name: name, }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts new file mode 100644 index 0000000000..ee7b5caa65 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts @@ -0,0 +1,90 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; +import { createGithubPagesEnableAction } from './githubPagesEnable'; +import { examples } from './githubPagesEnable.examples'; +import yaml from 'yaml'; + +const mockOctokit = { + request: jest.fn(), +}; + +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + +describe('github:pages', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'mock-token' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + let githubCredentialsProvider: GithubCredentialsProvider; + let action: TemplateAction; + + const input = yaml.parse(examples[0].example).steps[0].input; + const mockContext = createMockActionContext({ + input, + }); + + beforeEach(() => { + githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubPagesEnableAction({ + integrations, + githubCredentialsProvider, + }); + }); + + afterEach(jest.resetAllMocks); + + it('should work with example input', async () => { + await action.handler(mockContext); + + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'owner', + repo: 'repo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.ts new file mode 100644 index 0000000000..be4a33e5df --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.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 { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Enables GitHub Pages for a repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages', + name: 'Enable GitHub Pages', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + buildType: 'workflow', + sourceBranch: 'main', + sourcePath: '/', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts new file mode 100644 index 0000000000..203014b67b --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts @@ -0,0 +1,93 @@ +/* + * 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 { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; +import { createGithubPagesEnableAction } from './githubPagesEnable'; + +const mockOctokit = { + request: jest.fn(), +}; + +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + +describe('github:pages', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'mock-token' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + let githubCredentialsProvider: GithubCredentialsProvider; + let action: TemplateAction; + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + buildType: 'workflow', + sourceBranch: 'main', + sourcePath: '/', + token: 'gph_YourGitHubToken', + }, + }); + + beforeEach(() => { + githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubPagesEnableAction({ + integrations, + githubCredentialsProvider, + }); + }); + + afterEach(jest.resetAllMocks); + + it('should work happy path', async () => { + await action.handler(mockContext); + + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'owner', + repo: 'repo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts new file mode 100644 index 0000000000..044005961e --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts @@ -0,0 +1,130 @@ +/* + * 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 { InputError } from '@backstage/errors'; +import { + GithubCredentialsProvider, + ScmIntegrationRegistry, +} from '@backstage/integration'; +import { Octokit } from 'octokit'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; +import { examples } from './githubPagesEnable.examples'; +import { getOctokitOptions } from '@backstage/plugin-scaffolder-backend-module-github'; + +/** + * Creates a new action that enables GitHub Pages for a repository. + * + * @public + */ +export function createGithubPagesEnableAction(options: { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; +}) { + const { integrations, githubCredentialsProvider } = options; + + return createTemplateAction<{ + repoUrl: string; + buildType?: 'legacy' | 'workflow'; + sourceBranch?: string; + sourcePath?: '/' | '/docs'; + token?: string; + }>({ + id: 'github:pages:enable', + examples, + description: 'Enables GitHub Pages for a repository.', + schema: { + input: { + type: 'object', + required: ['repoUrl'], + 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', + }, + buildType: { + title: 'Build Type', + type: 'string', + description: + 'The GitHub Pages build type - "legacy" or "workflow". Default is "workflow', + }, + sourceBranch: { + title: 'Source Branch', + type: 'string', + description: + 'The the GitHub Pages source branch. Default is "main"', + }, + sourcePath: { + title: 'Source Path', + type: 'string', + description: + 'The the GitHub Pages source path - "/" or "/docs". Default is "/"', + }, + token: { + title: 'Authorization Token', + type: 'string', + description: 'The token to use for authorization to GitHub', + }, + }, + }, + }, + async handler(ctx) { + const { + repoUrl, + buildType = 'workflow', + sourceBranch = 'main', + sourcePath = '/', + token: providedToken, + } = ctx.input; + + const octokitOptions = await getOctokitOptions({ + integrations, + credentialsProvider: githubCredentialsProvider, + token: providedToken, + repoUrl: repoUrl, + }); + const client = new Octokit(octokitOptions); + + const { owner, repo } = parseRepoUrl(repoUrl, integrations); + + if (!owner) { + throw new InputError('Invalid repository owner provided in repoUrl'); + } + + ctx.logger.info( + `Attempting to enable GitHub Pages for ${owner}/${repo} with "${buildType}" build type, on source branch "${sourceBranch}" and source path "${sourcePath}"`, + ); + + await client.request('POST /repos/{owner}/{repo}/pages', { + owner: owner, + repo: repo, + build_type: buildType, + source: { + branch: sourceBranch, + path: sourcePath, + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }); + + ctx.logger.info('Completed enabling GitHub Pages'); + }, + }); +} diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts index 4d16a25774..d0ee4bfa12 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.test.ts @@ -305,168 +305,6 @@ describe('github:repo:push', () => { ); }); - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requireCodeOwnerReviews', async () => { - mockOctokit.rest.repos.get.mockResolvedValue({ - data: { - clone_url: 'https://github.com/clone/url.git', - html_url: 'https://github.com/html/url', - }, - }); - - await action.handler(mockContext); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repository', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - requiredCommitSigning: false, - restrictions: undefined, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - requireCodeOwnerReviews: true, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repository', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: true, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - requiredCommitSigning: false, - restrictions: undefined, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - requireCodeOwnerReviews: false, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repository', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - requiredCommitSigning: false, - restrictions: undefined, - }); - }); - - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of enforceAdmins', async () => { - mockOctokit.rest.repos.get.mockResolvedValue({ - data: { - clone_url: 'https://github.com/clone/url.git', - html_url: 'https://github.com/html/url', - }, - }); - - await action.handler(mockContext); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repository', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - requiredCommitSigning: false, - restrictions: undefined, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - protectEnforceAdmins: true, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repository', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - requiredCommitSigning: false, - restrictions: undefined, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - protectEnforceAdmins: false, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repository', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: false, - dismissStaleReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - requiredCommitSigning: false, - restrictions: undefined, - }); - }); - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requiredStatusCheckContexts and requireBranchesToBeUpToDate', async () => { mockOctokit.rest.repos.get.mockResolvedValue({ data: { @@ -487,6 +325,7 @@ describe('github:repo:push', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, bypassPullRequestAllowances: undefined, @@ -501,7 +340,6 @@ describe('github:repo:push', () => { ...mockContext.input, requiredStatusCheckContexts: ['statusCheck'], requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, }, }); @@ -515,6 +353,7 @@ describe('github:repo:push', () => { requiredStatusCheckContexts: ['statusCheck'], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, bypassPullRequestAllowances: undefined, @@ -542,6 +381,7 @@ describe('github:repo:push', () => { requiredStatusCheckContexts: ['statusCheck'], requireBranchesToBeUpToDate: false, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, bypassPullRequestAllowances: undefined, @@ -556,7 +396,6 @@ describe('github:repo:push', () => { ...mockContext.input, requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, }, }); @@ -570,6 +409,7 @@ describe('github:repo:push', () => { requiredStatusCheckContexts: [], requireBranchesToBeUpToDate: true, requiredConversationResolution: false, + requireLastPushApproval: false, enforceAdmins: true, dismissStaleReviews: false, bypassPullRequestAllowances: undefined, @@ -598,165 +438,135 @@ describe('github:repo:push', () => { expect(enableBranchProtectionOnDefaultRepoBranch).not.toHaveBeenCalled(); }); - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of dismissStaleReviews', async () => { - mockOctokit.rest.repos.get.mockResolvedValue({ - data: { - clone_url: 'https://github.com/clone/url.git', - html_url: 'https://github.com/html/url', - }, - }); + it.each([ + { + inputProperty: 'dismissStaleReviews', + defaultValue: false, + overrideValue: true, + }, + { + inputProperty: 'requiredConversationResolution', + defaultValue: false, + overrideValue: true, + }, + { + inputProperty: 'requireLastPushApproval', + defaultValue: false, + overrideValue: true, + }, + { + inputProperty: 'requiredApprovingReviewCount', + defaultValue: 1, + overrideValue: 2, + }, + { + inputProperty: 'requiredCommitSigning', + defaultValue: false, + overrideValue: true, + }, + { + inputProperty: 'protectEnforceAdmins', + defaultValue: true, + overrideValue: false, + octokitParameter: 'enforceAdmins', + }, + { + inputProperty: 'requireCodeOwnerReviews', + defaultValue: false, + overrideValue: true, + }, + ])( + 'should call enableBranchProtectionOnDefaultRepoBranch with the correct values of $inputProperty', + async ({ + inputProperty, + defaultValue, + overrideValue, + octokitParameter, + }) => { + mockOctokit.rest.repos.get.mockResolvedValue({ + data: { + clone_url: 'https://github.com/clone/url.git', + html_url: 'https://github.com/html/url', + }, + }); - await action.handler(mockContext); + await action.handler(mockContext); - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repository', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - requiredCommitSigning: false, - restrictions: undefined, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - dismissStaleReviews: true, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repository', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: true, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - requiredCommitSigning: false, - restrictions: undefined, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - dismissStaleReviews: false, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repository', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - requiredCommitSigning: false, - restrictions: undefined, - }); - }); - - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requiredConversationResolution', async () => { - mockOctokit.rest.repos.get.mockResolvedValue({ - data: { - clone_url: 'https://github.com/clone/url.git', - html_url: 'https://github.com/html/url', - }, - }); - - await action.handler(mockContext); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repository', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - requiredCommitSigning: false, - restrictions: undefined, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - requiredConversationResolution: true, - }, - }); - - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repository', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: true, - enforceAdmins: true, - dismissStaleReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - requiredCommitSigning: false, - restrictions: undefined, - }); - - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockOctokit, + repoName: 'repository', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, requiredConversationResolution: false, - }, - }); + requireLastPushApproval: false, + enforceAdmins: true, + dismissStaleReviews: false, + bypassPullRequestAllowances: undefined, + requiredApprovingReviewCount: 1, + requiredCommitSigning: false, + restrictions: undefined, + [octokitParameter || inputProperty]: defaultValue, + }); - expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ - owner: 'owner', - client: mockOctokit, - repoName: 'repository', - logger: mockContext.logger, - defaultBranch: 'master', - requireCodeOwnerReviews: false, - requiredStatusCheckContexts: [], - requireBranchesToBeUpToDate: true, - requiredConversationResolution: false, - enforceAdmins: true, - dismissStaleReviews: false, - bypassPullRequestAllowances: undefined, - requiredApprovingReviewCount: 1, - requiredCommitSigning: false, - restrictions: undefined, - }); - }); + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + [inputProperty]: overrideValue, + }, + }); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockOctokit, + repoName: 'repository', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, + requiredConversationResolution: false, + requireLastPushApproval: false, + enforceAdmins: true, + dismissStaleReviews: false, + bypassPullRequestAllowances: undefined, + requiredApprovingReviewCount: 1, + requiredCommitSigning: false, + restrictions: undefined, + [octokitParameter || inputProperty]: overrideValue, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + [inputProperty]: defaultValue, + }, + }); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockOctokit, + repoName: 'repository', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, + requiredConversationResolution: false, + requireLastPushApproval: false, + enforceAdmins: true, + dismissStaleReviews: false, + bypassPullRequestAllowances: undefined, + requiredApprovingReviewCount: 1, + requiredCommitSigning: false, + restrictions: undefined, + [octokitParameter || inputProperty]: defaultValue, + }); + }, + ); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts index 5b9db78f48..a4fa7673c6 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoPush.ts @@ -75,6 +75,7 @@ export function createGithubRepoPushAction(options: { sourcePath?: string; token?: string; requiredCommitSigning?: boolean; + requireLastPushApproval?: boolean; }>({ id: 'github:repo:push', description: @@ -95,6 +96,7 @@ export function createGithubRepoPushAction(options: { requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, requiredConversationResolution: inputProps.requiredConversationResolution, + requireLastPushApproval: inputProps.requireLastPushApproval, defaultBranch: inputProps.defaultBranch, protectDefaultBranch: inputProps.protectDefaultBranch, protectEnforceAdmins: inputProps.protectEnforceAdmins, @@ -132,6 +134,7 @@ export function createGithubRepoPushAction(options: { requiredStatusCheckContexts = [], requireBranchesToBeUpToDate = true, requiredConversationResolution = false, + requireLastPushApproval = false, token: providedToken, requiredCommitSigning = false, } = ctx.input; @@ -174,6 +177,7 @@ export function createGithubRepoPushAction(options: { requiredStatusCheckContexts, requireBranchesToBeUpToDate, requiredConversationResolution, + requireLastPushApproval, config, ctx.logger, gitCommitMessage, diff --git a/plugins/scaffolder-backend-module-github/src/actions/helpers.ts b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts index dd2f4a9f76..eec6f73f92 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/helpers.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts @@ -360,6 +360,7 @@ export async function initRepoPushAndProtect( requiredStatusCheckContexts: string[], requireBranchesToBeUpToDate: boolean, requiredConversationResolution: boolean, + requireLastPushApproval: boolean, config: Config, logger: any, gitCommitMessage?: string, @@ -408,6 +409,7 @@ export async function initRepoPushAndProtect( requiredStatusCheckContexts, requireBranchesToBeUpToDate, requiredConversationResolution, + requireLastPushApproval, enforceAdmins: protectEnforceAdmins, dismissStaleReviews: dismissStaleReviews, requiredCommitSigning: requiredCommitSigning, diff --git a/plugins/scaffolder-backend-module-github/src/actions/index.ts b/plugins/scaffolder-backend-module-github/src/actions/index.ts index 40a0da5455..6f37678099 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/index.ts @@ -21,12 +21,12 @@ 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'; +export { createGithubPagesEnableAction } from './githubPagesEnable'; export { getOctokitOptions } from './helpers'; diff --git a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts index 585526fe94..f6fc592b72 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts @@ -64,6 +64,11 @@ const requiredConversationResolution = { 'Requires all conversations on code to be resolved before a pull request can be merged into this branch', type: 'boolean', }; +const requireLastPushApproval = { + title: 'Require last push approval', + type: 'boolean', + description: `Whether the most recent push to a PR must be approved by someone other than the person who pushed it. The default value is 'false'`, +}; const repoVisibility = { title: 'Repository Visibility', type: 'string', @@ -326,6 +331,7 @@ export { dismissStaleReviews }; export { requiredStatusCheckContexts }; export { requireBranchesToBeUpToDate }; export { requiredConversationResolution }; +export { requireLastPushApproval }; export { hasProjects }; export { hasIssues }; export { hasWiki }; diff --git a/plugins/scaffolder-backend-module-github/src/module.ts b/plugins/scaffolder-backend-module-github/src/module.ts index 1817a68bfb..a5676ab8c9 100644 --- a/plugins/scaffolder-backend-module-github/src/module.ts +++ b/plugins/scaffolder-backend-module-github/src/module.ts @@ -29,6 +29,7 @@ import { createGithubWebhookAction, createPublishGithubAction, createPublishGithubPullRequestAction, + createGithubPagesEnableAction, } from './actions'; import { DefaultGithubCredentialsProvider, @@ -91,6 +92,10 @@ export const githubModule = createBackendModule({ githubCredentialsProvider, config, }), + createGithubPagesEnableAction({ + integrations, + githubCredentialsProvider, + }), ); }, }); diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index c64aa0ca32..41df7c2a60 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.4.5-next.0 + +### Patch Changes + +- fad1b90: Allow the `createGitlabProjectVariableAction` to use oauth tokens +- aab708e: Added test cases for gitlab:issue:edit examples +- ef742dc: Added test cases for gitlab:projectAccessToken:create example +- 1ba4c2f: Added test cases for gitlab:pipeline:trigger examples +- a6603e4: Add custom action for merge request: **auto** + + The **Auto** action selects the committed action between _create_ and _update_. + + The **Auto** action fetches files using the **/projects/repository/tree endpoint**. + After fetching, it checks if the file exists locally and in the repository. If it does, it chooses **update**; otherwise, it chooses **create**. + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.4.4 + +### Patch Changes + +- 0ac124b: Updated configuration instructions +- 2fb0eb8: Added support for passing `variables` to `gitlab:pipeline:trigger` +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.4.4-next.1 + +### Patch Changes + +- 2fb0eb8: Added support for passing `variables` to `gitlab:pipeline:trigger` +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.4.3-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/api-report.md b/plugins/scaffolder-backend-module-gitlab/api-report.md index 4f9239e9b2..ab89669ede 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -193,7 +193,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; + commitAction?: 'auto' | 'update' | 'delete' | 'create' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; @@ -211,6 +211,7 @@ export const createTriggerGitlabPipelineAction: (options: { projectId: number; tokenDescription: string; token?: string | undefined; + variables?: Record | undefined; }, { pipelineUrl: string; diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 7444bde77d..e4705e6206 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.4.3-next.0", + "version": "0.4.5-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts new file mode 100644 index 0000000000..7e50eac66c --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.test.ts @@ -0,0 +1,817 @@ +/* + * 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 { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { ConfigReader } from '@backstage/core-app-api'; +import { ScmIntegrations } from '@backstage/integration'; +import { editGitlabIssueAction } from './gitlabIssueEdit'; +import { examples } from './gitlabIssueEdit.examples'; +import yaml from 'yaml'; + +const mockGitlabClient = { + Issues: { + edit: jest.fn(), + }, +}; +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:issue:edit', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.useFakeTimers({ + now: new Date(1999, 6, 14, 12, 0, 0), + }); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'myIntegrationsToken', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = editGitlabIssueAction({ integrations }); + + it(`Should ${examples[0].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[0].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: input.title, + issueType: undefined, + addLabels: undefined, + removeLabels: undefined, + description: input.description, + assigneeIds: [], + confidential: false, + discussionLocked: false, + epicId: undefined, + labels: undefined, + updatedAt: new Date().toISOString(), + dueDate: undefined, + milestoneId: undefined, + weight: undefined, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`Should ${examples[1].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[1].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: input.title, + issueType: undefined, + addLabels: undefined, + removeLabels: undefined, + description: input.description, + assigneeIds: [18], + confidential: false, + discussionLocked: false, + epicId: undefined, + labels: undefined, + updatedAt: '2024-05-10T18:00:00.000Z', + dueDate: '2024-09-28', + milestoneId: undefined, + weight: undefined, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`Should ${examples[2].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[2].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + epicId: undefined, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: input.title, + issueType: undefined, + addLabels: undefined, + removeLabels: undefined, + description: input.description, + assigneeIds: [18, 15], + confidential: false, + discussionLocked: true, + epicId: undefined, + labels: 'phase1:label1,phase2:label2', + updatedAt: '2024-05-10T18:00:00.000Z', + dueDate: '2024-09-28', + milestoneId: undefined, + weight: undefined, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`Should ${examples[2].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[2].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + epicId: undefined, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: input.title, + issueType: undefined, + addLabels: undefined, + removeLabels: undefined, + description: input.description, + assigneeIds: [18, 15], + confidential: false, + discussionLocked: true, + epicId: undefined, + labels: 'phase1:label1,phase2:label2', + updatedAt: '2024-05-10T18:00:00.000Z', + dueDate: '2024-09-28', + milestoneId: undefined, + weight: undefined, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`Should ${examples[3].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[3].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: 'Computer banks to rule the world', + issueType: undefined, + addLabels: undefined, + removeLabels: undefined, + description: input.description, + assigneeIds: [], + confidential: false, + discussionLocked: false, + epicId: undefined, + labels: undefined, + updatedAt: new Date().toISOString(), + dueDate: undefined, + milestoneId: undefined, + weight: undefined, + stateEvent: 'close', + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`Should ${examples[4].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[4].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: 'Computer banks to rule the world', + issueType: undefined, + addLabels: undefined, + removeLabels: undefined, + description: input.description, + assigneeIds: [], + confidential: false, + discussionLocked: false, + epicId: undefined, + labels: undefined, + updatedAt: new Date().toISOString(), + dueDate: undefined, + milestoneId: undefined, + weight: undefined, + stateEvent: 'reopen', + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`Should ${examples[5].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[5].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: input.title, + issueType: undefined, + addLabels: undefined, + removeLabels: undefined, + description: input.description, + assigneeIds: [18, 20], + confidential: false, + discussionLocked: false, + epicId: undefined, + labels: undefined, + updatedAt: new Date().toISOString(), + dueDate: undefined, + milestoneId: input.milestoneId, + weight: undefined, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`Should ${examples[6].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[6].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: input.title, + issueType: undefined, + addLabels: undefined, + removeLabels: undefined, + description: input.description, + assigneeIds: [], + confidential: false, + discussionLocked: false, + epicId: undefined, + labels: input.labels, + updatedAt: new Date().toISOString(), + dueDate: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`Should ${examples[7].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[7].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: input.title, + issueType: undefined, + addLabels: undefined, + removeLabels: undefined, + description: input.description, + assigneeIds: [], + confidential: input.confidential, + discussionLocked: false, + epicId: undefined, + labels: input.labels, + updatedAt: new Date().toISOString(), + dueDate: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`Should ${examples[8].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[8].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: input.title, + issueType: undefined, + addLabels: undefined, + removeLabels: undefined, + description: input.description, + assigneeIds: [], + confidential: false, + discussionLocked: input.discussionLocked, + epicId: undefined, + labels: input.labels, + updatedAt: new Date().toISOString(), + dueDate: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`Should ${examples[9].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[9].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: input.title, + issueType: undefined, + addLabels: undefined, + removeLabels: input.removeLabels, + description: input.description, + assigneeIds: [], + confidential: false, + discussionLocked: false, + epicId: undefined, + labels: input.labels, + updatedAt: new Date().toISOString(), + dueDate: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + it(`Should ${examples[10].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[10].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: input.title, + issueType: undefined, + addLabels: undefined, + removeLabels: input.removeLabels, + description: input.description, + assigneeIds: [], + confidential: false, + discussionLocked: false, + epicId: undefined, + labels: input.labels, + updatedAt: new Date().toISOString(), + dueDate: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + it(`Should ${examples[11].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + issueIid: 42, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.edit.mockResolvedValue({ + id: 123, + iid: 42, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + let input; + try { + input = yaml.parse(examples[11].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.edit).toHaveBeenCalledWith(12, 42, { + title: input.title, + issueType: input.issueType, + addLabels: undefined, + removeLabels: input.removeLabels, + description: input.description, + assigneeIds: [], + confidential: false, + discussionLocked: false, + epicId: undefined, + labels: input.labels, + updatedAt: new Date().toISOString(), + dueDate: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + stateEvent: undefined, + }); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 123); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 42); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.ts index 861c0736f0..8686a66250 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueEdit.examples.ts @@ -50,7 +50,7 @@ export const examples: TemplateExample[] = [ title: 'Test Issue', assignees: [18], description: 'This is the edited description of the issue', - updatedAt: '2024-05-10 18:00:00.000', + updatedAt: '2024-05-10T18:00:00.000Z', dueDate: '2024-09-28', }, }, @@ -72,7 +72,7 @@ export const examples: TemplateExample[] = [ assignees: [18, 15], description: 'This is the description of the issue', confidential: false, - updatedAt: '2024-05-10 18:00:00.000', + updatedAt: '2024-05-10T18:00:00.000Z', dueDate: '2024-09-28', discussionLocked: true, epicId: 1, @@ -82,4 +82,177 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Edit a gitlab issue to change its state to close', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'EditIssues', + action: 'gitlab:issue:edit', + input: { + ...commonGitlabConfigExample, + projectId: 12, + stateEvent: 'close', + }, + }, + ], + }), + }, + { + description: 'Edit a gitlab issue to change its state to reopened', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'EditIssues', + action: 'gitlab:issue:edit', + input: { + ...commonGitlabConfigExample, + projectId: 12, + stateEvent: 'reopen', + }, + }, + ], + }), + }, + { + description: + 'Edit a gitlab issue to assign it to multiple users and set milestone', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'EditIssues', + action: 'gitlab:issue:edit', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Test issue with milestone', + assignees: [18, 20], + description: 'This issue has milestone set', + milestoneId: 5, + }, + }, + ], + }), + }, + { + description: 'Edit a gitlab issue to add weight and update labels', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'EditIssues', + action: 'gitlab:issue:edit', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Issue with weight and labels', + description: 'This issue has weight and new labels', + weight: 3, + labels: 'bug,urgent', + }, + }, + ], + }), + }, + { + description: 'Edit a gitlab issue to make it confidential', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'EditIssues', + action: 'gitlab:issue:edit', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Confidential Issue', + description: 'This issue is confidential', + confidential: true, + }, + }, + ], + }), + }, + { + description: 'Edit a gitlab issue to lock the discussion', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'EditIssues', + action: 'gitlab:issue:edit', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Locked Discussion Issue', + description: 'This discussion on this issue is locked', + discussionLocked: true, + }, + }, + ], + }), + }, + { + description: 'Edit a gitlab issue to remove labels and update milestone', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'EditIssues', + action: 'gitlab:issue:edit', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Issue with labels removed and milestone updated', + description: 'This issue has labels removed and milestone updated', + removeLabels: 'phase1:label1', + milestoneId: 6, + }, + }, + ], + }), + }, + { + description: 'Edit a gitlab issue to remove some labels and new ones', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'EditIssues', + action: 'gitlab:issue:edit', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Issue with labels updated', + description: 'This issue has labels removed and new ones added', + removeLabels: 'bug,urgent', + labels: 'enhancement:documentation', + }, + }, + ], + }), + }, + { + description: 'Edit a gitlab issue to change issue type and add labels', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'EditIssues', + action: 'gitlab:issue:edit', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Issue with type and labels', + description: 'This issue has been changes and new labels added', + labels: 'task,high-priority', + issueType: 'task', + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts index befc2c01c9..3eda50a924 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.test.ts @@ -61,6 +61,28 @@ const mockGitlabClient = { ]; }), }, + Repositories: { + tree: jest.fn( + async ( + repoID: string | number, + options: { ref: string; recursive: boolean; path: string | undefined }, + ) => { + if (repoID !== 'owner/repo') throw new Error('repo does not exist'); + if (options.recursive === false) throw new Error('malformed options'); + else { + return [ + { + id: 'a1e8f8d745cc87e3a9248358d9352bb7f9a0aeba', + name: 'auto.txt', + type: 'blob', + path: 'source/auto.txt', + mode: '040000', + }, + ]; + } + }, + ), + }, }; jest.mock('@gitbeaker/node', () => ({ @@ -407,7 +429,7 @@ describe('createGitLabMergeRequest', () => { }); describe('createGitLabMergeRequestWithoutCommitAction', () => { - it('default commitAction is create', async () => { + it('default commitAction is auto', async () => { const input = { repoUrl: 'gitlab.com?repo=repo&owner=owner', title: 'Create my new MR', @@ -417,7 +439,7 @@ describe('createGitLabMergeRequest', () => { }; mockDir.setContent({ [workspacePath]: { - source: { 'foo.txt': 'Hello there!' }, + source: { 'foo.txt': 'Hello there!', 'auto.txt': 'File exist' }, irrelevant: { 'bar.txt': 'Nothing to see here' }, }, }); @@ -429,6 +451,13 @@ describe('createGitLabMergeRequest', () => { 'new-mr', 'Create my new MR', [ + { + action: 'update', + filePath: 'source/auto.txt', + content: 'RmlsZSBleGlzdA==', + encoding: 'base64', + execute_filemode: false, + }, { action: 'create', filePath: 'source/foo.txt', @@ -512,6 +541,88 @@ describe('createGitLabMergeRequest', () => { ); }); + it('commitAction is auto when auto is passed in options', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + commitAction: 'auto', + }; + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!', 'auto.txt': 'File exist' }, + }, + }); + + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'Create my new MR', + [ + { + action: 'update', + filePath: 'source/auto.txt', + content: 'RmlsZSBleGlzdA==', + encoding: 'base64', + execute_filemode: false, + }, + { + action: 'create', + filePath: 'source/foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + }); + + it('commitAction is auto when auto is passed in options with targetPath', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + commitAction: 'auto', + targetPath: 'source', + }; + mockDir.setContent({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!', 'auto.txt': 'File exist' }, + irrevelant: {}, + }, + }); + + const ctx = createMockActionContext({ input, workspacePath }); + await instance.handler(ctx); + + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'Create my new MR', + [ + { + action: 'update', + filePath: 'source/auto.txt', + content: 'RmlsZSBleGlzdA==', + encoding: 'base64', + execute_filemode: false, + }, + { + action: 'create', + filePath: 'source/foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + }); + it('commitAction is delete when delete is passed in options', async () => { const input = { repoUrl: 'gitlab.com?repo=repo&owner=owner', diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index a549585426..733365c6df 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -17,6 +17,7 @@ import { createTemplateAction, parseRepoUrl, + SerializedFile, serializeDirectoryContents, } from '@backstage/plugin-scaffolder-node'; import { Types } from '@gitbeaker/core'; @@ -27,6 +28,21 @@ import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createGitlabApi } from './helpers'; import { examples } from './gitlabMergeRequest.examples'; +function getFileAction( + fileInfo: { file: SerializedFile; targetPath: string | undefined }, + remoteFiles: Types.RepositoryTreeSchema[], + defaultCommitAction: 'create' | 'delete' | 'update' | 'auto' | undefined, +): 'create' | 'delete' | 'update' { + if (!defaultCommitAction || defaultCommitAction === 'auto') { + const filePath = path.join(fileInfo.targetPath ?? '', fileInfo.file.path); + return remoteFiles && + remoteFiles.some(remoteFile => remoteFile.path === filePath) + ? 'update' + : 'create'; + } + return defaultCommitAction; +} + /** * Create a new action that creates a gitlab merge request. * @@ -46,7 +62,7 @@ export const createPublishGitlabMergeRequestAction = (options: { sourcePath?: string; targetPath?: string; token?: string; - commitAction?: 'create' | 'delete' | 'update'; + commitAction?: 'create' | 'delete' | 'update' | 'auto'; /** @deprecated projectID passed as query parameters in the repoUrl */ projectid?: string; removeSourceBranch?: boolean; @@ -109,9 +125,9 @@ export const createPublishGitlabMergeRequestAction = (options: { commitAction: { title: 'Commit action', type: 'string', - enum: ['create', 'update', 'delete'], + enum: ['create', 'update', 'delete', 'auto'], description: - 'The action to be used for git commit. Defaults to create.', + 'The action to be used for git commit. Defaults to auto. "auto" is custom action provide by backstage, (automatic assign create or update action) /!\\ Use more api calls /!\\ *', }, removeSourceBranch: { title: 'Delete source branch', @@ -199,16 +215,6 @@ export const createPublishGitlabMergeRequestAction = (options: { gitignore: true, }); - const actions: Types.CommitAction[] = fileContents.map(file => ({ - action: ctx.input.commitAction ?? 'create', - filePath: targetPath - ? path.posix.join(targetPath, file.path) - : file.path, - encoding: 'base64', - content: file.content.toString('base64'), - execute_filemode: file.executable, - })); - let targetBranch = targetBranchName; if (!targetBranch) { const projects = await api.Projects.show(repoID); @@ -217,6 +223,35 @@ export const createPublishGitlabMergeRequestAction = (options: { targetBranch = defaultBranch!; } + let remoteFiles: Types.RepositoryTreeSchema[] = []; + if (!ctx.input.commitAction || ctx.input.commitAction === 'auto') { + try { + remoteFiles = await api.Repositories.tree(repoID, { + ref: targetBranch, + recursive: true, + path: targetPath ?? undefined, + }); + } catch (e) { + ctx.logger.warn( + `Could not retrieve the list of files for ${repoID} (branch: ${targetBranch}) : ${e}`, + ); + } + } + + const actions: Types.CommitAction[] = fileContents.map(file => ({ + action: getFileAction( + { file, targetPath }, + remoteFiles, + ctx.input.commitAction, + ), + filePath: targetPath + ? path.posix.join(targetPath, file.path) + : file.path, + encoding: 'base64', + content: file.content.toString('base64'), + execute_filemode: file.executable, + })); + try { await api.Branches.create(repoID, branchName, String(targetBranch)); } catch (e) { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts new file mode 100644 index 0000000000..53cbfb0b87 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.test.ts @@ -0,0 +1,320 @@ +/* + * 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 { ConfigReader } from '@backstage/core-app-api'; +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createTriggerGitlabPipelineAction } from './gitlabPipelineTrigger'; +import { examples } from './gitlabPipelineTrigger.examples'; +import yaml from 'yaml'; + +const mockGitlabClient = { + PipelineTriggerTokens: { + create: jest.fn(), + trigger: jest.fn(), + remove: jest.fn(), + }, +}; +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:pipeline:trigger', () => { + beforeEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + jest.useFakeTimers({ + now: new Date(1988, 5, 3, 12, 0, 0), + }); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'glpat-abcdef', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = createTriggerGitlabPipelineAction({ integrations }); + + it(`should ${examples[0].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'Gitlab sample pipeline', + branch: 'main', + }, + workspacePath: 'repository', + }); + + mockGitlabClient.PipelineTriggerTokens.create.mockResolvedValue({ + id: 42, + description: 'Gitlab sample pipeline', + createdAt: new Date().toISOString(), + last_used: null, + token: 'abcdd-ghijkl', + updated_at: new Date().toISOString(), + owner: null, + }); + + mockGitlabClient.PipelineTriggerTokens.trigger.mockResolvedValue({ + id: 99, + web_url: 'https://gitlab.com/hangar18-/pipelines/99', + }); + + let input; + try { + input = yaml.parse(examples[0].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.PipelineTriggerTokens.create).toHaveBeenCalledWith( + input.projectId, + input.tokenDescription, + ); + + expect(mockGitlabClient.PipelineTriggerTokens.trigger).toHaveBeenCalledWith( + input.projectId, + 'main', + 'abcdd-ghijkl', + { variables: input.variables }, + ); + + expect(mockGitlabClient.PipelineTriggerTokens.remove).toHaveBeenCalledWith( + input.projectId, + 42, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'pipelineUrl', + 'https://gitlab.com/hangar18-/pipelines/99', + ); + }); + + it(`should ${examples[1].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'Gitlab sample pipeline', + branch: 'main', + }, + workspacePath: 'repository', + }); + + mockGitlabClient.PipelineTriggerTokens.create.mockResolvedValue({ + id: 42, + description: 'Gitlab sample pipeline', + createdAt: new Date().toISOString(), + last_used: null, + token: 'abcdd-ghijkl', + updated_at: new Date().toISOString(), + owner: null, + }); + + mockGitlabClient.PipelineTriggerTokens.trigger.mockResolvedValue({ + id: 99, + web_url: 'https://gitlab.com/hangar18-/pipelines/99', + }); + + let input; + try { + input = yaml.parse(examples[1].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.PipelineTriggerTokens.create).toHaveBeenCalledWith( + input.projectId, + input.tokenDescription, + ); + + expect(mockGitlabClient.PipelineTriggerTokens.trigger).toHaveBeenCalledWith( + input.projectId, + 'main', + 'abcdd-ghijkl', + { variables: input.variables }, + ); + + expect(mockGitlabClient.PipelineTriggerTokens.remove).toHaveBeenCalledWith( + input.projectId, + 42, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'pipelineUrl', + 'https://gitlab.com/hangar18-/pipelines/99', + ); + }); + it(`should ${examples[2].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'Gitlab sample pipeline', + branch: 'main', + }, + workspacePath: 'repository', + }); + + mockGitlabClient.PipelineTriggerTokens.create.mockResolvedValue({ + id: 42, + description: 'Gitlab sample pipeline', + createdAt: new Date().toISOString(), + last_used: null, + token: 'abcdd-ghijkl', + updated_at: new Date().toISOString(), + owner: null, + }); + + mockGitlabClient.PipelineTriggerTokens.trigger.mockResolvedValue({ + id: 99, + web_url: 'https://gitlab.com/hangar18-/pipelines/99', + }); + + let input; + try { + input = yaml.parse(examples[2].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.PipelineTriggerTokens.create).toHaveBeenCalledWith( + input.projectId, + input.tokenDescription, + ); + + expect(mockGitlabClient.PipelineTriggerTokens.trigger).toHaveBeenCalledWith( + input.projectId, + 'main', + 'abcdd-ghijkl', + { variables: input.variables }, + ); + + expect(mockGitlabClient.PipelineTriggerTokens.remove).toHaveBeenCalledWith( + input.projectId, + 42, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'pipelineUrl', + 'https://gitlab.com/hangar18-/pipelines/99', + ); + }); + it(`should ${examples[3].description}`, async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'Gitlab sample pipeline', + branch: 'main', + }, + workspacePath: 'repository', + }); + + mockGitlabClient.PipelineTriggerTokens.create.mockResolvedValue({ + id: 42, + description: 'Gitlab sample pipeline', + createdAt: new Date().toISOString(), + last_used: null, + token: 'abcdd-ghijkl', + updated_at: new Date().toISOString(), + owner: null, + }); + + mockGitlabClient.PipelineTriggerTokens.trigger.mockResolvedValue({ + id: 99, + web_url: 'https://gitlab.com/hangar18-/pipelines/99', + }); + + let input; + try { + input = yaml.parse(examples[3].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.PipelineTriggerTokens.create).toHaveBeenCalledWith( + input.projectId, + input.tokenDescription, + ); + + expect(mockGitlabClient.PipelineTriggerTokens.trigger).toHaveBeenCalledWith( + input.projectId, + 'main', + 'abcdd-ghijkl', + { variables: input.variables }, + ); + + expect(mockGitlabClient.PipelineTriggerTokens.remove).toHaveBeenCalledWith( + input.projectId, + 42, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'pipelineUrl', + 'https://gitlab.com/hangar18-/pipelines/99', + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.ts index 4948db908b..f213fcd83f 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.examples.ts @@ -33,6 +33,70 @@ export const examples: TemplateExample[] = [ 'This is the text that will appear in the pipeline token', token: 'glpt-xxxxxxxxxxxx', branch: 'main', + variables: { var_one: 'one', var_two: 'two' }, + }, + }, + ], + }), + }, + { + description: 'Trigger a GitLab Project Pipeline with No Variables', + example: yaml.stringify({ + steps: [ + { + id: 'triggerPipeline', + name: 'Trigger Project Pipeline', + action: 'gitlab:pipeline:trigger', + input: { + ...commonGitlabConfigExample, + projectId: 12, + tokenDescription: + 'This is the text that will appear in the pipeline token', + token: 'glpt-xxxxxxxxxxxx', + branch: 'main', + variables: {}, + }, + }, + ], + }), + }, + { + description: 'Trigger a GitLab Project Pipeline with Single Variables', + example: yaml.stringify({ + steps: [ + { + id: 'triggerPipeline', + name: 'Trigger Project Pipeline', + action: 'gitlab:pipeline:trigger', + input: { + ...commonGitlabConfigExample, + projectId: 12, + tokenDescription: + 'This is the text that will appear in the pipeline token', + token: 'glpt-xxxxxxxxxxxx', + branch: 'main', + variables: { var_one: 'one' }, + }, + }, + ], + }), + }, + { + description: 'Trigger a GitLab Project Pipeline with Multiple Variables', + example: yaml.stringify({ + steps: [ + { + id: 'triggerPipeline', + name: 'Trigger Project Pipeline', + action: 'gitlab:pipeline:trigger', + input: { + ...commonGitlabConfigExample, + projectId: 12, + tokenDescription: + 'This is the text that will appear in the pipeline token', + token: 'glpt-xxxxxxxxxxxx', + branch: 'main', + variables: { var_one: 'one', var_two: 'two', var_three: 'three' }, }, }, ], diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts index a7412032a4..3803198ec8 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.test.ts @@ -101,6 +101,7 @@ describe('gitlab:pipeline:trigger', () => { 123, 'main', 'glptt-abcdef', + { variables: undefined }, ); expect(mockGitlabClient.PipelineTriggerTokens.remove).toHaveBeenCalledWith( @@ -189,6 +190,7 @@ describe('gitlab:pipeline:trigger', () => { 123, 'main', 'glptt-abcdef', + { variables: undefined }, ); expect(mockGitlabClient.PipelineTriggerTokens.remove).toHaveBeenCalledWith( @@ -196,6 +198,7 @@ describe('gitlab:pipeline:trigger', () => { 42, ); }); + it('should clean up pipeline token on failure', async () => { const mockContext = createMockActionContext({ input: { @@ -232,4 +235,30 @@ describe('gitlab:pipeline:trigger', () => { 42, ); }); + + it('should succeed trigger and pass variables', async () => { + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 123, + tokenDescription: 'My cool pipeline token', + branch: 'main', + variables: { var_one: 'val1', var_two: 'val2' }, + }, + workspacePath: 'seen2much', + }); + + await expect( + action.handler({ + ...mockContext, + }), + ).rejects.toThrow('Failed to trigger pipeline'); + + expect(mockGitlabClient.PipelineTriggerTokens.trigger).toHaveBeenCalledWith( + 123, + 'main', + 'glptt-abcdef', + { variables: { var_one: 'val1', var_two: 'val2' } }, + ); + }); }); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts index 6937fe44aa..b9f629edfb 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabPipelineTrigger.ts @@ -30,6 +30,12 @@ const pipelineInputProperties = z.object({ projectId: z.number().describe('Project Id'), tokenDescription: z.string().describe('Pipeline token description'), branch: z.string().describe('Project branch'), + variables: z + .record(z.string(), z.string()) + .optional() + .describe( + 'A object/record of key-valued strings containing the pipeline variables.', + ), }); const pipelineOutputProperties = z.object({ @@ -57,7 +63,7 @@ export const createTriggerGitlabPipelineAction = (options: { async handler(ctx) { let pipelineTokenResponse: PipelineTriggerTokenSchema | null = null; - const { repoUrl, projectId, tokenDescription, token, branch } = + const { repoUrl, projectId, tokenDescription, token, branch, variables } = commonGitlabConfig.merge(pipelineInputProperties).parse(ctx.input); const { host } = parseRepoUrl(repoUrl, integrations); @@ -84,6 +90,7 @@ export const createTriggerGitlabPipelineAction = (options: { projectId, branch, pipelineTokenResponse.token, + { variables }, )) as ExpandedPipelineSchema; if (!pipelineTriggerResponse.id) { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.test.ts index 811926cc82..bdf14ce1c8 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.test.ts @@ -51,6 +51,10 @@ describe('gitlab:projectAccessToken:create examples', () => { host: 'hosted.gitlab.com', apiBaseUrl: 'https://api.hosted.gitlab.com', }, + { + host: 'gitlab.example.com', + apiBaseUrl: 'https://api.gitlab.example.com', + }, ], }, }); @@ -192,4 +196,256 @@ describe('gitlab:projectAccessToken:create examples', () => { expect(mockContext.output).toHaveBeenCalledWith('access_token', 'TOKEN'); }); + + it(`should ${examples[5].description}`, async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'personal-access-token', + username: 'gitlab-user', + }); + + const input = yaml.parse(examples[5].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '456', + 'tokenname', + ['read_repository'], + { + accessLevel: 30, + expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'personal-access-token', + ); + }); + + it(`should ${examples[6].description}`, async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'personal-access-token', + username: 'gitlab-user', + }); + + const input = yaml.parse(examples[6].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '456', + 'full-access-token', + ['read_repository'], + { + accessLevel: 40, + expiresAt: '2024-12-31', + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'personal-access-token', + ); + }); + + it(`should ${examples[7].description}`, async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'personal-access-token', + username: 'gitlab-user', + }); + + const input = yaml.parse(examples[7].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '101112', + 'tokenname', + ['read_repository'], + { + accessLevel: 40, + expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'personal-access-token', + ); + }); + + it(`should ${examples[8].description}`, async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'personal-access-token', + username: 'gitlab-user', + }); + + const input = yaml.parse(examples[8].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '101112', + 'tokenname', + ['read_repository', 'read_api'], + { + accessLevel: 40, + expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'personal-access-token', + ); + }); + + it(`should ${examples[9].description}`, async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'personal-access-token', + username: 'gitlab-user', + }); + + const input = yaml.parse(examples[9].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '101112', + 'tokenname', + ['read_repository'], + { + accessLevel: 10, + expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'personal-access-token', + ); + }); + + it(`should ${examples[10].description}`, async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'personal-access-token', + username: 'gitlab-user', + }); + + const input = yaml.parse(examples[10].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '101112', + 'tokenname', + ['read_repository'], + { + accessLevel: 40, + expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'personal-access-token', + ); + }); + + it(`should ${examples[11].description}`, async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'personal-access-token', + username: 'gitlab-user', + }); + + const input = yaml.parse(examples[11].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '101112', + 'tokenname', + ['read_repository'], + { + accessLevel: 50, + expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'personal-access-token', + ); + }); + + it(`should ${examples[12].description}`, async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'personal-access-token', + username: 'gitlab-user', + }); + + const input = yaml.parse(examples[12].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '101112', + 'no-expiry-token', + ['read_repository'], + { + accessLevel: 40, + expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'personal-access-token', + ); + }); + + it(`should ${examples[13].description}`, async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'personal-access-token', + username: 'gitlab-user', + }); + + const input = yaml.parse(examples[13].example).steps[0].input; + await action.handler({ + ...mockContext, + input, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '101112', + 'tokenname', + ['read_repository'], + { + accessLevel: 40, + expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'personal-access-token', + ); + }); }); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.ts index 8d4f335a46..89f8ae6269 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.examples.ts @@ -102,4 +102,162 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Create a GitLab project access token with an access level', + example: yaml.stringify({ + steps: [ + { + id: 'createAccessToken', + action: 'gitlab:projectAccessToken:create', + name: 'Create GitLab Project Access Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '456', + accessLevel: 30, + }, + }, + ], + }), + }, + { + description: 'Create a GitLab project access token with multiple options', + example: yaml.stringify({ + steps: [ + { + id: 'createAccessToken', + action: 'gitlab:projectAccessToken:create', + name: 'Create GitLab Project Access Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '456', + accessLevel: 40, + name: 'full-access-token', + expiresAt: '2024-12-31', + }, + }, + ], + }), + }, + { + description: + 'Create a GitLab project access token with a token for authorization', + example: yaml.stringify({ + steps: [ + { + id: 'createAccessToken', + action: 'gitlab:projectAccessToken:create', + name: 'Create GitLab Project Access Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '101112', + token: 'personal-access-token', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab project access token with read-only scopes', + example: yaml.stringify({ + steps: [ + { + id: 'createAccessToken', + action: 'gitlab:projectAccessToken:create', + name: 'Create GitLab Project Access Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '101112', + scopes: ['read_repository', 'read_api'], + }, + }, + ], + }), + }, + { + description: 'Create a GitLab project access token with guest access level', + example: yaml.stringify({ + steps: [ + { + id: 'createAccessToken', + action: 'gitlab:projectAccessToken:create', + name: 'Create GitLab Project Access Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '101112', + accessLevel: 10, + }, + }, + ], + }), + }, + { + description: + 'Create a GitLab project access token with maintainer access level', + example: yaml.stringify({ + steps: [ + { + id: 'createAccessToken', + action: 'gitlab:projectAccessToken:create', + name: 'Create GitLab Project Access Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '101112', + accessLevel: 40, + }, + }, + ], + }), + }, + { + description: 'Create a GitLab project access token with owner access level', + example: yaml.stringify({ + steps: [ + { + id: 'createAccessToken', + action: 'gitlab:projectAccessToken:create', + name: 'Create GitLab Project Access Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '101112', + accessLevel: 50, + }, + }, + ], + }), + }, + { + description: + 'Create a GitLab project access token with a specified name and no expiration date', + example: yaml.stringify({ + steps: [ + { + id: 'createAccessToken', + action: 'gitlab:projectAccessToken:create', + name: 'Create GitLab Project Access Token', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '101112', + name: 'no-expiry-token', + }, + }, + ], + }), + }, + { + description: + 'Create a GitLab project access token for a specific gitlab instance', + example: yaml.stringify({ + steps: [ + { + id: 'createAccessToken', + action: 'gitlab:projectAccessToken:create', + name: 'Create GitLab Project Access Token', + input: { + repoUrl: 'gitlab.example.com?repo=repo&owner=owner', + projectId: '101112', + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.test.ts new file mode 100644 index 0000000000..34ce8878f2 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectAccessTokenCreate.test.ts @@ -0,0 +1,221 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { createGitlabProjectAccessTokenAction } from './gitlabProjectAccessTokenCreate'; // Adjust the import based on your project structure + +import { DateTime } from 'luxon'; + +jest.mock('node-fetch'); + +const mockGitlabClient = { + ProjectAccessTokens: { + create: jest.fn(), + }, +}; + +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:projectAccessToken:create examples', () => { + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'gitlab-token', + apiBaseUrl: 'https://api.gitlab.com', + }, + { + host: 'hosted.gitlab.com', + apiBaseUrl: 'https://api.hosted.gitlab.com', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createGitlabProjectAccessTokenAction({ integrations }); + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + }, + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should create a GitLab project access token with minimal options.', async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'gitlab-token', + username: 'gitlab-user', + }); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '987', + }, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '987', + 'tokenname', + ['read_repository'], + { + accessLevel: 40, + expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'gitlab-token', + ); + }); + + it('should create a GitLab project access token with custom scopes.', async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'gitlab-token', + username: 'gitlab-user', + }); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '987', + scopes: ['read_registry', 'write_repository'], + }, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '987', + 'tokenname', + ['read_registry', 'write_repository'], + { + accessLevel: 40, + expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'gitlab-token', + ); + }); + + it('should create a GitLab project access token with a specified name.', async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'gitlab-token', + username: 'gitlab-user', + }); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '2110', + name: 'token', + }, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '2110', + 'token', + ['read_repository'], + { + accessLevel: 40, + expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'gitlab-token', + ); + }); + + it('should create a GitLab project access token with a numeric project ID.', async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'gitlab-token', + username: 'gitlab-user', + }); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 23, + }, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + 23, + 'tokenname', + ['read_repository'], + { + accessLevel: 40, + expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'gitlab-token', + ); + }); + + it('should create a GitLab project access token with a specified expired Date.', async () => { + mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({ + token: 'gitlab-token', + username: 'gitlab-user', + }); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + expiresAt: '1999-07-14', + }, + }); + + expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith( + '123', + 'tokenname', + ['read_repository'], + { + accessLevel: 40, + expiresAt: '1999-07-14', + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith( + 'access_token', + 'gitlab-token', + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts index 1eeb01ca91..99b26682d8 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.examples.test.ts @@ -26,7 +26,7 @@ const mockGitlabClient = { create: jest.fn(), }, }; -jest.mock('@gitbeaker/node', () => ({ +jest.mock('@gitbeaker/rest', () => ({ Gitlab: class { constructor() { return mockGitlabClient; @@ -41,7 +41,7 @@ describe('gitlab:projectVariableAction: create examples', () => { { host: 'gitlab.com', token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', + apiBaseUrl: 'https://api.gitlab.com/api/v4', }, { host: 'hosted.gitlab.com', @@ -79,17 +79,18 @@ describe('gitlab:projectVariableAction: create examples', () => { expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( '123', + 'MY_VARIABLE', + 'my_value', { - key: 'MY_VARIABLE', - value: 'my_value', - variable_type: 'env_var', - environment_scope: '*', + variableType: 'env_var', // Correctly using variableType + environmentScope: '*', masked: false, protected: false, raw: false, }, ); }); + it(`Should ${examples[1].description}`, async () => { mockGitlabClient.ProjectVariables.create.mockResolvedValue({ token: 'TOKEN', @@ -102,14 +103,14 @@ describe('gitlab:projectVariableAction: create examples', () => { expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( '123', + 'MY_VARIABLE', + 'my-file-content', { - key: 'MY_VARIABLE', - value: 'my-file-content', + variableType: 'file', // Correctly using variableType protected: false, masked: false, raw: false, - environment_scope: '*', - variable_type: 'file', + environmentScope: '*', }, ); }); @@ -126,13 +127,13 @@ describe('gitlab:projectVariableAction: create examples', () => { expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( '456', + 'MY_VARIABLE', + 'my_value', { - key: 'MY_VARIABLE', - value: 'my_value', masked: false, raw: false, - environment_scope: '*', - variable_type: 'env_var', + environmentScope: '*', + variableType: 'env_var', // Correctly using variableType protected: true, }, ); @@ -150,13 +151,13 @@ describe('gitlab:projectVariableAction: create examples', () => { expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( '789', + 'DB_PASSWORD', + 'password123', { - key: 'DB_PASSWORD', - value: 'password123', protected: false, raw: false, - environment_scope: '*', - variable_type: 'env_var', + environmentScope: '*', + variableType: 'env_var', // Correctly using variableType masked: true, }, ); @@ -174,12 +175,12 @@ describe('gitlab:projectVariableAction: create examples', () => { expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( '123', + 'MY_VARIABLE', + 'my_value', { - key: 'MY_VARIABLE', - value: 'my_value', protected: false, - environment_scope: '*', - variable_type: 'env_var', + environmentScope: '*', + variableType: 'env_var', // Correctly using variableType masked: false, raw: true, }, @@ -198,14 +199,14 @@ describe('gitlab:projectVariableAction: create examples', () => { expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( '123', + 'MY_VARIABLE', + 'my_value', { - key: 'MY_VARIABLE', - value: 'my_value', protected: false, - variable_type: 'env_var', + variableType: 'env_var', // Correctly using variableType masked: false, raw: false, - environment_scope: 'production', + environmentScope: 'production', }, ); }); @@ -222,14 +223,14 @@ describe('gitlab:projectVariableAction: create examples', () => { expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( '123', + 'MY_VARIABLE', + 'my_value', { - key: 'MY_VARIABLE', - value: 'my_value', protected: false, - variable_type: 'env_var', + variableType: 'env_var', // Correctly using variableType masked: false, raw: false, - environment_scope: '*', + environmentScope: '*', }, ); }); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts index a07ec747fa..3a9eeec0a1 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabProjectVariableCreate.ts @@ -16,10 +16,10 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { Gitlab } from '@gitbeaker/node'; +import { VariableType } from '@gitbeaker/rest'; import { z } from 'zod'; import commonGitlabConfig from '../commonGitlabConfig'; -import { getToken } from '../util'; +import { getClient, parseRepoUrl } from '../util'; import { examples } from './gitlabProjectVariableCreate.examples'; /** @@ -72,6 +72,7 @@ export const createGitlabProjectVariableAction = (options: { }, async handler(ctx) { const { + repoUrl, projectId, key, value, @@ -80,21 +81,19 @@ export const createGitlabProjectVariableAction = (options: { masked = false, raw = false, environmentScope = '*', + token, } = ctx.input; - const { token, integrationConfig } = getToken(ctx.input, integrations); - const api = new Gitlab({ - host: integrationConfig.config.baseUrl, - token: token, - }); - await api.ProjectVariables.create(projectId, { - key: key, - value: value, - variable_type: variableType, + const { host } = parseRepoUrl(repoUrl, integrations); + + const api = getClient({ host, integrations, token }); + + await api.ProjectVariables.create(projectId, key, value, { + variableType: variableType as VariableType, protected: variableProtected, - masked: masked, - raw: raw, - environment_scope: environmentScope, + masked, + raw, + environmentScope, }); }, }); diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index 948770e600..f51473dd0c 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.0.6-next.0 + +### Patch Changes + +- 6fc03c7: Add examples for notification:send scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.3 + +## 0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/plugin-notifications-common@0.0.4 + - @backstage/plugin-notifications-node@0.2.3-next.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.0.4-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index bb8b7807e8..15fe158651 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.0.4-next.0", + "version": "0.0.6-next.0", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module", @@ -38,7 +38,8 @@ "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-notifications-node": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", - "octokit": "^3.0.0" + "octokit": "^3.0.0", + "yaml": "^2.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.test.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.test.ts new file mode 100644 index 0000000000..84473335e0 --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.test.ts @@ -0,0 +1,272 @@ +/* + * 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 { createSendNotificationAction } from './sendNotification'; +import { NotificationService } from '@backstage/plugin-notifications-node'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { examples } from './sendNotification.examples'; +import yaml from 'yaml'; + +describe('notification:send', () => { + const notificationService: jest.Mocked = { + send: jest.fn(), + }; + + let action: TemplateAction; + + beforeEach(() => { + jest.resetAllMocks(); + action = createSendNotificationAction({ + notifications: notificationService, + }); + }); + + const mockContext = createMockActionContext({ + input: { + recipients: 'broadcast', + title: 'Test notification', + }, + }); + + it(`should ${examples[0].description}`, async () => { + const input = yaml.parse(examples[0].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: input, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { type: 'broadcast' }, + payload: { + title: 'Test notification', + }, + }); + }); + + it(`should ${examples[1].description}`, async () => { + const input = yaml.parse(examples[1].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: input, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { + entityRef: ['entity:component:1'], + type: 'entity', + }, + payload: { + description: 'A security update has been applied. Please review.', + title: 'Security Update', + link: 'https://example.com/security/update', + severity: 'high', + scope: 'internal', + }, + }); + }); + + it(`should ${examples[2].description}`, async () => { + const input = yaml.parse(examples[2].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: input, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { + type: 'entity', + entityRef: ['entity:component:1'], + }, + payload: { + description: 'Here is your weekly update.', + link: undefined, + severity: 'low', + scope: undefined, + title: 'Weekly Update', + }, + }); + }); + + it(`should ${examples[3].description}`, async () => { + const input = yaml.parse(examples[3].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: input, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { type: 'broadcast' }, + payload: { + description: 'Version 2.0.0 is now available. Upgrade now!', + link: undefined, + severity: 'normal', + scope: 'public', + title: 'New Release Available', + }, + }); + }); + + it(`should ${examples[4].description}`, async () => { + const input = yaml.parse(examples[4].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: input, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { + type: 'entity', + entityRef: ['entity:component:1'], + }, + payload: { + description: + 'A critical bug has been identified. Immediate action required.', + link: undefined, + scope: 'internal', + severity: 'critical', + title: 'Critical Bug Found', + }, + }); + }); + + it(`should ${examples[5].description}`, async () => { + const input = yaml.parse(examples[5].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: input, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { + type: 'broadcast', + }, + payload: { + description: 'Server maintenance will occur tonight at 11 PM.', + link: undefined, + scope: 'internal', + severity: 'normal', + title: 'Server Maintenance Scheduled', + }, + }); + }); + + it(`should ${examples[6].description}`, async () => { + const input = yaml.parse(examples[6].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: input, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { type: 'broadcast' }, + payload: { + description: 'New features have been deployed. Explore them now!', + link: undefined, + scope: undefined, + severity: 'normal', + title: 'New Feature Deployment', + }, + }); + }); + + it(`should ${examples[7].description}`, async () => { + const input = yaml.parse(examples[7].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: input, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { type: 'broadcast' }, + payload: { + description: undefined, + link: undefined, + scope: 'internal', + severity: 'low', + title: 'Holiday Office Closure', + }, + }); + }); + + it(`should ${examples[8].description}`, async () => { + const input = yaml.parse(examples[8].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: input, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { type: 'broadcast' }, + payload: { + description: + "Don't forget, the weekly meeting is scheduled for tomorrow.", + link: undefined, + scope: 'internal', + severity: undefined, + title: 'Reminder: Weekly Meeting Tomorrow', + }, + }); + }); + + it(`should ${examples[9].description}`, async () => { + const input = yaml.parse(examples[9].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: input, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { type: 'broadcast' }, + payload: { + title: 'Important Announcement', + description: + 'Please read the latest announcement regarding the upcoming changes.', + link: undefined, + scope: undefined, + severity: 'high', + }, + }); + }); + + it(`should ${examples[10].description}`, async () => { + const input = yaml.parse(examples[10].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: input, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { type: 'broadcast' }, + payload: { + title: 'Broadcast Notification', + description: 'This is a broadcast notification', + link: undefined, + scope: undefined, + severity: 'low', + }, + }); + }); + + it(`should ${examples[11].description}`, async () => { + const input = yaml.parse(examples[11].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { + input: input, + }); + await action.handler(ctx); + expect(notificationService.send).toHaveBeenCalledWith({ + recipients: { + type: 'entity', + entityRef: ['entity:service1'], + }, + payload: { + title: 'Entity Notification', + description: 'This is a notification for entity service1', + link: undefined, + scope: undefined, + severity: 'normal', + }, + }); + }); +}); diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.ts new file mode 100644 index 0000000000..05bbd368f8 --- /dev/null +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.ts @@ -0,0 +1,255 @@ +/* + * 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: 'Sends a notification with minimal options', + example: yaml.stringify({ + steps: [ + { + id: 'sendNotification', + action: 'notification:send', + name: 'Send Notification', + input: { + recipients: 'broadcast', + title: 'Test notification', + }, + }, + ], + }), + }, + { + description: 'Sends a notification with entity recipients and link', + example: yaml.stringify({ + steps: [ + { + id: 'sendNotification', + action: 'notification:send', + name: 'Send Notification', + input: { + recipients: 'entity', + entityRefs: ['entity:component:1'], + title: 'Security Update', + info: 'A security update has been applied. Please review.', + link: 'https://example.com/security/update', + severity: 'high', + scope: 'internal', + }, + }, + ], + }), + }, + { + description: + 'Sends a notification with entity recipients and optional flag', + example: yaml.stringify({ + steps: [ + { + id: 'sendNotification', + action: 'notification:send', + name: 'Send Notification', + input: { + recipients: 'entity', + entityRefs: ['entity:component:1'], + title: 'Weekly Update', + info: 'Here is your weekly update.', + severity: 'low', + optional: true, + }, + }, + ], + }), + }, + { + description: + 'Sends a notification with broadcast recipients and custom scope', + example: yaml.stringify({ + steps: [ + { + id: 'sendNotification', + action: 'notification:send', + name: 'Send Notification', + input: { + recipients: 'broadcast', + title: 'New Release Available', + info: 'Version 2.0.0 is now available. Upgrade now!', + severity: 'normal', + scope: 'public', + }, + }, + ], + }), + }, + { + description: + 'Sends a notification with entity recipients and custom severity', + example: yaml.stringify({ + steps: [ + { + id: 'sendNotification', + action: 'notification:send', + name: 'Send Notification', + input: { + recipients: 'entity', + entityRefs: ['entity:component:1'], + title: 'Critical Bug Found', + info: 'A critical bug has been identified. Immediate action required.', + severity: 'critical', + scope: 'internal', + }, + }, + ], + }), + }, + { + description: 'Sends a notification with broadcast recipients and no link', + example: yaml.stringify({ + steps: [ + { + id: 'sendNotification', + action: 'notification:send', + name: 'Send Notification', + input: { + recipients: 'broadcast', + title: 'Server Maintenance Scheduled', + info: 'Server maintenance will occur tonight at 11 PM.', + severity: 'normal', + scope: 'internal', + }, + }, + ], + }), + }, + { + description: + 'Sends a notification with broadcast recipients and optional flag', + example: yaml.stringify({ + steps: [ + { + id: 'sendNotification', + action: 'notification:send', + name: 'Send Notification', + input: { + recipients: 'broadcast', + title: 'New Feature Deployment', + info: 'New features have been deployed. Explore them now!', + severity: 'normal', + optional: true, + }, + }, + ], + }), + }, + { + description: + 'Sends a notification with broadcast recipients and no description', + example: yaml.stringify({ + steps: [ + { + id: 'sendNotification', + action: 'notification:send', + name: 'Send Notification', + input: { + recipients: 'broadcast', + title: 'Holiday Office Closure', + severity: 'low', + scope: 'internal', + }, + }, + ], + }), + }, + { + description: + 'Sends a notification with broadcast recipients and no severity', + example: yaml.stringify({ + steps: [ + { + id: 'sendNotification', + action: 'notification:send', + name: 'Send Notification', + input: { + recipients: 'broadcast', + title: 'Reminder: Weekly Meeting Tomorrow', + info: "Don't forget, the weekly meeting is scheduled for tomorrow.", + scope: 'internal', + }, + }, + ], + }), + }, + { + description: 'Sends a notification with broadcast recipients and no scope', + example: yaml.stringify({ + steps: [ + { + id: 'sendNotification', + action: 'notification:send', + name: 'Send Notification', + input: { + recipients: 'broadcast', + title: 'Important Announcement', + info: 'Please read the latest announcement regarding the upcoming changes.', + severity: 'high', + }, + }, + ], + }), + }, + { + description: 'Sends a notification with optional parameters', + example: yaml.stringify({ + steps: [ + { + id: 'sendNotification', + action: 'notification:send', + name: 'Send Notification', + input: { + recipients: 'broadcast', + title: 'Broadcast Notification', + info: 'This is a broadcast notification', + severity: 'low', + optional: true, + }, + }, + ], + }), + }, + { + description: + 'Sends a notification with entity recipients and optional set to false', + example: yaml.stringify({ + steps: [ + { + id: 'sendNotification', + action: 'notification:send', + name: 'Send Notification to Entity', + input: { + recipients: 'entity', + entityRefs: ['entity:service1'], + title: 'Entity Notification', + info: 'This is a notification for entity service1', + severity: 'normal', + optional: false, + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts index f50e178174..9646de0001 100644 --- a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts @@ -22,6 +22,7 @@ import { NotificationSeverity, } from '@backstage/plugin-notifications-common'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; +import { examples } from './sendNotification.examples'; /** * @public @@ -42,6 +43,7 @@ export function createSendNotificationAction(options: { }>({ id: 'notification:send', description: 'Sends a notification using NotificationService', + examples, schema: { input: { type: 'object', diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index afaebde648..f9ce2e1e6b 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.40-next.0 + +### Patch Changes + +- 449def7: Add examples for fetch:rails scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.4.39 + +### Patch Changes + +- 0ac124b: Updated configuration instructions +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.4.38-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index a736106df1..a2e2a215dd 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.4.38-next.0", + "version": "0.4.40-next.0", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module", @@ -51,7 +51,8 @@ "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", "command-exists": "^1.2.9", - "fs-extra": "^11.0.0" + "fs-extra": "^11.0.0", + "yaml": "^2.0.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.test.ts new file mode 100644 index 0000000000..37e678ab6a --- /dev/null +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.test.ts @@ -0,0 +1,341 @@ +/* + * 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 mockRailsTemplater = { run: jest.fn() }; +jest.mock('@backstage/plugin-scaffolder-node', () => ({ + ...jest.requireActual('@backstage/plugin-scaffolder-node'), + fetchContents: jest.fn(), +})); +jest.mock('./railsNewRunner', () => { + return { + RailsNewRunner: jest.fn().mockImplementation(() => { + return mockRailsTemplater; + }), + }; +}); + +import { ContainerRunner, UrlReader } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { resolve as resolvePath } from 'path'; +import { createFetchRailsAction } from './index'; +import { fetchContents } from '@backstage/plugin-scaffolder-node'; +import { createMockDirectory } from '@backstage/backend-test-utils'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { examples } from './index.examples'; +import yaml from 'yaml'; + +describe('fetch:rails', () => { + const mockDir = createMockDirectory(); + const integrations = ScmIntegrations.fromConfig( + new ConfigReader({ + integrations: { + azure: [ + { host: 'dev.azure.com', token: 'tokenlols' }, + { host: 'myazurehostnotoken.com' }, + ], + }, + }), + ); + + const mockContext = createMockActionContext({ + input: { + url: 'https://rubyonrails.org/generator', + targetPath: 'something', + values: { + help: 'me', + }, + }, + templateInfo: { + baseUrl: 'somebase', + entityRef: 'template:default/myTemplate', + }, + workspacePath: mockDir.path, + }); + + const mockReader: UrlReader = { + readUrl: jest.fn(), + readTree: jest.fn(), + search: jest.fn(), + }; + const containerRunner: ContainerRunner = { + runContainer: jest.fn(), + }; + + const action = createFetchRailsAction({ + integrations, + reader: mockReader, + containerRunner, + allowedImageNames: ['foo/rails-custom-image'], + }); + + beforeEach(() => { + mockDir.setContent({ + result: '{}', + }); + jest.clearAllMocks(); + }); + + it(`should ${examples[0].description}`, async () => { + let input; + try { + input = yaml.parse(examples[0].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: input.url, + outputPath: resolvePath(mockContext.workspacePath), + }); + }); + + it(`should ${examples[1].description}`, async () => { + let input; + try { + input = yaml.parse(examples[1].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: input.url, + outputPath: resolvePath(mockContext.workspacePath), + }); + }); + + it(`should ${examples[2].description}`, async () => { + let input; + try { + input = yaml.parse(examples[2].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: input.url, + outputPath: resolvePath(mockContext.workspacePath), + }); + }); + + it(`should ${examples[3].description}`, async () => { + let input; + try { + input = yaml.parse(examples[3].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: input.url, + outputPath: resolvePath(mockContext.workspacePath), + }); + }); + + it(`should ${examples[4].description}`, async () => { + let input; + try { + input = yaml.parse(examples[4].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: input.url, + outputPath: resolvePath(mockContext.workspacePath), + }); + }); + + it(`should ${examples[5].description}`, async () => { + let input; + try { + input = yaml.parse(examples[5].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: input.url, + outputPath: resolvePath(mockContext.workspacePath), + }); + }); + + it(`should ${examples[6].description}`, async () => { + let input; + try { + input = yaml.parse(examples[6].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: input.url, + outputPath: resolvePath(mockContext.workspacePath), + }); + }); + + it(`should ${examples[7].description}`, async () => { + let input; + try { + input = yaml.parse(examples[7].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: input.url, + outputPath: resolvePath(mockContext.workspacePath), + }); + }); + + it(`should ${examples[8].description}`, async () => { + let input; + try { + input = yaml.parse(examples[8].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: input.url, + outputPath: resolvePath(mockContext.workspacePath), + }); + }); + + it(`should ${examples[9].description}`, async () => { + let input; + try { + input = yaml.parse(examples[9].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(fetchContents).toHaveBeenCalledWith({ + reader: mockReader, + integrations, + baseUrl: mockContext.templateInfo?.baseUrl, + fetchUrl: input.url, + outputPath: resolvePath(mockContext.workspacePath), + }); + }); +}); diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.ts new file mode 100644 index 0000000000..4c77eb3c1b --- /dev/null +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.ts @@ -0,0 +1,370 @@ +/* + * 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: 'Fetch and template a minimal Rails app', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:rails', + name: 'Fetch and Template Rails App', + input: { + url: 'https://github.com/backstage/backstage', + values: { + railsArguments: { + minimal: true, + skipBundle: true, + skipWebpackInstall: true, + skipTest: true, + skipActionCable: true, + skipActionMailer: true, + skipActionMailbox: true, + skipActiveStorage: true, + skipActionText: true, + skipActiveRecord: true, + force: true, + api: true, + webpacker: 'react', + database: 'postgresql', + railsVersion: 'dev', + }, + }, + }, + }, + ], + }), + }, + { + description: 'Fetch and template a Rails app with custom options', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:rails', + name: 'Fetch and Template Rails App', + input: { + url: 'https://github.com/backstage/backstage', + targetPath: 'custom-path', + values: { + railsArguments: { + minimal: false, + skipBundle: false, + skipWebpackInstall: false, + skipTest: false, + skipActionCable: false, + skipActionMailer: false, + skipActionMailbox: false, + skipActiveStorage: false, + skipActionText: false, + skipActiveRecord: false, + force: false, + api: false, + webpacker: 'vue', + database: 'mysql', + railsVersion: 'edge', + }, + }, + }, + }, + ], + }), + }, + { + description: 'Fetch and template a Rails app with Docker image', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:rails', + name: 'Fetch and Template Rails App', + input: { + url: 'https://github.com/backstage/backstage', + values: { + railsArguments: { + minimal: true, + skipBundle: true, + skipWebpackInstall: true, + skipTest: true, + skipActionCable: true, + skipActionMailer: true, + skipActionMailbox: true, + skipActiveStorage: true, + skipActionText: true, + skipActiveRecord: true, + force: true, + api: true, + webpacker: 'react', + database: 'postgresql', + railsVersion: 'dev', + }, + imageName: 'custom/rails-image', + }, + }, + }, + ], + }), + }, + { + description: + 'Fetch and template a Rails app with different database and action mailer settings', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:rails', + name: 'Fetch and Template Rails App', + input: { + url: 'https://github.com/backstage/backstage', + values: { + railsArguments: { + minimal: false, + skipBundle: false, + skipWebpackInstall: false, + skipTest: false, + skipActionCable: false, + skipActionMailer: false, + skipActionMailbox: true, + skipActiveStorage: false, + skipActionText: false, + skipActiveRecord: false, + force: true, + api: true, + webpacker: 'angular', + database: 'sqlite3', + railsVersion: 'master', + }, + }, + }, + }, + ], + }), + }, + { + description: + 'Fetch and template a Rails app with full options including action mailer and active storage', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:rails', + name: 'Fetch and Template Rails App', + input: { + url: 'https://github.com/backstage/backstage', + values: { + railsArguments: { + minimal: false, + skipBundle: false, + skipWebpackInstall: false, + skipTest: false, + skipActionCable: false, + skipActionMailer: false, + skipActionMailbox: false, + skipActiveStorage: false, + skipActionText: false, + skipActiveRecord: false, + force: false, + api: false, + webpacker: 'stimulus', + database: 'postgresql', + railsVersion: 'fromImage', + }, + }, + }, + }, + ], + }), + }, + { + description: + 'Fetch and template a Rails app with API-only stack and custom template path', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:rails', + name: 'Fetch and Template Rails App', + input: { + url: 'https://github.com/backstage/backstage', + values: { + railsArguments: { + minimal: false, + skipBundle: true, + skipWebpackInstall: true, + skipTest: true, + skipActionCable: true, + skipActionMailer: true, + skipActionMailbox: true, + skipActiveStorage: true, + skipActionText: true, + skipActiveRecord: true, + force: true, + api: true, + webpacker: 'elm', + database: 'mysql', + railsVersion: 'dev', + template: 'path/to/custom/template', + }, + }, + }, + }, + ], + }), + }, + { + description: 'Fetch and template a Rails app with custom Webpack setup', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:rails', + name: 'Fetch and Template Rails App', + input: { + url: 'https://github.com/backstage/backstage', + values: { + railsArguments: { + minimal: false, + skipBundle: false, + skipWebpackInstall: false, + skipTest: false, + skipActionCable: false, + skipActionMailer: false, + skipActionMailbox: false, + skipActiveStorage: false, + skipActionText: false, + skipActiveRecord: false, + force: false, + api: false, + webpacker: 'angular', + database: 'postgresql', + railsVersion: 'dev', + }, + }, + }, + }, + ], + }), + }, + { + description: + 'Fetch and template a Rails app with specific Rails version and Action Mailer enabled', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:rails', + name: 'Fetch and Template Rails App', + input: { + url: 'https://github.com/backstage/backstage', + values: { + railsArguments: { + minimal: false, + skipBundle: false, + skipWebpackInstall: false, + skipTest: false, + skipActionCable: false, + skipActionMailer: false, + skipActionMailbox: false, + skipActiveStorage: false, + skipActionText: false, + skipActiveRecord: false, + force: true, + api: true, + webpacker: 'react', + database: 'sqlite3', + railsVersion: 'master', + }, + }, + }, + }, + ], + }), + }, + { + description: + 'Fetch and template a Rails app with specific database and Active Storage enabled', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:rails', + name: 'Fetch and Template Rails App', + input: { + url: 'https://github.com/backstage/backstage', + values: { + railsArguments: { + minimal: false, + skipBundle: false, + skipWebpackInstall: false, + skipTest: false, + skipActionCable: false, + skipActionMailer: false, + skipActionMailbox: false, + skipActiveStorage: false, + skipActionText: false, + skipActiveRecord: false, + force: true, + api: true, + webpacker: 'vue', + database: 'postgresql', + railsVersion: 'fromImage', + }, + }, + }, + }, + ], + }), + }, + { + description: + 'Fetch and template a Rails app with full options including database and skip options', + example: yaml.stringify({ + steps: [ + { + id: 'fetchTemplate', + action: 'fetch:rails', + name: 'Fetch and Template Rails App', + input: { + url: 'https://github.com/backstage/backstage', + values: { + railsArguments: { + minimal: false, + skipBundle: true, + skipWebpackInstall: true, + skipTest: true, + skipActionCable: true, + skipActionMailer: true, + skipActionMailbox: true, + skipActiveStorage: true, + skipActionText: true, + skipActiveRecord: true, + force: false, + api: false, + webpacker: 'stimulus', + database: 'sqlite3', + railsVersion: 'dev', + }, + }, + }, + }, + ], + }), + }, +]; 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 394d0fb02c..94a2a3539b 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 @@ -27,6 +27,7 @@ import { import { resolve as resolvePath } from 'path'; import { RailsNewRunner } from './railsNewRunner'; import { PassThrough } from 'stream'; +import { examples } from './index.examples'; /** * Creates the `fetch:rails` Scaffolder action. @@ -56,6 +57,7 @@ export function createFetchRailsAction(options: { id: 'fetch:rails', description: 'Downloads a template from the given `url` into the workspace, and runs a rails new generator on it.', + examples, schema: { input: { type: 'object', diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index fa7712e500..0dd4fb6c5b 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.1.30 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.1.29-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 7943de9b5c..3eb11dc3e9 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.1.29-next.0", + "version": "0.1.31-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts index 158b7db4ae..48c437d2a4 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { ConfigReader } from '@backstage/config'; import { InputError } from '@backstage/errors'; @@ -27,7 +27,7 @@ import { createSentryCreateProjectAction } from './createProject'; describe('sentry:project:create action', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); const createScaffolderConfig = (configData: JsonObject = {}) => ({ config: new ConfigReader({ diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index c3c2322b9a..3c9fe7db6b 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.3.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + - @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.0 + +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/plugin-scaffolder-node-test-utils@0.1.9 + - @backstage/types@1.1.1 + +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node-test-utils@0.1.9-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 70133616ef..29f4d7c1aa 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.3.5-next.0", + "version": "0.3.7-next.0", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 5737dc0165..8496dbf17b 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,113 @@ # @backstage/plugin-scaffolder-backend +## 1.23.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/backend-tasks@0.5.28-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.1-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.0 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.0 + - @backstage/plugin-scaffolder-common@1.5.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 1.23.0 + +### Minor Changes + +- b5deed0: Add support for `autocomplete` extension point to provide additional `autocomplete` handlers +- 0b52438: Serialization of the scaffolder workspace into GCP bucket + +### Patch Changes + +- b9451dd: Updated `catalog:write` scaffolder action to show correct file path location in log message +- ff1bb4c: Added a documentation how to use checkpoints +- da90cce: Updated dependency `esbuild` to `^0.21.0`. +- 62d1fe3: Fix user entity not being fetched for scaffolder dry runner +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/backend-tasks@0.5.27 + - @backstage/plugin-scaffolder-backend-module-github@0.4.0 + - @backstage/plugin-permission-common@0.8.0 + - @backstage/plugin-permission-node@0.8.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.4 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.12 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.14 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.12 + - @backstage/plugin-bitbucket-cloud-common@0.2.21 + - @backstage/plugin-auth-node@0.4.17 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20 + - @backstage/plugin-catalog-node@1.12.4 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.12 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.14 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.12 + - @backstage/plugin-scaffolder-common@1.5.4 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.23.0-next.2 + +### Patch Changes + +- ff1bb4c: Added a documentation how to use checkpoints + +## 1.23.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.4.0-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.4-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/backend-tasks@0.5.27-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.17-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.21-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.20-next.1 + - @backstage/plugin-catalog-node@1.12.4-next.1 + - @backstage/plugin-permission-common@0.7.14 + - @backstage/plugin-permission-node@0.7.33-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.14-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.12-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.12-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.12-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.14-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.12-next.1 + - @backstage/plugin-scaffolder-common@1.5.3 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 1.23.0-next.0 ### Minor Changes diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index a3beee2b53..907a30b541 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -312,7 +312,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; + commitAction?: 'auto' | 'update' | 'delete' | 'create' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index c9c19d9983..af67447fed 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "1.23.0-next.0", + "version": "1.23.1-next.0", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin", diff --git a/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile b/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile index ecaefee183..0c8b305de5 100644 --- a/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile +++ b/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.19 +FROM alpine:3.20 RUN apk add --update \ git \ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts index 9feaa14c07..a957adb71a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts @@ -49,10 +49,10 @@ export function createCatalogWriteAction() { examples, supportsDryRun: true, async handler(ctx) { - ctx.logger.info(`Writing catalog-info.yaml`); const { filePath, entity } = ctx.input; const entityRef = ctx.templateInfo?.entityRef; const path = filePath ?? 'catalog-info.yaml'; + ctx.logger.info(`Writing ${path}`); await fs.writeFile( resolveSafeChildPath(ctx.workspacePath, path), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/deprecated.ts b/plugins/scaffolder-backend/src/scaffolder/actions/deprecated.ts index fd5fdd49e8..5d037b44b6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/deprecated.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/deprecated.ts @@ -22,56 +22,66 @@ import * as bitbucketServer from '@backstage/plugin-scaffolder-backend-module-bi import * as gerrit from '@backstage/plugin-scaffolder-backend-module-gerrit'; /** - * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-github instead + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @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 + * @public + * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-github` instead */ export const createPublishGithubPullRequestAction = github.createPublishGithubPullRequestAction; @@ -83,46 +93,54 @@ export const createPublishBitbucketAction = bitbucket.createPublishBitbucketAction; /** - * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-bitbucket-cloud instead + * @public + * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` instead */ export const createPublishBitbucketCloudAction = bitbucketCloud.createPublishBitbucketCloudAction; /** - * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-bitbucket-server instead + * @public + * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-server` instead */ export const createPublishBitbucketServerAction = bitbucketServer.createPublishBitbucketServerAction; /** - * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-bitbucket-server instead + * @public + * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-bitbucket-server` instead */ export const createPublishBitbucketServerPullRequestAction = bitbucketServer.createPublishBitbucketServerPullRequestAction; /** - * @public @deprecated use import from \@backstage/plugin-scaffolder-backend-module-azure instead + * @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 + * @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 + * @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 + * @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 + * @public + * @deprecated use import from `@backstage/plugin-scaffolder-backend-module-gitlab` instead */ export const createPublishGitlabMergeRequestAction = gitlab.createPublishGitlabMergeRequestAction; diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 12f3f9e235..914faf70f3 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-common +## 1.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + ## 1.5.3 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index 3e56510daf..d86f0d67c0 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-common", - "version": "1.5.3", + "version": "1.5.4", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", "backstage": { "role": "common-library", diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index 1136cce04b..9eb166062e 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/backend-test-utils@0.4.5-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.0 + +## 0.1.9 + +### Patch Changes + +- 54429b5: Filename and imports correction for mockActionContext.ts +- Updated dependencies + - @backstage/backend-test-utils@0.4.4 + - @backstage/backend-common@0.23.3 + - @backstage/plugin-scaffolder-node@0.4.8 + - @backstage/types@1.1.1 + +## 0.1.9-next.1 + +### Patch Changes + +- 54429b5: Filename and imports correction for mockActionContext.ts +- Updated dependencies + - @backstage/backend-test-utils@0.4.4-next.1 + - @backstage/backend-common@0.23.3-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.8-next.1 + ## 0.1.8-next.0 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 87f895efc3..ef31d541f4 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.1.8-next.0", + "version": "0.1.10-next.0", "backstage": { "role": "node-library", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-node-test-utils/src/actions/index.ts b/plugins/scaffolder-node-test-utils/src/actions/index.ts index 161bae8521..aee195cd34 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/index.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { createMockActionContext } from './mockActionConext'; +export { createMockActionContext } from './mockActionContext'; diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionConext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts similarity index 100% rename from plugins/scaffolder-node-test-utils/src/actions/mockActionConext.ts rename to plugins/scaffolder-node-test-utils/src/actions/mockActionContext.ts diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index f0deb319ff..db361e0120 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-scaffolder-node +## 0.4.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.4 + +## 0.4.8 + +### Patch Changes + +- 661b354: Fixed a bug where the `RepoUrlPicker` would still require the `owner` field for `azure` +- b5deed0: Add support for `autocomplete` extension point to provide additional `autocomplete` handlers +- 0b52438: Serialization of the scaffolder workspace into GCP bucket +- Updated dependencies + - @backstage/backend-plugin-api@0.7.0 + - @backstage/backend-common@0.23.3 + - @backstage/integration@1.13.0 + - @backstage/plugin-scaffolder-common@1.5.4 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.3-next.1 + - @backstage/backend-plugin-api@0.6.22-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.13.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.3 + ## 0.4.7-next.0 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 2568809647..2cb7198631 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.4.7-next.0", + "version": "0.4.9-next.0", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index baa34dbc5b..f23a0e2425 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,85 @@ # @backstage/plugin-scaffolder-react +## 1.11.0-next.0 + +### Minor Changes + +- 8839381: Add scaffolder option to display object items in separate rows on review page + +### Patch Changes + +- d0e95a7: Add ability to customise form fields in the UI by exposing `uiSchema` and `formContext` in `FormProps` +- 4670f06: support `ajv-errors` for scaffolder validation to allow for customizing the error messages +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-scaffolder-common@1.5.4 + +## 1.10.0 + +### Minor Changes + +- 354e68c: Improve validation error display text in scaffolder +- b5deed0: Add support for `bitbucketCloud` autocomplete in `RepoUrlPicker` + +### Patch Changes + +- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`. + Updated dependency `@rjsf/core` to `5.18.5`. + Updated dependency `@rjsf/material-ui` to `5.18.5`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.5`. +- 4d7e11f: disables rendering of output box if no output is returned +- Updated dependencies + - @backstage/core-components@0.14.9 + - @backstage/plugin-catalog-react@1.12.2 + - @backstage/plugin-permission-react@0.4.24 + - @backstage/plugin-scaffolder-common@1.5.4 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## 1.10.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.9-next.1 + - @backstage/plugin-catalog-react@1.12.2-next.2 + +## 1.10.0-next.1 + +### Minor Changes + +- 354e68c: Improve validation error display text in scaffolder + +### Patch Changes + +- cc81579: Updated dependency `@rjsf/utils` to `5.18.5`. + Updated dependency `@rjsf/core` to `5.18.5`. + Updated dependency `@rjsf/material-ui` to `5.18.5`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.5`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.2-next.1 + - @backstage/core-components@0.14.9-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-permission-react@0.4.23 + - @backstage/plugin-scaffolder-common@1.5.3 + ## 1.10.0-next.0 ### Minor Changes diff --git a/plugins/scaffolder-react/api-report.md b/plugins/scaffolder-react/api-report.md index a14490b80c..a19a656d17 100644 --- a/plugins/scaffolder-react/api-report.md +++ b/plugins/scaffolder-react/api-report.md @@ -128,7 +128,7 @@ export interface FieldExtensionUiSchema // @public export type FormProps = Pick< FormProps_2, - 'transformErrors' | 'noHtml5Validate' + 'transformErrors' | 'noHtml5Validate' | 'uiSchema' | 'formContext' >; // @public diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 6e37ff3e71..7c16c11c1a 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.10.0-next.0", + "version": "1.11.0-next.0", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library", @@ -72,12 +72,13 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.61", "@react-hookz/web": "^24.0.0", - "@rjsf/core": "5.18.4", - "@rjsf/material-ui": "5.18.4", - "@rjsf/utils": "5.18.4", - "@rjsf/validator-ajv8": "5.18.4", + "@rjsf/core": "5.18.5", + "@rjsf/material-ui": "5.18.5", + "@rjsf/utils": "5.18.5", + "@rjsf/validator-ajv8": "5.18.5", "@types/json-schema": "^7.0.9", "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "ajv-errors": "^3.0.0", "classnames": "^2.2.6", "flatted": "3.3.1", "humanize-duration": "^3.25.1", diff --git a/plugins/scaffolder-react/src/components/types.ts b/plugins/scaffolder-react/src/components/types.ts index 597998bac4..d5d317015d 100644 --- a/plugins/scaffolder-react/src/components/types.ts +++ b/plugins/scaffolder-react/src/components/types.ts @@ -32,7 +32,7 @@ export type TemplateGroupFilter = { */ export type FormProps = Pick< SchemaFormProps, - 'transformErrors' | 'noHtml5Validate' + 'transformErrors' | 'noHtml5Validate' | 'uiSchema' | 'formContext' >; /** diff --git a/plugins/scaffolder-react/src/next/components/Form/Form.tsx b/plugins/scaffolder-react/src/next/components/Form/Form.tsx index 8717c8b55a..05c8a4934f 100644 --- a/plugins/scaffolder-react/src/next/components/Form/Form.tsx +++ b/plugins/scaffolder-react/src/next/components/Form/Form.tsx @@ -44,6 +44,8 @@ export const Form = (props: PropsWithChildren) => { uiSchema={wrapperProps.uiSchema ?? {}} formData={wrapperProps.formData} rawErrors={wrapperProps.rawErrors ?? []} + disabled={wrapperProps.disabled ?? false} + readonly={wrapperProps.readonly ?? false} /> ); }, 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 e9277b2230..194c5f3e77 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.test.tsx @@ -69,6 +69,9 @@ describe('ReviewState', () => { const formState = { name: 'John Doe', test: 'bob', + nest: { + foo: 'bar', + }, }; const schemas: ParsedTemplateSchema[] = [ @@ -85,6 +88,20 @@ describe('ReviewState', () => { }, }, }, + nest: { + type: 'object', + properties: { + foo: { + type: 'string', + 'ui:widget': 'password', + 'ui:backstage': { + review: { + show: false, + }, + }, + }, + }, + }, }, }, schema: {}, @@ -99,12 +116,16 @@ describe('ReviewState', () => { ); expect(getAllByRole('row').length).toEqual(1); expect(queryByRole('row', { name: 'Name ******' })).not.toBeInTheDocument(); + expect(queryByRole('row', { name: 'Foo ******' })).not.toBeInTheDocument(); }); it('should allow for masking an option with a set text', () => { const formState = { name: 'John Doe', test: 'bob', + nest: { + foo: 'bar', + }, }; const schemas: ParsedTemplateSchema[] = [ @@ -121,6 +142,20 @@ describe('ReviewState', () => { }, }, }, + nest: { + type: 'object', + properties: { + foo: { + type: 'string', + 'ui:widget': 'password', + 'ui:backstage': { + review: { + mask: 'lols', + }, + }, + }, + }, + }, }, }, schema: {}, @@ -135,11 +170,15 @@ describe('ReviewState', () => { ); expect(getByRole('row', { name: 'Name lols' })).toBeInTheDocument(); + expect(getByRole('row', { name: 'Foo lols' })).toBeInTheDocument(); }); it('should display enum label from enumNames', async () => { const formState = { name: 'type2', + nest: { + foo: 'type2', + }, }; const schemas: ParsedTemplateSchema[] = [ @@ -153,6 +192,17 @@ describe('ReviewState', () => { enum: ['type1', 'type2', 'type3'], enumNames: ['Label-type1', 'Label-type2', 'Label-type3'], }, + nest: { + type: 'object', + properties: { + foo: { + type: 'string', + default: 'type1', + enum: ['type1', 'type2', 'type3'], + enumNames: ['Label-type1', 'Label-type2', 'Label-type3'], + }, + }, + }, }, }, schema: {}, @@ -169,6 +219,7 @@ describe('ReviewState', () => { expect( queryByRole('row', { name: 'Name Label-type2' }), ).toBeInTheDocument(); + expect(queryByRole('row', { name: 'Foo Label-type2' })).toBeInTheDocument(); }); it('should display enum value if no corresponding enumNames', async () => { @@ -202,4 +253,159 @@ describe('ReviewState', () => { expect(queryByRole('row', { name: 'Name type4' })).toBeInTheDocument(); }); + + it('should display object in separate rows', async () => { + const formState = { + name: { + foo: 'type3', + bar: 'type4', + }, + }; + + const schemas: ParsedTemplateSchema[] = [ + { + mergedSchema: { + type: 'object', + properties: { + name: { + type: 'object', + properties: { + foo: { + type: 'string', + default: 'type1', + }, + bar: { + type: 'string', + default: 'type2', + }, + }, + }, + }, + }, + schema: {}, + title: 'test', + uiSchema: {}, + }, + ]; + + const { queryByRole } = render( + , + ); + + expect(queryByRole('row', { name: 'Foo type3' })).toBeInTheDocument(); + expect(queryByRole('row', { name: 'Bar type4' })).toBeInTheDocument(); + }); + + it('should display nested objects in separate rows', async () => { + const formState = { + name: { + foo: 'type3', + bar: 'type4', + example: { + test: 'type6', + }, + }, + }; + + const schemas: ParsedTemplateSchema[] = [ + { + mergedSchema: { + type: 'object', + properties: { + name: { + type: 'object', + properties: { + foo: { + type: 'string', + default: 'type1', + }, + bar: { + type: 'string', + default: 'type2', + }, + example: { + type: 'object', + properties: { + test: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + schema: {}, + title: 'test', + uiSchema: {}, + }, + ]; + + const { queryByRole } = render( + , + ); + + expect(queryByRole('row', { name: 'Foo type3' })).toBeInTheDocument(); + expect(queryByRole('row', { name: 'Bar type4' })).toBeInTheDocument(); + expect(queryByRole('row', { name: 'Test type6' })).toBeInTheDocument(); + }); + + it('should display partially nested objects', async () => { + const formState = { + name: { + foo: 'type3', + bar: 'type4', + example: { + test: 'type6', + }, + }, + }; + + const schemas: ParsedTemplateSchema[] = [ + { + mergedSchema: { + type: 'object', + properties: { + name: { + type: 'object', + properties: { + foo: { + type: 'string', + default: 'type1', + }, + bar: { + type: 'string', + default: 'type2', + }, + example: { + type: 'object', + 'ui:backstage': { + review: { + explode: false, + }, + }, + properties: { + test: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + schema: {}, + title: 'test', + uiSchema: {}, + }, + ]; + + const { queryByRole } = render( + , + ); + + expect(queryByRole('row', { name: 'Foo type3' })).toBeInTheDocument(); + expect(queryByRole('row', { name: 'Bar type4' })).toBeInTheDocument(); + expect(queryByRole('row', { name: 'Test type6' })).not.toBeInTheDocument(); + }); }); diff --git a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx index e3c7bc303e..d87584afff 100644 --- a/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx +++ b/plugins/scaffolder-react/src/next/components/ReviewState/ReviewState.tsx @@ -15,9 +15,10 @@ */ import React from 'react'; import { StructuredMetadataTable } from '@backstage/core-components'; -import { JsonObject } from '@backstage/types'; +import { JsonObject, JsonValue } from '@backstage/types'; import { Draft07 as JSONSchema } from 'json-schema-library'; import { ParsedTemplateSchema } from '../../hooks/useTemplateSchema'; +import { isJsonObject, getLastKey } from './util'; /** * The props for the {@link ReviewState} component. @@ -28,6 +29,55 @@ export type ReviewStateProps = { formState: JsonObject; }; +function processSchema( + key: string, + value: JsonValue | undefined, + schema: ParsedTemplateSchema, + formState: JsonObject, +): [string, JsonValue | undefined][] { + const parsedSchema = new JSONSchema(schema.mergedSchema); + const definitionInSchema = parsedSchema.getSchema({ + pointer: `#/${key}`, + data: formState, + }); + + if (definitionInSchema) { + const backstageReviewOptions = definitionInSchema['ui:backstage']?.review; + if (backstageReviewOptions) { + if (backstageReviewOptions.mask) { + return [[getLastKey(key), backstageReviewOptions.mask]]; + } + if (backstageReviewOptions.show === false) { + return []; + } + } + + if (definitionInSchema['ui:widget'] === 'password') { + return [[getLastKey(key), '******']]; + } + + if (definitionInSchema.enum && definitionInSchema.enumNames) { + return [ + [ + getLastKey(key), + definitionInSchema.enumNames[ + definitionInSchema.enum.indexOf(value) + ] || value, + ], + ]; + } + + if (backstageReviewOptions?.explode !== false && isJsonObject(value)) { + // Recurse nested objects + return Object.entries(value).flatMap(([nestedKey, nestedValue]) => + processSchema(`${key}/${nestedKey}`, nestedValue, schema, formState), + ); + } + } + + return [[getLastKey(key), value]]; +} + /** * The component used by the {@link Stepper} to render the review step. * @alpha @@ -35,42 +85,11 @@ export type ReviewStateProps = { export const ReviewState = (props: ReviewStateProps) => { const reviewData = Object.fromEntries( Object.entries(props.formState) - .map(([key, value]) => { + .flatMap(([key, value]) => { for (const step of props.schemas) { - const parsedSchema = new JSONSchema(step.mergedSchema); - const definitionInSchema = parsedSchema.getSchema({ - pointer: `#/${key}`, - data: props.formState, - }); - - if (definitionInSchema) { - const backstageReviewOptions = - definitionInSchema['ui:backstage']?.review; - - if (backstageReviewOptions) { - if (backstageReviewOptions.mask) { - return [key, backstageReviewOptions.mask]; - } - if (backstageReviewOptions.show === false) { - return []; - } - } - - if (definitionInSchema['ui:widget'] === 'password') { - return [key, '******']; - } - - if (definitionInSchema.enum && definitionInSchema.enumNames) { - return [ - key, - definitionInSchema.enumNames[ - definitionInSchema.enum.indexOf(value) - ] || value, - ]; - } - } + return processSchema(key, value, step, props.formState); } - return [key, value]; + return [[key, value]]; }) .filter(prop => prop.length > 0), ); diff --git a/plugins/scaffolder-react/src/next/components/ReviewState/util.test.ts b/plugins/scaffolder-react/src/next/components/ReviewState/util.test.ts new file mode 100644 index 0000000000..9b88ff157e --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/ReviewState/util.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { isJsonObject, getLastKey } from './util'; + +describe('isJsonObject', () => { + it('should return true for non-null objects', () => { + expect(isJsonObject({})).toBe(true); + expect(isJsonObject({ key: 'value' })).toBe(true); + }); + + it('should return false for arrays', () => { + expect(isJsonObject([])).toBe(false); + expect(isJsonObject([1, 2, 3])).toBe(false); + }); + + it('should return false for non-objects', () => { + expect(isJsonObject('string')).toBe(false); + expect(isJsonObject(123)).toBe(false); + expect(isJsonObject(true)).toBe(false); + expect(isJsonObject(undefined)).toBe(false); + }); +}); + +describe('getLastKey', () => { + it('should return the last part of a simple key', () => { + expect(getLastKey('simple')).toBe('simple'); + }); + + it('should return the last part of a nested key', () => { + expect(getLastKey('parent/child')).toBe('child'); + }); + + it('should return the last part of a deeply nested key', () => { + expect(getLastKey('grandparent/parent/child')).toBe('child'); + }); + + it('should handle keys with trailing slash', () => { + expect(getLastKey('parent/child/')).toBe(''); + }); + + it('should handle empty string', () => { + expect(getLastKey('')).toBe(''); + }); + + it('should handle keys with multiple consecutive slashes', () => { + expect(getLastKey('parent//child')).toBe('child'); + }); + + it('should handle keys with only slashes', () => { + expect(getLastKey('////')).toBe(''); + }); + + it('should handle keys with spaces', () => { + expect(getLastKey('parent/child with spaces')).toBe('child with spaces'); + }); + + it('should handle keys with special characters', () => { + expect(getLastKey('parent/child@!#$%^&*()')).toBe('child@!#$%^&*()'); + }); +}); diff --git a/plugins/scaffolder-react/src/next/components/ReviewState/util.ts b/plugins/scaffolder-react/src/next/components/ReviewState/util.ts new file mode 100644 index 0000000000..49c3b30e7a --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/ReviewState/util.ts @@ -0,0 +1,27 @@ +/* + * 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 { JsonObject, JsonValue } from '@backstage/types'; + +export function isJsonObject(value?: JsonValue): value is JsonObject { + return typeof value === 'object' && !Array.isArray(value); +} + +// Helper function to get the last part of the key +export function getLastKey(key: string): string { + const parts = key.split('/'); + return parts[parts.length - 1]; +} 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 index c5ddf6854f..d632340031 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.test.tsx @@ -19,25 +19,88 @@ 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; + describe('should render the error messages', () => { + it('no properties', async () => { + 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( - , - ); + const { getByText } = await renderInTestApp( + , + ); - for (const error of errorList.errors) { - expect(getByText(error.stack)).toBeInTheDocument(); - } + for (const error of errorList.errors) { + expect(getByText(error.stack)).toBeInTheDocument(); + } + }); + + it('properties no title', async () => { + const errorList = { + errors: [ + { + property: '.foo', + stack: 'Test error 1', + message: 'Test error 1', + }, + { + property: '.anExampleTitle', + stack: 'Test error 2', + message: 'Test error 2', + }, + ], + errorSchema: {}, + schema: {}, + } as Partial as ErrorListProps; + + const { getByText } = await renderInTestApp( + , + ); + + expect(getByText("'Foo' Test error 1")).toBeInTheDocument(); + expect(getByText("'An Example Title' Test error 2")).toBeInTheDocument(); + }); + + it('properties with title', async () => { + const errorList = { + errors: [ + { + property: '.foo', + stack: 'Test error 1', + message: 'Test error 1', + }, + { + property: '.bar', + stack: 'Test error 2', + message: 'Test error 2', + }, + ], + errorSchema: {}, + schema: { + properties: { + foo: { + title: 'Hello', + }, + bar: { + title: 'Example Title', + }, + }, + }, + } as Partial as ErrorListProps; + + const { getByText } = await renderInTestApp( + , + ); + + expect(getByText("'Hello' Test error 1")).toBeInTheDocument(); + expect(getByText("'Example Title' Test error 2")).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 index a5310a5806..6b664fc235 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/ErrorListTemplate/errorListTemplate.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React from 'react'; -import { ErrorListProps } from '@rjsf/utils'; +import { ErrorListProps, RJSFValidationError } from '@rjsf/utils'; import List from '@material-ui/core/List'; import ListItem from '@material-ui/core/ListItem'; import ListItemIcon from '@material-ui/core/ListItemIcon'; @@ -22,6 +22,7 @@ import ListItemText from '@material-ui/core/ListItemText'; import Paper from '@material-ui/core/Paper'; import { Theme, createStyles, makeStyles } from '@material-ui/core/styles'; import ErrorIcon from '@material-ui/icons/Error'; +import startCase from 'lodash/startCase'; const useStyles = makeStyles((_theme: Theme) => createStyles({ @@ -39,9 +40,28 @@ const useStyles = makeStyles((_theme: Theme) => * * @public */ -export const ErrorListTemplate = ({ errors }: ErrorListProps) => { +export const ErrorListTemplate = ({ errors, schema }: ErrorListProps) => { const classes = useStyles(); + function formatErrorMessage(error: RJSFValidationError) { + if (error.property && error.message) { + const propertyName = error.property.startsWith('.') + ? error.property.substring(1) + : error.property; + if (schema.properties && propertyName in schema.properties) { + const property = schema.properties[propertyName]; + + if (typeof property === 'object' && 'title' in property) { + return `'${property.title}' ${error.message}`; + } + } + // fall back to property name + return `'${startCase(propertyName)}' ${error.message}`; + } + // fall back if property does not exist + return error.stack; + } + return ( @@ -52,7 +72,7 @@ export const ErrorListTemplate = ({ errors }: ErrorListProps) => { ))} 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 d7e271f8b3..78aef3428f 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx @@ -392,6 +392,46 @@ describe('Stepper', () => { expect(getByText('invalid postcode')).toBeInTheDocument(); }); + it('should render ajv-errors message', async () => { + const manifest: TemplateParameterSchema = { + steps: [ + { + title: 'Step 1', + schema: { + properties: { + postcode: { + type: 'string', + pattern: '[A-Z][0-9][A-Z] [0-9][A-Z][0-9]', + }, + }, + errorMessage: { + properties: { + postcode: 'invalid postcode', + }, + }, + }, + }, + ], + title: 'transformErrors Form Test', + }; + + const { getByText, getByRole } = await renderInTestApp( + + + , + ); + + await fireEvent.change(getByRole('textbox', { name: 'postcode' }), { + target: { value: 'invalid' }, + }); + + await act(async () => { + await fireEvent.click(getByRole('button', { name: 'Review' })); + }); + + expect(getByText('invalid postcode')).toBeInTheDocument(); + }); + it('should grab the initial formData from the query', async () => { const manifest: TemplateParameterSchema = { steps: [ @@ -509,6 +549,66 @@ describe('Stepper', () => { }); }); + it('should allow overrides to the uiSchema and formContext correctly', async () => { + const manifest: TemplateParameterSchema = { + title: 'Custom Fields', + steps: [ + { + title: 'Test', + schema: { + properties: { + name: { + type: 'string', + 'ui:placeholder': 'Enter your name', + }, + age: { + type: 'number', + 'ui:placeholder': 'Enter your age', + }, + }, + }, + }, + ], + }; + + const uiSchema = { + name: { + 'ui:readonly': true, + 'ui:placeholder': 'Should be overwritten', + }, + }; + + const formContext = { + readOnlyAsDisabled: true, + }; + + const { getByRole } = await renderInTestApp( + + + , + ); + + expect(getByRole('textbox', { name: 'name' })).toHaveValue('Some Name'); + expect(getByRole('textbox', { name: 'name' })).toBeDisabled(); + expect(getByRole('textbox', { name: 'name' })).toHaveAttribute( + 'placeholder', + 'Enter your name', + ); + + expect(getByRole('spinbutton', { name: 'age' })).toHaveValue(40); + expect(getByRole('spinbutton', { name: 'age' })).toBeEnabled(); + expect(getByRole('spinbutton', { name: 'age' })).toHaveAttribute( + 'placeholder', + 'Enter your age', + ); + }); + describe('Scaffolder Layouts', () => { it('should render the step in the scaffolder layout', async () => { const ScaffolderLayout: LayoutTemplate = ({ properties }) => ( diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 269b6a11d5..3a8858144b 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -35,7 +35,7 @@ import { } from './createAsyncValidators'; import { ReviewState, type ReviewStateProps } from '../ReviewState'; import { useTemplateSchema, useFormDataFromQuery } from '../../hooks'; -import validator from '@rjsf/validator-ajv8'; +import { customizeValidator } from '@rjsf/validator-ajv8'; import { useTransformSchemaToProps } from '../../hooks/useTransformSchemaToProps'; import { hasErrors } from './utils'; import * as FieldOverrides from './FieldOverrides'; @@ -50,6 +50,11 @@ import { ReviewStepProps } from '@backstage/plugin-scaffolder-react'; import { ErrorListTemplate } from './ErrorListTemplate'; import { makeStyles } from '@material-ui/core/styles'; import { PasswordWidget } from '../PasswordWidget/PasswordWidget'; +import ajvErrors from 'ajv-errors'; +import { merge } from 'lodash'; + +const validator = customizeValidator(); +ajvErrors(validator.ajv); const useStyles = makeStyles(theme => ({ backButton: { @@ -189,6 +194,14 @@ export const Stepper = (stepperProps: StepperProps) => { setFormState(current => ({ ...current, ...formData })); }; + const { + formContext: propFormContext, + uiSchema: propUiSchema, + ...restFormProps + } = props.formProps ?? {}; + + const mergedUiSchema = merge({}, propUiSchema, currentStep?.uiSchema); + return ( <> {isValidating && } @@ -225,9 +238,9 @@ export const Stepper = (stepperProps: StepperProps) => { validator={validator} extraErrors={errors as unknown as ErrorSchema} formData={formState} - formContext={{ formData: formState }} + formContext={{ ...propFormContext, formData: formState }} schema={currentStep.schema} - uiSchema={currentStep.uiSchema} + uiSchema={mergedUiSchema} onSubmit={handleNext} fields={fields} showErrorList="top" @@ -237,7 +250,7 @@ export const Stepper = (stepperProps: StepperProps) => { experimental_defaultFormStateBehavior={{ allOf: 'populateDefaults', }} - {...(props.formProps ?? {})} + {...restFormProps} >