diff --git a/.changeset/big-spies-stare.md b/.changeset/big-spies-stare.md new file mode 100644 index 0000000000..bab1643284 --- /dev/null +++ b/.changeset/big-spies-stare.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +Dedicated token for techdocs cache sync diff --git a/.changeset/bright-fireants-sit.md b/.changeset/bright-fireants-sit.md deleted file mode 100644 index 320bc69ff9..0000000000 --- a/.changeset/bright-fireants-sit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Added `MyGroupsSidebarItem` to the sidebar in the `create-app` template 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/brown-actors-clap.md b/.changeset/brown-actors-clap.md new file mode 100644 index 0000000000..af25a6e87f --- /dev/null +++ b/.changeset/brown-actors-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': minor +--- + +**BREAKING**: The deprecated identity and token manager services have been removed. This means that `coreServices.identity` and `coreServices.tokenManager` are gone, along with related types and utilities in other packages. diff --git a/.changeset/brown-boxes-arrive.md b/.changeset/brown-boxes-arrive.md new file mode 100644 index 0000000000..edefc0dcaf --- /dev/null +++ b/.changeset/brown-boxes-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/frontend-plugin-api': minor +--- + +**BREAKING**: Removed support for "v1" extensions. This means that it is no longer possible to declare inputs and outputs as objects when using `createExtension`. In addition, all extension creators except for `createComponentExtension` have been removed, use the equivalent blueprint instead. See the [1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations/#130) for more information on this change. diff --git a/.changeset/brown-worms-sneeze.md b/.changeset/brown-worms-sneeze.md new file mode 100644 index 0000000000..51359dd384 --- /dev/null +++ b/.changeset/brown-worms-sneeze.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-react': patch +--- + +Fixed issue in useShadowRootElements which could lead to unlimited render loops 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/clever-waves-judge.md b/.changeset/clever-waves-judge.md deleted file mode 100644 index 100ab66f90..0000000000 --- a/.changeset/clever-waves-judge.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-react': patch ---- - -Resolved the issue where changes in TechDoc add-ons, including the TextSize add-on, were not reapplying during navigation diff --git a/.changeset/cold-ways-protect.md b/.changeset/cold-ways-protect.md new file mode 100644 index 0000000000..26b693fcc6 --- /dev/null +++ b/.changeset/cold-ways-protect.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dynamic-feature-service': patch +--- + +Relax type check for a plugin's default export to also accept a BackendFeature defined as a function instead of an object diff --git a/.changeset/cool-poems-hammer.md b/.changeset/cool-poems-hammer.md new file mode 100644 index 0000000000..fd5897343e --- /dev/null +++ b/.changeset/cool-poems-hammer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-react': patch +--- + +Slight type tweak to match newer React versions better 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-rocks-invent.md b/.changeset/cuddly-rocks-invent.md new file mode 100644 index 0000000000..c4ce8ebdb9 --- /dev/null +++ b/.changeset/cuddly-rocks-invent.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dynamic-feature-service': minor +--- + +**BREAKING**: `dynamicPluginsServiceFactory` is no longer callable as a function. If you need to provide options to make a custom factory, use `dynamicPluginsSchemasServiceFactoryWithOptions` instead. diff --git a/.changeset/curly-brooms-raise.md b/.changeset/curly-brooms-raise.md new file mode 100644 index 0000000000..84c8892f7c --- /dev/null +++ b/.changeset/curly-brooms-raise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend': patch +--- + +Deprecate create router as the legacy backend system will no longer be supported. 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-monkeys-mate.md b/.changeset/dry-monkeys-mate.md new file mode 100644 index 0000000000..51883ddefa --- /dev/null +++ b/.changeset/dry-monkeys-mate.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +The `KubernetesBuilder` and its related types has been marked as deprecared. This backend should instead be initialized using the new backend system. 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-changelog.md b/docs/releases/v1.30.0-changelog.md new file mode 100644 index 0000000000..0af531f483 --- /dev/null +++ b/docs/releases/v1.30.0-changelog.md @@ -0,0 +1,3093 @@ +# Release v1.30.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.30.0](https://backstage.github.io/upgrade-helper/?to=1.30.0) + +## @backstage/backend-app-api@0.9.0 + +### Minor Changes + +- da4fde5: **BREAKING**: Removed several deprecated service factories. These can instead be imported from `@backstage/backend-defaults` package. +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. +- 389f5a4: Remove deprecated `urlReaderServiceFactory`, please import from `@backstage/backend-defaults/urlReader` instead. + +### Patch Changes + +- 8b13183: Added support for the latest version of `BackendFeature`s from `@backstage/backend-plugin-api`, including feature loaders. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 7c5f3b0: Update the `ServiceRegister` implementation to enable registering multiple service implementations for a given service ref. +- 80a0737: Added configuration for the `packages` options to config schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @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/backend-common@0.24.0 + +### Minor Changes + +- 389f5a4: **BREAKING**: Removed the following `Url Reader` deprecated exports: + + - UrlReader: Use `UrlReaderService` from `@backstage/backend-plugin-api` instead; + - AzureUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - BitbucketUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - BitbucketCloudUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - BitbucketServerUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GithubUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GitlabUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GerritUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GiteaUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - HarnessUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - AwsS3UrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - FetchUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - UrlReaders: Import from `@backstage/backend-defaults/urlReader` instead; + - UrlReadersOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - UrlReaderPredicateTuple: Import from `@backstage/backend-defaults/urlReader` instead; + - FromReadableArrayOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - ReaderFactory: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadUrlOptions:Use `UrlReaderServiceReadUrlOptions` from `@backstage/backend-plugin-api` instead; + - ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` from `@backstage/backend-plugin-api` instead; + - ReadUrlResponseFactory: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadUrlResponseFactoryFromStreamOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadTreeOptions: Use `UrlReaderServiceReadTreeOptions` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponse: Use `UrlReaderServiceReadTreeResponse` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponseFile: Use `UrlReaderServiceReadTreeResponseFile` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponseDirOptions: Use `UrlReaderServiceReadTreeResponseDirOptions` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponseFactory: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadTreeResponseFactoryOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - SearchOptions: Use `UrlReaderServiceSearchOptions` from `@backstage/backend-plugin-api` instead; + - SearchResponse: Use `UrlReaderServiceSearchResponse` from `@backstage/backend-plugin-api` instead; + - SearchResponseFile: Use `UrlReaderServiceSearchResponseFile` from `@backstage/backend-plugin-api` instead. + +### 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. + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater + +- 6795e33: This package is marked as `deprecated` and will be removed in a near future, please follow the deprecated instructions for the exports you still use. + +- 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). + +- ddde5fe: Internal type refactor. + +- b63d378: export `createConfigSecretEnumerator` from `@backstage/backend-common` instead of `@backstage/backend-app-api`. + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/integration@1.14.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/backend-dynamic-feature-service@0.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- b63d378: Update internal imports +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @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 + - @backstage/plugin-events-backend@0.3.10 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/backend-plugin-api@0.8.0 + +### Minor Changes + +- 389f5a4: **BREAKING** Deleted the following deprecated `UrlReader` exports + + - ReadUrlOptions: Use `UrlReaderServiceReadUrlOptions` instead; + - ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` instead; + - ReadTreeOptions: Use `UrlReaderServiceReadTreeOptions` instead; + - ReadTreeResponse: Use `UrlReaderServiceReadTreeResponse` instead; + - ReadTreeResponseFile: Use `UrlReaderServiceReadTreeResponseFile` instead; + - ReadTreeResponseDirOptions: Use `UrlReaderServiceReadTreeResponseDirOptions` instead; + - SearchOptions: Use `UrlReaderServiceSearchOptions` instead; + - SearchResponse: Use `UrlReaderServiceSearchResponse` instead; + - SearchResponseFile: Use `UrlReaderServiceSearchResponseFile` instead. + +- 7c5f3b0: The `createServiceRef` function now accepts a new boolean `multiple` option. The `multiple` option defaults to `false` and when set to `true`, it enables that multiple implementation are installed for the created service ref. + + We're looking for ways to make it possible to augment services without the need to replace the entire service. + + Typical example of that being the ability to install support for additional targets for the `UrlReader` service without replacing the service itself. This achieves that by allowing us to define services that can have multiple simultaneous implementation, allowing the `UrlReader` implementation to depend on such a service to collect all possible implementation of support for external targets: + + ```diff + // @backstage/backend-defaults + + + export const urlReaderFactoriesServiceRef = createServiceRef({ + + id: 'core.urlReader.factories', + + scope: 'plugin', + + multiton: true, + + }); + + ... + + export const urlReaderServiceFactory = createServiceFactory({ + service: coreServices.urlReader, + deps: { + config: coreServices.rootConfig, + logger: coreServices.logger, + + factories: urlReaderFactoriesServiceRef, + }, + - async factory({ config, logger }) { + + async factory({ config, logger, factories }) { + return UrlReaders.default({ + config, + logger, + + factories, + }); + }, + }); + ``` + + With that, you can then add more custom `UrlReader` factories by installing more implementations of the `urlReaderFactoriesServiceRef` in your backend instance. Something like: + + ```ts + // packages/backend/index.ts + import { createServiceFactory } from '@backstage/backend-plugin-api'; + import { urlReaderFactoriesServiceRef } from '@backstage/backend-defaults'; + ... + + backend.add(createServiceFactory({ + service: urlReaderFactoriesServiceRef, + deps: {}, + async factory() { + return CustomUrlReader.factory; + }, + })); + + ... + + ``` + +- c99c620: **BREAKING** Removed the following deprecated types: + + - `ServiceRefConfig` use `ServiceRefOptions` + - `RootServiceFactoryConfig` use `RootServiceFactoryOptions` + - `PluginServiceFactoryConfig` use `PluginServiceFactoryOptions` + +### Patch Changes + +- 6061061: Added `createBackendFeatureLoader`, which can be used to create an installable backend feature that can in turn load in additional backend features in a dynamic way. + +- ba9abf4: The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. + +- 8b13183: Added `createBackendFeatureLoader`, which can be used to programmatically select and install backend features. + + A feature loader can return an list of features to be installed, for example in the form on an `Array` or other for of iterable, which allows for the loader to be defined as a generator function. Both synchronous and asynchronous loaders are supported. + + Additionally, a loader can depend on services in its implementation, with the restriction that it can only depend on root-scoped services, and it may not override services that have already been instantiated. + + ```ts + const searchLoader = createBackendFeatureLoader({ + deps: { + config: coreServices.rootConfig, + }, + *loader({ config }) { + // Example of a custom config flag to enable search + if (config.getOptionalString('customFeatureToggle.search')) { + yield import('@backstage/plugin-search-backend/alpha'); + yield import('@backstage/plugin-search-backend-module-catalog/alpha'); + yield import('@backstage/plugin-search-backend-module-explore/alpha'); + yield import('@backstage/plugin-search-backend-module-techdocs/alpha'); + } + }, + }); + ``` + +- ddde5fe: Fixed a type issue where plugin and modules depending on multiton services would not receive the correct type. + +- f011d1b: fix typo in `getPluginRequestToken` comments + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-tasks@0.6.0 + +### Minor Changes + +- fc24d9e: This package is deprecated and will be removed in a near future, follow the instructions below to stop using it: + + - `TaskScheduler`: Please migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead, or use `DefaultSchedulerService` from \`@backstage/backend-defaults; + - `TaskRunner`: Please import `SchedulerServiceTaskRunner` from `@backstage/backend-plugin-api` instead; + - `TaskFunction`: Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead; + - `TaskDescriptor`: Please import `SchedulerServiceTaskDescriptor` from `@backstage/backend-plugin-api` instead; + - `TaskInvocationDefinition`: Please import `SchedulerServiceTaskInvocationDefinition` from `@backstage/backend-plugin-api` instead; + - `TaskScheduleDefinition`: Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead; + - `TaskScheduleDefinitionConfig`: Please import `SchedulerServiceTaskScheduleDefinitionConfig` from `@backstage/backend-plugin-api` instead; + - `PluginTaskScheduler`: Please use `SchedulerService` from `@backstage/backend-plugin-api` instead (most likely via `coreServices.scheduler`); + - `readTaskScheduleDefinitionFromConfig`: Please import `readSchedulerServiceTaskScheduleDefinitionFromConfig` from `@backstage/backend-plugin-api` instead; + - `HumanDuration`: Import `TypesHumanDuration` from `@backstage/types` instead. + +### Patch Changes + +- ba9abf4: The `PluginTaskScheduler` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `PluginTaskScheduler.triggerTask`. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.5.0 + +### Minor Changes + +- 861f162: **BREAKING**: Removed these deprecated helpers: + + - `setupRequestMockHandlers` is removed; use `registerMswTestHooks` instead. + - `MockDirectoryOptions` is removed; use `CreateMockDirectoryOptions` instead. + + Stopped exporting the deprecated and internal `isDockerDisabledForTests` helper. + + Removed `get` method from `ServiceFactoryTester` which is replaced by `getSubject` + +### Patch Changes + +- 8b13183: Internal updates to support latest version of `BackendFeauture`s from `@backstage/backend-plugin-api`. +- b63d378: Update internal imports +- 7c5f3b0: Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation. +- 4e79d19: The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service. +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/catalog-model@1.6.0 + +### Minor Changes + +- 34fa803: Introduce an optional spec.type attribute on the Domain and System entity kinds + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/cli@0.27.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 +- b2d97fd: Fixing loading of additional config files with new `ConfigSources` +- fbc7819: Use ES2022 in CLI bundler +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 6d898d8: Switched the `process` polyfill to use `require.resolve` for greater compatability. +- 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 +- e6e7d86: Switched the target from `'ES2022'` to `'es2022'` for better compatibility with older versions of `swc`. +- 2ced236: Updated dependency `@module-federation/enhanced` to `0.3.1` +- 0eedec3: Add support for dynamic plugins via the EXPERIMENTAL_MODULE_FEDERATION environment variable when running `yarn start`. +- adabb40: New command now supports setting package license +- dc4fb4f: Fix for `repo build --all` not properly detecting the experimental public entry point. +- Updated dependencies + - @backstage/config-loader@1.9.0 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.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/config-loader@1.9.0 + +### Minor Changes + +- 274428f: Add configuration key to File and Remote `ConfigSource`s that enables configuration of parsing logic. Previously limited to yaml, these `ConfigSource`s now allow for a multitude of parsing options (e.g. JSON). + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 1edd6c2: The `env` option of `ConfigSources.default` now correctly allows undefined members. +- 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/frontend-app-api@0.8.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 7777b5f: Support icon overriding with the new `IconBundleBlueprint` API. +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- 3be9aeb: Added support for v2 extensions, which declare their inputs and outputs without using a data map. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @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.7.0 + +### Minor Changes + +- 72754db: **BREAKING**: All types of route refs are always considered optional by `useRouteRef`, which means the caller must always handle a potential `undefined` return value. Related to this change, the `optional` option from `createExternalRouteRef` has been removed, since it is no longer necessary. + + This is released as an immediate breaking change as we expect the usage of the new route refs to be extremely low or zero, since plugins that support the new system will still use route refs and `useRouteRef` from `@backstage/core-plugin-api` in combination with `convertLegacyRouteRef` from `@backstage/core-compat-api`. + +### Patch Changes + +- 6f72c2b: Fixing issue with extension blueprints `inputs` merging. + +- 210d066: Added support for using the `params` in other properties of the `createExtensionBlueprint` options by providing a callback. + +- 9b356dc: Renamed `createPlugin` to `createFrontendPlugin`. The old symbol is still exported but deprecated. + +- a376559: Correct the `TConfig` type of data references to only contain config + +- 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: [coreExtensionData.reactElement], + factory(params: { text: string }) { + return [coreExtensionData.reactElement(

{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. + +- e493020: Deprecated `inputs` and `configSchema` options for `createComponentExtenion`, these will be removed in a future release + +- 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, + }, + }, + }); + ``` + +- 99abb6b: Support overriding of plugin extensions using the new `plugin.withOverrides` method. + + ```tsx + import homePlugin from '@backstage/plugin-home'; + + export default homePlugin.withOverrides({ + extensions: [ + homePage.getExtension('page:home').override({ + *factory(originalFactory) { + yield* originalFactory(); + yield coreExtensionData.reactElement(

My custom home page

); + }, + }), + ], + }); + ``` + +- 813cac4: Add an `ExtensionBoundary.lazy` function to create properly wrapped lazy-loading enabled elements, suitable for use with `coreExtensionData.reactElement`. The page blueprint now automatically leverages this. + +- a65cfc8: Add support for accessing extensions definitions provided by a plugin via `plugin.getExtension(...)`. For this to work the extensions must be defined using the v2 format, typically using an extension blueprint. + +- 3be9aeb: Extensions have been changed to be declared with an array of inputs and outputs, rather than a map of named data refs. This change was made to reduce confusion around the role of the input and output names, as well as enable more powerful APIs for overriding extensions. + + An extension that was previously declared like this: + + ```tsx + const exampleExtension = createExtension({ + name: 'example', + inputs: { + items: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + output: { + element: coreExtensionData.reactElement, + }, + factory({ inputs }) { + return { + element: ( +
+ Example + {inputs.items.map(item => { + return
{item.output.element}
; + })} +
+ ), + }; + }, + }); + ``` + + Should be migrated to the following: + + ```tsx + const exampleExtension = createExtension({ + name: 'example', + inputs: { + items: createExtensionInput([coreExtensionData.reactElement]), + }, + output: [coreExtensionData.reactElement], + factory({ inputs }) { + return [ + coreExtensionData.reactElement( +
+ Example + {inputs.items.map(item => { + return
{item.get(coreExtensionData.reactElement)}
; + })} +
, + ), + ]; + }, + }); + ``` + +- 34f1b2a: Support merging of `inputs` in extension blueprints, but stop merging `output`. In addition, the original factory in extension blueprints now returns a data container that both provides access to the returned data, but can also be forwarded as output. + +- 3fb421d: Added support to be able to define `zod` config schema in Blueprints, with built in schema merging from the Blueprint and the extension instances. + +- 2d21599: Added support for being able to override extension definitions. + + ```tsx + const TestCard = EntityCardBlueprint.make({ + ... + }); + + TestCard.override({ + // override attachment points + attachTo: { id: 'something-else', input: 'overridden' }, + // extend the config schema + config: { + schema: { + newConfig: z => z.string().optional(), + } + }, + // override factory + *factory(originalFactory, { inputs, config }){ + const originalOutput = originalFactory(); + + yield coreExentsionData.reactElement( + + {originalOutput.get(coreExentsionData.reactElement)} + + ); + } + }); + + ``` + +- 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', + }); + ``` + +- 6349099: Added config input type to the extensions + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/integration@1.14.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-auth-backend-module-cloudflare-access-provider@0.2.0 + +### Minor Changes + +- 75d026a: Support for Cloudflare Custom Headers and Custom Cookie Auth Name + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-node@0.5.0 + +### Minor Changes + +- 579afd0: **BREAKING**: Sign-in resolvers configured via `.signIn.resolvers` now take precedence over sign-in resolvers passed to `signInResolver` option of `createOAuthProviderFactory`. This effectively makes sign-in resolvers passed via the `signInResolver` the default one, which you can then override through configuration. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog@1.22.0 + +### Minor Changes + +- 6925dcb: Introduces the HasSubdomainsCard component that displays the subdomains of a given domain + +### Patch Changes + +- 496b8a9: Export `RelatedEntitiesCard` presets to be reused. +- 604a504: The entity relation cards available for the new frontend system via `/alpha` now have more accurate and granular default filters. +- 7bd27e1: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 6582799: Add `tableOptions` to all tables and additionally `title` to API tables. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-search-common@1.2.14 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-catalog-backend@1.25.0 + +### Minor Changes + +- 163ba08: Deprecated `RouterOptions`, `CatalogBuilder`, and `CatalogEnvironment`. Please make sure to upgrade to the new backend system. +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 776eb56: `ProcessorOutputCollector` returns an error when receiving deferred entities that have an invalid `metadata.annotations` format. + + This allows to return an error on an actual validation issue instead of reporting that the location annotations are missing afterwards, which is misleading for the users. + +- 389f5a4: Update deprecated url-reader-related imports. + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater + +- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint + +- 51240ee: Preserve default `allowedLocationTypes` when `setAllowedLocationTypes()` of `CatalogLocationsExtensionPoint` is not called. + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-search-backend-module-catalog@0.2.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/backend-openapi-utils@0.1.16 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-aws@0.4.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### 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. +- 9342ac8: Removed unused dependency +- 389f5a4: Update deprecated url-reader-related imports. +- 90a7340: `AwsOrganizationCloudAccountProcessor` configuration field `roleArn` is deprecated in favor of new field `accountId` +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-catalog-backend-module-azure@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/backend-openapi-utils@0.1.16 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-gcp@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-github@0.7.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater + +- c1eb809: Fix GitHub `repository` event support. + + - `$.repository.organization` is only provided for `push` events. Switched to `$.organization.login` instead. + - `$.repository.url` is not always returning the expected and required value. Use `$.repository.html_url` instead. + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-github-org@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-backend-module-github@0.7.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-gitlab@0.4.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- c7b14ed: Adds new optional `excludeRepos` configuration option to the Gitlab catalog provider. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.1.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.4.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.5.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-ldap@0.8.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-msgraph@0.6.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 58dff4d: Added option to ingest groups based on their group membership in Azure Entra ID +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### 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-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-notifications@0.3.0 + +### Minor Changes + +- 0410fc9: By default, set notification as read when opening snackbar or web notification link + +### Patch Changes + +- 80b84f7: Fixed issue with notification reloading on page change +- b58e452: Broadcast notifications are now decorated with an icon. +- Updated dependencies + - @backstage/core-components@0.14.10 + - @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-module-email@0.2.0 + +### Minor Changes + +- def53a7: **BREAKING** Following `NotificationTemplateRenderer` methods now return a Promise and **must** be awaited: `getSubject`, `getText` and `getHtml`. + + Required changes and example usage: + + ```diff + import { notificationsEmailTemplateExtensionPoint } from '@backstage/plugin-notifications-backend-module-email'; + import { Notification } from '@backstage/plugin-notifications-common'; + +import { getNotificationSubject, getNotificationTextContent, getNotificationHtmlContent } from 'my-notification-processing-library` + export const notificationsModuleEmailDecorator = createBackendModule({ + pluginId: 'notifications', + moduleId: 'email.templates', + register(reg) { + reg.registerInit({ + deps: { + emailTemplates: notificationsEmailTemplateExtensionPoint, + }, + async init({ emailTemplates }) { + emailTemplates.setTemplateRenderer({ + - getSubject(notification) { + + async getSubject(notification) { + - return `New notification from ${notification.source}`; + + const subject = await getNotificationSubject(notification); + + return `New notification from ${subject}`; + }, + - getText(notification) { + + async getText(notification) { + - return notification.content; + + const text = await getNotificationTextContent(notification); + + return text; + }, + - getHtml(notification) { + + async getHtml(notification) { + - return `

${notification.content}

`; + + const html = await getNotificationHtmlContent(notification); + + return html; + }, + }); + }, + }); + }, + }); + ``` + +### Patch Changes + +- d55b8e3: Avoid sending broadcast emails as a fallback in case the entity-typed notification user can not be resolved. +- cdb630d: Add support for stream transport for debugging purposes +- 83faf24: Notification email processor supports allowing or denying specific email addresses from receiving notifications +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-notifications-node@0.2.4 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @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-scaffolder@1.24.0 + +### Minor Changes + +- 1552c33: Changed the way to display entities in `MyGroupsPicker` to use `entityPresentationApi` and make it consistent across scaffolder pickers +- 3fca643: Added field extension `RepoBranchPicker` that supports autocompletion for Bitbucket + +### Patch Changes + +- 47ed51b: Add an extra bit of height to the EntityPicker dropdown to make it clear there are more options to select from, and to remove the scroll bar when there is less than 10 options +- 46e5e55: Change scaffolder widgets to use `TextField` component for more flexibility in theme overrides. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 2ae63cd: add i18n for scaffolder +- d18f4eb: Fix undefined in the title of Scaffolder Runs on the page load +- 896a22d: Fix helper text margin for scaffolder EntityNamePicker and EntityTagsPicker when using outlined text field +- 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.7.0 + - @backstage/plugin-scaffolder-react@1.11.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-backend@1.24.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. +- dcd6a79: Added OpenTelemetry support to Scaffolder metrics + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- c544f81: Add support for status filtering in scaffolder tasks endpoint +- b63d378: Update internal imports +- ef87e06: Fix scaffolder action `catalog:write` to write to directories that don't already exist +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.15 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @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 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-react@1.11.0 + +### Minor Changes + +- 8839381: Add scaffolder option to display object items in separate rows on review page + +### Patch Changes + +- 072c00c: Fixed a bug in `DefaultTableOutputs` where output elements overlapped on smaller screen sizes +- 46e5e55: Change scaffolder widgets to use `TextField` component for more flexibility in theme overrides. +- 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 +- 04759f2: Fix null check in `isJsonObject` utility function for scaffolder review state component +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @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.25 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-search-backend-module-catalog@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-search-backend-module-explore@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 93fc1a0: Updated dependency `@backstage-community/plugin-explore-common` to `^0.0.4`. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-techdocs@0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-techdocs-node@1.12.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-search-backend-node@1.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 3123c16: Fix package metadata +- 7c5f3b0: Explicit declare if the service ref accepts `single` or `multiple` implementations. +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-techdocs-common@0.1.0 + +### Minor Changes + +- 4698e1f: Initial release of the techdocs-common package. + +## @backstage/app-defaults@1.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/backend-defaults@0.4.2 + +### Patch Changes + +- 0d16b52: Add access restrictions to the JWKS external access method config schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 3b429fb: Added deprecation warning to urge users to perform the auth service migration or implement their own token manager service. + See for more information. +- 7681b17: update the `morgan` middleware to use a custom format to prevent PII from being logged +- 4e79d19: The `createHealthRouter` utility that allows you to create a health check router is now exported via `@backstage/backend-defaults/rootHttpRouter`. +- ba9abf4: The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. +- 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`. +- 7c5f3b0: Update the `UrlReader` service to depends on multiple instances of `UrlReaderFactoryProvider` service. +- 81f930a: use formatted query to prevent chance of SQL-injection +- 1d5f298: Avoid excessive numbers of error listeners on cache clients +- Updated dependencies + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/integration@1.14.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-events-node@0.3.9 + +## @backstage/backend-dev-utils@0.1.5 + +### Patch Changes + +- 3a35172: Fix `EventEmitter` memory leak in the development utilities + +## @backstage/backend-openapi-utils@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/errors@1.2.4 + +## @backstage/catalog-client@1.6.6 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/core-app-api@1.14.2 + +### 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 + +### Patch Changes + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- 16cf96c: Both `compatWrapper` and `convertLegacyRouteRef` now support converting from the new system to the old. +- 519b8e0: Added new utilities for converting legacy plugins and extensions to the new system. The `convertLegacyPlugin` option will convert an existing plugin to the new system, although you need to supply extensions for the plugin yourself. To help out with this, there is also a new `convertLegacyPageExtension` which converts an existing page extension to the new system. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/core-components@0.14.10 + +### Patch Changes + +- 678971a: Move the `Link` component to the `RoutedTabs` instead of the `HeaderTabs` component +- 13a9c63: Corrected the documentation for the GCP IAP auth module and updated the configuration to follow proxy configuration conventions by ignoring authEnv +- 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 + +### Patch Changes + +- c0a705d: Added the Kubernetes plugin to `create-app` +- d7a0aa3: Bumped create-app version. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 6c1081c: Updated dockerfile and `app-config.production.yaml` to make it easier to get started with example data +- bfeba46: Included permission config and enabled it out of the box +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/dev-utils@1.0.37 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + +## @backstage/frontend-test-utils@0.1.12 + +### Patch Changes + +- 8209449: Added new APIs for testing extensions + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. + +- 3be9aeb: Added support for v2 extensions, which declare their inputs and outputs without using a data map. + +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. + +- 2d21599: Added support for being able to override extension definitions. + + ```tsx + const TestCard = EntityCardBlueprint.make({ + ... + }); + + TestCard.override({ + // override attachment points + attachTo: { id: 'something-else', input: 'overridden' }, + // extend the config schema + config: { + schema: { + newConfig: z => z.string().optional(), + } + }, + // override factory + *factory(originalFactory, { inputs, config }){ + const originalOutput = originalFactory(); + + yield coreExentsionData.reactElement( + + {originalOutput.get(coreExentsionData.reactElement)} + + ); + } + }); + + ``` + +- c00e1a0: Deprecate the `.render` method of the `createExtensionTester` in favour of using `renderInTestApp` directly. + + ```tsx + import { + renderInTestApp, + createExtensionTester, + } from '@backstage/frontend-test-utils'; + + const tester = createExtensionTester(extension); + + const { getByTestId } = renderInTestApp(tester.reactElement()); + + // or if you're not using `coreExtensionData.reactElement` as the output ref + const { getByTestId } = renderInTestApp(tester.get(myComponentRef)); + ``` + +- 264e10f: Deprecate existing `ExtensionCreators` in favour of their new Blueprint counterparts. + +- 264e10f: Refactor `.make` method on Blueprints into two different methods, `.make` and `.makeWithOverrides`. + + When using `createExtensionBlueprint` you can define parameters for the factory function, if you wish to take advantage of these parameters you should use `.make` when creating an extension instance of a Blueprint. If you wish to override more things other than the standard `attachTo`, `name`, `namespace` then you should use `.makeWithOverrides` instead. + + `.make` is reserved for simple creation of extension instances from Blueprints using higher level parameters, whereas `.makeWithOverrides` is lower level and you have more control over the final extension. + +- 6349099: Added config input type to the extensions + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/frontend-app-api@0.8.0 + - @backstage/config@1.2.0 + - @backstage/test-utils@1.5.10 + - @backstage/types@1.1.1 + +## @backstage/integration-react@1.1.30 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/repo-tools@0.9.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config-loader@1.9.0 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/plugin-techdocs-node@1.12.9 + - @backstage/catalog-model@1.6.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## @backstage/test-utils@1.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/core-app-api@1.14.2 + - @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-react@0.4.25 + +## @backstage/plugin-api-docs@0.11.8 + +### Patch Changes + +- 770ba02: `ConsumingComponentsCard` and `ProvidingComponentsCard` will now optionally accept `columns` to override which table columns are displayed +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- ebfeb40: Added `resolvers` prop to `AsyncApiDefinitionWidget`. This allows to override the default http/https resolvers, for example to add authentication to requests to internal schema registries. +- 4b6d2cb: Updated dependency `@graphiql/react` to `^0.23.0`. +- 6582799: Add `tableOptions` to all tables and additionally `title` to API tables. +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-app-backend@0.3.72 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 6bd6fda: Deprecate `createRouter` and its options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.23 + +## @backstage/plugin-app-node@0.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config-loader@1.9.0 + +## @backstage/plugin-app-visualizer@0.1.9 + +### Patch Changes + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- e493020: Fixing issue with the visualizer crashing when clicking on the detailed view because of `routeRef` parameters +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-auth-backend@0.22.10 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- cc9a7a5: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.2.0 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20 + - @backstage/plugin-auth-backend-module-google-provider@0.1.20 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4 + - @backstage/plugin-auth-backend-module-oidc-provider@0.2.4 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.16 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema + +- 4ea354f: Added a `signer` configuration option to validate against the token claims. We strongly recommend that you set this value (typically on the format `arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456`) to ensure that the auth provider can safely check the authenticity of any incoming tokens. + + Example: + + ```diff + auth: + providers: + awsalb: + # this is the URL of the IdP you configured + issuer: 'https://example.okta.com/oauth2/default' + # this is the ARN of your ALB instance + + signer: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456' + # this is the region where your ALB instance resides + region: 'us-west-2' + signIn: + resolvers: + # typically you would pick one of these + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + ``` + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 13a9c63: Corrected the documentation for the GCP IAP auth module and updated the configuration to follow proxy configuration conventions by ignoring authEnv +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.20 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.20 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 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.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.16 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.2.4 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + +## @backstage/plugin-auth-react@0.1.5 + +### Patch Changes + +- aeac3e9: feat: Hide visibility of CookieAuthRedirect +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-bitbucket-cloud-common@0.2.22 + +### Patch Changes + +- 3fca643: Added method `listBranchesByRepository` to `BitbucketCloudClient` +- Updated dependencies + - @backstage/integration@1.14.0 + +## @backstage/plugin-catalog-backend-module-logs@0.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.41 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + +## @backstage/plugin-catalog-common@1.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/catalog-model@1.6.0 + +## @backstage/plugin-catalog-graph@0.4.8 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- fba7537: Memoize entity graph nodes when applying an `entityFilter` to prevent repeated redraws +- 4a529c2: Use `entityPresentationApi` for the node title and the icon. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.12.2 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-node@1.12.5 + +### Patch Changes + +- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint +- 7c5f3b0: Explicit declare if the service ref accepts `single` or `multiple` implementations. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-catalog-react@1.12.3 + +### Patch Changes + +- 7bd27e1: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. +- 31bfc44: Updated alpha definitions of extension data references. +- 7ca331c: Correct `EntityDisplayName`'s icon alignment with the text. +- 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 +- 519b8e0: Added utilities for converting existing entity card and content extensions to the new frontend system. This is in particular useful when used in combination with the new `convertLegacyPlugin` utility from `@backstage/core-compat-api`. +- d001a42: Fix label related accessibility issues with `FavorityEntity` +- 012e3eb: Entity page extensions created for the new frontend system via the `/alpha` exports will now be enabled by default. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + +## @backstage/plugin-config-schema@0.1.58 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-devtools@0.1.17 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-react@0.4.25 + +## @backstage/plugin-devtools-backend@0.3.9 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 32a38e1: Removed unused code for lockfile analysis. +- 2886ef7: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-permission-node@0.8.1 + - @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.12 + +## @backstage/plugin-devtools-common@0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-events-backend@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-backend-module-azure@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-backend-module-gerrit@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-backend-module-github@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-backend-module-gitlab@0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-backend-test-utils@0.1.33 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-events-node@0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + +## @backstage/plugin-home@0.7.9 + +### Patch Changes + +- 31bfc44: Updated alpha definitions of extension data references. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- 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.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/core-app-api@1.14.2 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-home-react@0.1.16 + +## @backstage/plugin-home-react@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-kubernetes@0.11.13 + +### Patch Changes + +- e6c15cc: Adds support for Backstage's new frontend system, available via the `/alpha` sub-path export. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-kubernetes-react@0.4.2 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-kubernetes-backend@0.18.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b63d378: Update internal imports +- 8c1aa06: Add `kubernetes.clusterLocatorMethods[].clusters[].customResources` to the configuration schema. + This was already documented and supported by the plugin. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-kubernetes-node@0.1.17 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @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-common@0.8.2 + +## @backstage/plugin-kubernetes-cluster@0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-kubernetes-react@0.4.2 + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-kubernetes-common@0.8.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-node@0.1.17 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b63d378: Update internal imports +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-kubernetes-react@0.4.2 + +### Patch Changes + +- 954a593: `Liveness Probe` added in ContainerCard Component of PodDrawer +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## @backstage/plugin-notifications-backend@0.3.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- d55b8e3: Avoid sending broadcast emails as a fallback in case the entity-typed notification user can not be resolved. +- 8013044: fix: consider broadcast union with user +- 7a05f50: Allow using notifications without users in the catalog +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-notifications-node@0.2.4 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.9 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.9 + +## @backstage/plugin-notifications-node@0.2.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.9 + +## @backstage/plugin-org@0.6.28 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-org-react@0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-permission-backend@0.5.47 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + +## @backstage/plugin-permission-common@0.8.1 + +### Patch Changes + +- df784fe: Add the MetadataResponse type from @backstage/plugin-permission-node, since this + type might be used in frontend code. +- 137fa34: Add the MetadataResponseSerializedRule type from @backstage/plugin-permission-node, since this type might be used in frontend code. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-permission-node@0.8.1 + +### Patch Changes + +- df784fe: The MetadataResponse type has been moved to @backstage/plugin-permission-common + to match the recent move of MetadataResponseSerializedRule, and should be + imported from there going forward. To avoid an immediate breaking change, this + type is still re-exported from this package, but is marked as deprecated and + will be removed in a future release. +- 5cd9878: The MetadataResponseSerializedRule type has been moved to @backstage/plugin-permission-common, and should be imported from there going forward. To avoid an immediate breaking change, this type is still re-exported from this package, but is marked as deprecated and will be removed in a future release. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-permission-react@0.4.25 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-proxy-backend@0.5.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b63d378: Update internal imports +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.15 + +### Patch Changes + +- 187f583: Added examples for publish:azure action and updated its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 3fca643: Added autocompletion support for resource `branches` +- d57967c: Add ability to set the initial commit message when initializing a repository using the scaffolder action. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- e3b64be: Added examples for publish:bitbucketServer action and improve its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.47 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- dae85df: Add examples for `fetch:cookiecutter` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 24de005: Added test cases for publish:gitea examples +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-github@0.4.1 + +### Patch Changes + +- d21d307: Added examples for github:environment:create action and improve its test cases +- 6d4cb97: Added examples for github:repo:create action and improved test cases +- cd203f1: Added examples for action github:pages and improved its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5 + +### Patch Changes + +- da97131: Added test cases for gitlab:issues:create examples + +- 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-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.6 + +### Patch Changes + +- 6fc03c7: Add examples for notification:send scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-notifications-node@0.2.4 + - @backstage/plugin-notifications-common@0.0.5 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.40 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 449def7: Add examples for fetch:rails scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.31 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 382e868: Added test cases for sentry:project:create examples +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.10 + +## @backstage/plugin-scaffolder-common@1.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node@0.4.9 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- c544f81: Add support for status filtering in scaffolder tasks endpoint +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.24.0 + - @backstage/backend-test-utils@0.5.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/types@1.1.1 + +## @backstage/plugin-search@1.4.15 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 3123c16: Fix package metadata +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-search-common@1.2.14 + - @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.15 + +### Patch Changes + +- 3123c16: Fix package metadata +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/backend-openapi-utils@0.1.16 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + +## @backstage/plugin-search-backend-module-pg@0.5.33 + +### Patch Changes + +- 7251567: Removing `@backstage/backend-app-api` dependency +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-common@1.2.14 + +### Patch Changes + +- 3123c16: Fix package metadata +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-search-react@1.7.14 + +### Patch Changes + +- 7bd27e1: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. +- 31bfc44: Updated alpha definitions of extension data references. +- 3123c16: Fix package metadata +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/plugin-search-common@1.2.14 + - @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.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @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 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + - @backstage/plugin-signals-node@0.1.9 + +## @backstage/plugin-signals-node@0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + +## @backstage/plugin-techdocs@1.10.8 + +### Patch Changes + +- 69bd940: Use annotation constants from new techdocs-common package. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 27794d1: Allow for more granular control of TechDocsReaderPage styling. Theme overrides can now be provided to TechDocs without affecting the theme in other areas of Backstage. +- 4490d73: Refactor TechDocs' mkdocs-redirects support. +- 8543e72: TechDocs redirect feature now includes a notification to the user before they are redirected. +- 67e76f2: TechDocs now supports the `mkdocs-redirects` plugin. Redirects defined using the `mkdocs-redirect` plugin will be handled automatically in TechDocs. Redirecting to external urls is not supported. In the case that an external redirect url is provided, TechDocs will redirect to the current documentation site home. +- bdc5471: Fixed issue where header styles were incorrectly generated when themes used CSS variables to define font size. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-search-common@1.2.14 + - @backstage/integration@1.14.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-react@1.2.7 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.37 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/test-utils@1.5.10 + - @backstage/plugin-techdocs-react@1.2.7 + +## @backstage/plugin-techdocs-backend@1.10.10 + +### Patch Changes + +- 69bd940: Use annotation constants from new techdocs-common package. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b77fbf4: Added back `type: 'local'` to TechDocs config schema for `publisher` +- a16632c: Update configuration schema to match actual behavior +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-techdocs-node@1.12.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.0 + - @backstage/integration@1.14.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.26 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/integration@1.14.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-techdocs-react@1.2.7 + +## @backstage/plugin-techdocs-node@1.12.9 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 69bd940: Use annotation constants from new techdocs-common package. +- 949083d: Update `patchMkdocsYmlPrebuild` to modify `repo_url` and `edit_uri` independently. +- 5cedd9f: Fix TechDocs Edit URL for nested docs +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-search-common@1.2.14 + - @backstage/integration@1.14.0 + - @backstage/plugin-techdocs-common@0.1.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + +## @backstage/plugin-techdocs-react@1.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.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.11 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/core-app-api@1.14.2 + - @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 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-node@0.1.9 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.100 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/plugin-scaffolder@1.24.0 + - @backstage/plugin-scaffolder-react@1.11.0 + - @backstage/cli@0.27.0 + - @backstage/plugin-notifications@0.3.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/plugin-home@0.7.9 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-components@0.14.10 + - @backstage/plugin-api-docs@0.11.8 + - @backstage/frontend-app-api@0.8.0 + - @backstage/plugin-catalog-graph@0.4.8 + - @backstage/plugin-catalog-import@0.12.2 + - @backstage/plugin-devtools@0.1.17 + - @backstage/plugin-org@0.6.28 + - @backstage/plugin-search@1.4.15 + - @backstage/plugin-user-settings@0.8.11 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-kubernetes@0.11.13 + - @backstage/core-app-api@1.14.2 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-kubernetes-cluster@0.0.14 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-signals@0.0.9 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13 + - @backstage/plugin-techdocs-react@1.2.7 + +## example-app-next@0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/plugin-scaffolder@1.24.0 + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-scaffolder-react@1.11.0 + - @backstage/cli@0.27.0 + - @backstage/plugin-notifications@0.3.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/plugin-home@0.7.9 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-components@0.14.10 + - @backstage/plugin-api-docs@0.11.8 + - @backstage/frontend-app-api@0.8.0 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-app-visualizer@0.1.9 + - @backstage/plugin-catalog-graph@0.4.8 + - @backstage/plugin-catalog-import@0.12.2 + - @backstage/plugin-org@0.6.28 + - @backstage/plugin-search@1.4.15 + - @backstage/plugin-user-settings@0.8.11 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-kubernetes@0.11.13 + - @backstage/core-app-api@1.14.2 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-kubernetes-cluster@0.0.14 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-signals@0.0.9 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13 + - @backstage/plugin-techdocs-react@1.2.7 + +## app-next-example-plugin@0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + +## example-backend@0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.41 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-scaffolder-backend@1.24.0 + - @backstage/plugin-techdocs-backend@1.10.10 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.0 + - @backstage/plugin-search-backend-module-explore@0.2.0 + - @backstage/plugin-notifications-backend@0.3.4 + - @backstage/plugin-kubernetes-backend@0.18.4 + - @backstage/plugin-permission-backend@0.5.47 + - @backstage/plugin-devtools-backend@0.3.9 + - @backstage/plugin-signals-backend@0.1.9 + - @backstage/plugin-proxy-backend@0.5.4 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-app-backend@0.3.72 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.3.0 + - @backstage/plugin-search-backend-module-catalog@0.2.0 + - @backstage/plugin-search-backend@1.5.15 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.9 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20 + +## example-backend-legacy@0.2.101 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.40 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-scaffolder-backend@1.24.0 + - @backstage/plugin-techdocs-backend@1.10.10 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.0 + - @backstage/plugin-search-backend-module-explore@0.2.0 + - @backstage/plugin-kubernetes-backend@0.18.4 + - @backstage/plugin-permission-backend@0.5.47 + - @backstage/plugin-devtools-backend@0.3.9 + - @backstage/plugin-signals-backend@0.1.9 + - @backstage/plugin-proxy-backend@0.5.4 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-app-backend@0.3.72 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5 + - @backstage/plugin-search-backend-module-catalog@0.2.0 + - @backstage/plugin-search-backend@1.5.15 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/plugin-search-backend-module-pg@0.5.33 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10 + - @backstage/plugin-events-backend@0.3.10 + - @backstage/plugin-events-node@0.3.9 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.4 + - @backstage/plugin-signals-node@0.1.9 + +## e2e-test@0.2.19 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.18 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.99 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/cli@0.27.0 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/test-utils@1.5.10 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-react@1.2.7 + +## @internal/plugin-todo-list@1.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + +## @internal/plugin-todo-list-backend@1.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + +## @internal/plugin-todo-list-common@1.0.21 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 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.30.0-next.1-changelog.md b/docs/releases/v1.30.0-next.1-changelog.md new file mode 100644 index 0000000000..1d93235740 --- /dev/null +++ b/docs/releases/v1.30.0-next.1-changelog.md @@ -0,0 +1,2175 @@ +# Release v1.30.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.30.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.30.0-next.1) + +## @backstage/core-app-api@1.14.2-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/config-loader@1.9.0-next.1 + +### Minor Changes + +- 274428f: Add configuration key to File and Remote `ConfigSource`s that enables configuration of parsing logic. Previously limited to yaml, these `ConfigSource`s now allow for a multitude of parsing options (e.g. JSON). + +### Patch Changes + +- 1edd6c2: The `env` option of `ConfigSources.default` now correctly allows undefined members. +- 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-notifications-backend-module-email@0.2.0-next.1 + +### Minor Changes + +- def53a7: **BREAKING** Following `NotificationTemplateRenderer` methods now return a Promise and **must** be awaited: `getSubject`, `getText` and `getHtml`. + + Required changes and example usage: + + ```diff + import { notificationsEmailTemplateExtensionPoint } from '@backstage/plugin-notifications-backend-module-email'; + import { Notification } from '@backstage/plugin-notifications-common'; + +import { getNotificationSubject, getNotificationTextContent, getNotificationHtmlContent } from 'my-notification-processing-library` + export const notificationsModuleEmailDecorator = createBackendModule({ + pluginId: 'notifications', + moduleId: 'email.templates', + register(reg) { + reg.registerInit({ + deps: { + emailTemplates: notificationsEmailTemplateExtensionPoint, + }, + async init({ emailTemplates }) { + emailTemplates.setTemplateRenderer({ + - getSubject(notification) { + + async getSubject(notification) { + - return `New notification from ${notification.source}`; + + const subject = await getNotificationSubject(notification); + + return `New notification from ${subject}`; + }, + - getText(notification) { + + async getText(notification) { + - return notification.content; + + const text = await getNotificationTextContent(notification); + + return text; + }, + - getHtml(notification) { + + async getHtml(notification) { + - return `

${notification.content}

`; + + const html = await getNotificationHtmlContent(notification); + + return html; + }, + }); + }, + }); + }, + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration-aws-node@0.1.12 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.1 + +## @backstage/plugin-techdocs-common@0.1.0-next.0 + +### Minor Changes + +- 4698e1f: Initial release of the techdocs-common package. + +## @backstage/app-defaults@1.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## @backstage/backend-app-api@0.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @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.1 + +## @backstage/backend-common@0.23.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/backend-dev-utils@0.1.4 + - @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.1 + +## @backstage/backend-defaults@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-app-api@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/backend-dev-utils@0.1.4 + - @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.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/backend-dynamic-feature-service@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-app-api@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-app-node@0.1.23-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/backend-tasks@0.5.28-next.1 + - @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.1 + - @backstage/plugin-events-backend@0.3.10-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/backend-openapi-utils@0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/errors@1.2.4 + +## @backstage/backend-plugin-api@0.7.1-next.1 + +### Patch Changes + +- f011d1b: fix typo in `getPluginRequestToken` comments +- Updated dependencies + - @backstage/plugin-permission-common@0.8.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-auth-node@0.4.18-next.1 + +## @backstage/backend-tasks@0.5.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-app-api@0.8.1-next.1 + - @backstage/backend-defaults@0.4.2-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/cli@0.27.0-next.1 + +### Patch Changes + +- e6e7d86: Switched the target from `'ES2022'` to `'es2022'` for better compatibility with older versions of `swc`. +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/integration@1.14.0-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/core-compat-api@0.2.8-next.1 + +### Patch Changes + +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/create-app@0.5.18-next.1 + +### Patch Changes + +- 6c1081c: Updated dockerfile and `app-config.production.yaml` to make it easier to get started with example data +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/dev-utils@1.0.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.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.5-next.1 + +### Patch Changes + +- 3be9aeb: Added support for v2 extensions, which declare their inputs and outputs without using a data map. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @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/version-bridge@1.0.8 + +## @backstage/frontend-plugin-api@0.6.8-next.1 + +### Patch Changes + +- 3be9aeb: Extensions have been changed to be declared with an array of inputs and outputs, rather than a map of named data refs. This change was made to reduce confusion around the role of the input and output names, as well as enable more powerful APIs for overriding extensions. + + An extension that was previously declared like this: + + ```tsx + const exampleExtension = createExtension({ + name: 'example', + inputs: { + items: createExtensionInput({ + element: coreExtensionData.reactElement, + }), + }, + output: { + element: coreExtensionData.reactElement, + }, + factory({ inputs }) { + return { + element: ( +
+ Example + {inputs.items.map(item => { + return
{item.output.element}
; + })} +
+ ), + }; + }, + }); + ``` + + Should be migrated to the following: + + ```tsx + const exampleExtension = createExtension({ + name: 'example', + inputs: { + items: createExtensionInput([coreExtensionData.reactElement]), + }, + output: [coreExtensionData.reactElement], + factory({ inputs }) { + return [ + coreExtensionData.reactElement( +
+ Example + {inputs.items.map(item => { + return
{item.get(coreExtensionData.reactElement)}
; + })} +
, + ), + ]; + }, + }); + ``` + +- 3fb421d: Added support to be able to define `zod` config schema in Blueprints, with built in schema merging from the Blueprint and the extension instances. + +- 6349099: Added config input type to the extensions + +- 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.12-next.1 + +### Patch Changes + +- 3be9aeb: Added support for v2 extensions, which declare their inputs and outputs without using a data map. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-app-api@0.7.5-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/test-utils@1.5.10-next.1 + - @backstage/types@1.1.1 + +## @backstage/repo-tools@0.9.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @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.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.9-next.1 + - @backstage/backend-defaults@0.4.2-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## @backstage/test-utils@1.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + +## @backstage/plugin-api-docs@0.11.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-app-backend@0.3.72-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-app-node@0.1.23-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-app-node@0.1.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + +## @backstage/plugin-app-visualizer@0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-auth-backend@0.22.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.6-next.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4-next.1 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6-next.1 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18-next.1 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4-next.1 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15-next.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20-next.1 + - @backstage/plugin-auth-backend-module-google-provider@0.1.20-next.1 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4-next.1 + - @backstage/plugin-auth-backend-module-oidc-provider@0.2.4-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.16-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-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-azure-easyauth-provider@0.1.6-next.1 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-backend@0.22.10-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.6-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.20-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.2.4-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-backend@0.22.10-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.16-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.2.4-next.1 + +### Patch Changes + +- c8f1cae: Add `signIn` to authentication provider configuration schema +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-auth-node@0.4.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-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.2-next.1 + +### Patch Changes + +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-scaffolder-common@1.5.5-next.0 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-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-backend@1.24.1-next.1 + +### Patch Changes + +- 51240ee: Preserve default `allowedLocationTypes` when `setAllowedLocationTypes()` of `CatalogLocationsExtensionPoint` is not called. +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.1 + - @backstage/backend-openapi-utils@0.1.16-next.1 + - @backstage/backend-tasks@0.5.28-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-events-node@0.3.9-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.0 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-azure@0.1.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-openapi-utils@0.1.16-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @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.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @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-events-node@0.3.9-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @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.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.0 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-github@0.6.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-catalog-backend-module-github@0.6.6-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.1 + +### Patch Changes + +- c7b14ed: Adds new optional `excludeRepos` configuration option to the Gitlab catalog provider. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-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-backend-module-logs@0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-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-backend-module-scaffolder-entity-model@0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.0 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-catalog-common@1.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-catalog-graph@0.4.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.12.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10-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-node@1.12.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-catalog-common@1.0.26-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-react@1.12.3-next.1 + +### Patch Changes + +- 7ca331c: Correct `EntityDisplayName`'s icon alignment with the text. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-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/version-bridge@1.0.8 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + +## @backstage/plugin-devtools@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-devtools-common@0.1.12-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-devtools-backend@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-devtools-common@0.1.12-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.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-events-backend@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-events-backend-module-azure@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-events-backend-module-github@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-events-backend-module-gitlab@0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-events-backend-test-utils@0.1.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-events-node@0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + +## @backstage/plugin-home@0.7.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-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-kubernetes@0.11.13-next.1 + +### Patch Changes + +- e6c15cc: Adds support for Backstage's new frontend system, available via the `/alpha` sub-path export. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-react@0.4.2-next.1 + +## @backstage/plugin-kubernetes-backend@0.18.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-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.1 + - @backstage/plugin-kubernetes-node@0.1.17-next.1 + +## @backstage/plugin-kubernetes-cluster@0.0.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-react@0.4.2-next.1 + +## @backstage/plugin-kubernetes-common@0.8.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-node@0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-react@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.8.2-next.0 + - @backstage/catalog-model@1.5.0 + - @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-notifications-backend@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-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.18-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.1 + - @backstage/plugin-signals-node@0.1.9-next.1 + +## @backstage/plugin-notifications-node@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @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.1 + +## @backstage/plugin-org@0.6.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-org-react@0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-permission-backend@0.5.47-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-permission-common@0.8.1-next.0 + +### Patch Changes + +- 137fa34: Add the MetadataResponseSerializedRule type from @backstage/plugin-permission-node, since this type might be used in frontend code. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-permission-node@0.8.1-next.1 + +### Patch Changes + +- 5cd9878: The MetadataResponseSerializedRule type has been moved to @backstage/plugin-permission-common, and should be imported from there going forward. To avoid an immediate breaking change, this type is still re-exported from this package, but is marked as deprecated and will be removed in a future release. +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @backstage/plugin-permission-react@0.4.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-proxy-backend@0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder@1.23.1-next.1 + +### Patch Changes + +- 896a22d: Fix helper text margin for scaffolder EntityNamePicker and EntityTagsPicker when using outlined text field +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-scaffolder-common@1.5.5-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-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-scaffolder-backend@1.23.1-next.1 + +### Patch Changes + +- ef87e06: Fix scaffolder action `catalog:write` to write to directories that don't already exist +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.1 + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.1 + - @backstage/backend-tasks@0.5.28-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-node@0.4.18-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-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.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.1 + +### Patch Changes + +- e3b64be: Added examples for publish:bitbucketServer action and improve its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.47-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-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.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.1 + +### Patch Changes + +- 24de005: Added test cases for publish:gitea examples +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.1 + +### Patch Changes + +- d21d307: Added examples for github:environment:create action and improve its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-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.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.1 + +## @backstage/plugin-scaffolder-common@1.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node@0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.5-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.1 + - @backstage/backend-test-utils@0.4.5-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## @backstage/plugin-scaffolder-react@1.11.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-scaffolder-common@1.5.5-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.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@1.4.15-next.1 + +### Patch Changes + +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @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/version-bridge@1.0.8 + +## @backstage/plugin-search-backend@1.5.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/backend-defaults@0.4.2-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/backend-openapi-utils@0.1.16-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-search-backend-module-catalog@0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/backend-tasks@0.5.28-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-search-backend-module-elasticsearch@1.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-explore@0.1.29-next.1 + +### Patch Changes + +- 93fc1a0: Updated dependency `@backstage-community/plugin-explore-common` to `^0.0.4`. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-pg@0.5.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-techdocs@0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.9-next.1 + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-node@1.2.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-search-common@1.2.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-search-react@1.7.14-next.1 + +### Patch Changes + +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/plugin-search-common@1.2.14-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-signals-backend@0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + - @backstage/plugin-signals-node@0.1.9-next.1 + +## @backstage/plugin-signals-node@0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## @backstage/plugin-techdocs@1.10.8-next.1 + +### Patch Changes + +- 69bd940: Use annotation constants from new techdocs-common package. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/plugin-techdocs-common@0.1.0-next.0 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10-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-auth-react@0.1.5-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.8-next.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/test-utils@1.5.10-next.1 + - @backstage/core-app-api@1.14.2-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-backend@1.10.10-next.1 + +### Patch Changes + +- 69bd940: Use annotation constants from new techdocs-common package. +- Updated dependencies + - @backstage/plugin-techdocs-node@1.12.9-next.1 + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-techdocs-common@0.1.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-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-techdocs-node@1.12.9-next.1 + +### Patch Changes + +- 69bd940: Use annotation constants from new techdocs-common package. +- 949083d: Update `patchMkdocsYmlPrebuild` to modify `repo_url` and `edit_uri` independently. +- Updated dependencies + - @backstage/plugin-techdocs-common@0.1.0-next.0 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-user-settings@0.8.11-next.1 + +### Patch Changes + +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/core-app-api@1.14.2-next.0 + - @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-signals-react@0.0.4 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + - @backstage/plugin-signals-node@0.1.9-next.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.100-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.8-next.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-app-api@0.7.5-next.1 + - @backstage/plugin-kubernetes@0.11.13-next.1 + - @backstage/cli@0.27.0-next.1 + - @backstage/plugin-scaffolder@1.23.1-next.1 + - @backstage/plugin-user-settings@0.8.11-next.1 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/plugin-search@1.4.15-next.1 + - @backstage/plugin-api-docs@0.11.8-next.1 + - @backstage/plugin-catalog-graph@0.4.8-next.1 + - @backstage/plugin-catalog-import@0.12.2-next.1 + - @backstage/plugin-home@0.7.9-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.1 + - @backstage/plugin-org@0.6.28-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/plugin-devtools@0.1.17-next.1 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## example-app-next@0.0.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.8-next.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-app-api@0.7.5-next.1 + - @backstage/plugin-kubernetes@0.11.13-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/cli@0.27.0-next.1 + - @backstage/plugin-scaffolder@1.23.1-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-user-settings@0.8.11-next.1 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/plugin-search@1.4.15-next.1 + - @backstage/plugin-api-docs@0.11.8-next.1 + - @backstage/plugin-catalog-graph@0.4.8-next.1 + - @backstage/plugin-catalog-import@0.12.2-next.1 + - @backstage/plugin-home@0.7.9-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.1 + - @backstage/plugin-org@0.6.28-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/plugin-app-visualizer@0.1.9-next.1 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## app-next-example-plugin@0.0.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-components@0.14.10-next.0 + +## example-backend@0.0.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.1 + - @backstage/plugin-techdocs-backend@1.10.10-next.1 + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-scaffolder-backend@1.23.1-next.1 + - @backstage/plugin-auth-backend@0.22.10-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.1 + - @backstage/backend-defaults@0.4.2-next.1 + - @backstage/plugin-app-backend@0.3.72-next.1 + - @backstage/plugin-devtools-backend@0.3.9-next.1 + - @backstage/plugin-proxy-backend@0.5.4-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.1 + - @backstage/plugin-kubernetes-backend@0.18.4-next.1 + - @backstage/plugin-permission-backend@0.5.47-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.1 + - @backstage/plugin-search-backend@1.5.15-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.1 + - @backstage/plugin-notifications-backend@0.3.4-next.1 + - @backstage/plugin-signals-backend@0.1.9-next.1 + +## example-backend-legacy@0.2.101-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@1.10.10-next.1 + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.1 + - @backstage/plugin-scaffolder-backend@1.23.1-next.1 + - @backstage/plugin-auth-backend@0.22.10-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.1 + - example-app@0.2.100-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-app-backend@0.3.72-next.1 + - @backstage/plugin-devtools-backend@0.3.9-next.1 + - @backstage/plugin-proxy-backend@0.5.4-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-kubernetes-backend@0.18.4-next.1 + - @backstage/plugin-permission-backend@0.5.47-next.1 + - @backstage/plugin-search-backend@1.5.15-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-auth-node@0.4.18-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.1 + - @backstage/plugin-events-backend@0.3.10-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.33-next.1 + - @backstage/plugin-signals-backend@0.1.9-next.1 + - @backstage/plugin-signals-node@0.1.9-next.1 + +## e2e-test@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.18-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.99-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.8-next.1 + - @backstage/cli@0.27.0-next.1 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/test-utils@1.5.10-next.1 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## @internal/plugin-todo-list-backend@1.0.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## @internal/plugin-todo-list-common@1.0.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 diff --git a/docs/releases/v1.30.0-next.2-changelog.md b/docs/releases/v1.30.0-next.2-changelog.md new file mode 100644 index 0000000000..cdf4164566 --- /dev/null +++ b/docs/releases/v1.30.0-next.2-changelog.md @@ -0,0 +1,156 @@ +# Release v1.30.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.30.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.30.0-next.2) + +## @backstage/cli@0.27.0-next.2 + +### Patch Changes + +- b2d97fd: Fixing loading of additional config files with new `ConfigSources` +- adabb40: New command now supports setting package license +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.1 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.14.0-next.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/create-app@0.5.18-next.2 + +### Patch Changes + +- Bumped create-app version. +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/plugin-catalog-graph@0.4.8-next.2 + +### Patch Changes + +- 4a529c2: Use `entityPresentationApi` for the node title and the icon. +- Updated dependencies + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + +## example-app@0.2.100-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.2 + - @backstage/plugin-catalog-graph@0.4.8-next.2 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/frontend-app-api@0.7.5-next.1 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.8-next.1 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-import@0.12.2-next.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-devtools@0.1.17-next.1 + - @backstage/plugin-home@0.7.9-next.1 + - @backstage/plugin-kubernetes@0.11.13-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.1 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-org@0.6.28-next.1 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-scaffolder@1.23.1-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.1 + - @backstage/plugin-search@1.4.15-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs@1.10.8-next.1 + - @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.11-next.1 + +## example-app-next@0.0.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.2 + - @backstage/plugin-catalog-graph@0.4.8-next.2 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/frontend-app-api@0.7.5-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.8-next.1 + - @backstage/plugin-app-visualizer@0.1.9-next.1 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-import@0.12.2-next.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-home@0.7.9-next.1 + - @backstage/plugin-kubernetes@0.11.13-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.1 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-org@0.6.28-next.1 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-scaffolder@1.23.1-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.1 + - @backstage/plugin-search@1.4.15-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs@1.10.8-next.1 + - @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.11-next.1 + +## e2e-test@0.2.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.18-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.99-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.2 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/test-utils@1.5.10-next.1 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/plugin-techdocs@1.10.8-next.1 + - @backstage/plugin-techdocs-react@1.2.7-next.0 diff --git a/docs/releases/v1.30.0-next.3-changelog.md b/docs/releases/v1.30.0-next.3-changelog.md new file mode 100644 index 0000000000..4fa86da821 --- /dev/null +++ b/docs/releases/v1.30.0-next.3-changelog.md @@ -0,0 +1,2199 @@ +# Release v1.30.0-next.3 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.30.0-next.3](https://backstage.github.io/upgrade-helper/?to=1.30.0-next.3) + +## @backstage/backend-plugin-api@0.8.0-next.2 + +### Minor Changes + +- 7c5f3b0: The `createServiceRef` function now accepts a new boolean `multiple` option. The `multiple` option defaults to `false` and when set to `true`, it enables that multiple implementation are installed for the created service ref. + + We're looking for ways to make it possible to augment services without the need to replace the entire service. + + Typical example of that being the ability to install support for additional targets for the `UrlReader` service without replacing the service itself. This achieves that by allowing us to define services that can have multiple simultaneous implementation, allowing the `UrlReader` implementation to depend on such a service to collect all possible implementation of support for external targets: + + ```diff + // @backstage/backend-defaults + + + export const urlReaderFactoriesServiceRef = createServiceRef({ + + id: 'core.urlReader.factories', + + scope: 'plugin', + + multiton: true, + + }); + + ... + + export const urlReaderServiceFactory = createServiceFactory({ + service: coreServices.urlReader, + deps: { + config: coreServices.rootConfig, + logger: coreServices.logger, + + factories: urlReaderFactoriesServiceRef, + }, + - async factory({ config, logger }) { + + async factory({ config, logger, factories }) { + return UrlReaders.default({ + config, + logger, + + factories, + }); + }, + }); + ``` + + With that, you can then add more custom `UrlReader` factories by installing more implementations of the `urlReaderFactoriesServiceRef` in your backend instance. Something like: + + ```ts + // packages/backend/index.ts + import { createServiceFactory } from '@backstage/backend-plugin-api'; + import { urlReaderFactoriesServiceRef } from '@backstage/backend-defaults'; + ... + + backend.add(createServiceFactory({ + service: urlReaderFactoriesServiceRef, + deps: {}, + async factory() { + return CustomUrlReader.factory; + }, + })); + + ... + + ``` + +### Patch Changes + +- 6061061: Added `createBackendFeatureLoader`, which can be used to create an installable backend feature that can in turn load in additional backend features in a dynamic way. + +- ba9abf4: The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. + +- 8b13183: Added `createBackendFeatureLoader`, which can be used to programmatically select and install backend features. + + A feature loader can return an list of features to be installed, for example in the form on an `Array` or other for of iterable, which allows for the loader to be defined as a generator function. Both synchronous and asynchronous loaders are supported. + + Additionally, a loader can depend on services in its implementation, with the restriction that it can only depend on root-scoped services, and it may not override services that have already been instantiated. + + ```ts + const searchLoader = createBackendFeatureLoader({ + deps: { + config: coreServices.rootConfig, + }, + *loader({ config }) { + // Example of a custom config flag to enable search + if (config.getOptionalString('customFeatureToggle.search')) { + yield import('@backstage/plugin-search-backend/alpha'); + yield import('@backstage/plugin-search-backend-module-catalog/alpha'); + yield import('@backstage/plugin-search-backend-module-explore/alpha'); + yield import('@backstage/plugin-search-backend-module-techdocs/alpha'); + } + }, + }); + ``` + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/frontend-plugin-api@0.7.0-next.2 + +### Minor Changes + +- 72754db: **BREAKING**: All types of route refs are always considered optional by `useRouteRef`, which means the caller must always handle a potential `undefined` return value. Related to this change, the `optional` option from `createExternalRouteRef` has been removed, since it is no longer necessary. + + This is released as an immediate breaking change as we expect the usage of the new route refs to be extremely low or zero, since plugins that support the new system will still use route refs and `useRouteRef` from `@backstage/core-plugin-api` in combination with `convertLegacyRouteRef` from `@backstage/core-compat-api`. + +### Patch Changes + +- 210d066: Added support for using the `params` in other properties of the `createExtensionBlueprint` options by providing a callback. +- 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/plugin-auth-node@0.5.0-next.2 + +### Minor Changes + +- 579afd0: **BREAKING**: Sign-in resolvers configured via `.signIn.resolvers` now take precedence over sign-in resolvers passed to `signInResolver` option of `createOAuthProviderFactory`. This effectively makes sign-in resolvers passed via the `signInResolver` the default one, which you can then override through configuration. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @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.22.0-next.2 + +### Minor Changes + +- 6925dcb: Introduces the HasSubdomainsCard component that displays the subdomains of a given domain + +### Patch Changes + +- 604a504: The entity relation cards available for the new frontend system via `/alpha` now have more accurate and granular default filters. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-search-react@1.7.14-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-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-scaffolder@1.24.0-next.2 + +### Minor Changes + +- 3fca643: Added field extension `RepoBranchPicker` that supports autocompletion for Bitbucket + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.11.0-next.2 + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-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/app-defaults@1.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## @backstage/backend-app-api@0.8.1-next.2 + +### Patch Changes + +- 8b13183: Added support for the latest version of `BackendFeature`s from `@backstage/backend-plugin-api`, including feature loaders. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 7c5f3b0: Update the `ServiceRegister` implementation to enable registering multiple service implementations for a given service ref. +- 80a0737: Added configuration for the `packages` options to config schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/cli-node@0.2.7 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-common@0.23.4-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.4.2-next.2 + +### Patch Changes + +- 0d16b52: Add access restrictions to the JWKS external access method config schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- ba9abf4: The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. +- 7c5f3b0: Update the `UrlReader` service to depends on multiple instances of `UrlReaderFactoryProvider` service. +- 1d5f298: Avoid excessive numbers of error listeners on cache clients +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-app-api@0.8.1-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-dynamic-feature-service@0.2.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-app-api@0.8.1-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-app-node@0.1.23-next.2 + - @backstage/plugin-events-backend@0.3.10-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/cli-node@0.2.7 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-openapi-utils@0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/errors@1.2.4 + +## @backstage/backend-tasks@0.5.28-next.2 + +### Patch Changes + +- ba9abf4: The `PluginTaskScheduler` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `PluginTaskScheduler.triggerTask`. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.4.5-next.2 + +### Patch Changes + +- 8b13183: Internal updates to support latest version of `BackendFeauture`s from `@backstage/backend-plugin-api`. +- 7c5f3b0: Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation. +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.2 + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-app-api@0.8.1-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/cli@0.27.0-next.3 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/config-loader@1.9.0-next.2 + - @backstage/cli-node@0.2.7 + - @backstage/integration@1.14.0-next.0 + - @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 + +## @backstage/config-loader@1.9.0-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 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-compat-api@0.2.8-next.2 + +### Patch Changes + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- 16cf96c: Both `compatWrapper` and `convertLegacyRouteRef` now support converting from the new system to the old. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/create-app@0.5.18-next.3 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/dev-utils@1.0.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.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.5-next.2 + +### Patch Changes + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @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/version-bridge@1.0.8 + +## @backstage/frontend-test-utils@0.1.12-next.2 + +### Patch Changes + +- 8209449: Added new APIs for testing extensions +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/frontend-app-api@0.7.5-next.2 + - @backstage/test-utils@1.5.10-next.2 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/repo-tools@0.9.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/cli-node@0.2.7 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.2 + - @backstage/plugin-techdocs-node@1.12.9-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## @backstage/test-utils@1.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/types@1.1.1 + +## @backstage/plugin-api-docs@0.11.8-next.2 + +### Patch Changes + +- 4b6d2cb: Updated dependency `@graphiql/react` to `^0.23.0`. +- Updated dependencies + - @backstage/plugin-catalog@1.22.0-next.2 + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-app-backend@0.3.72-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-app-node@0.1.23-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-app-node@0.1.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/config-loader@1.9.0-next.2 + +## @backstage/plugin-app-visualizer@0.1.9-next.2 + +### Patch Changes + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-auth-backend@0.22.10-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.6-next.2 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18-next.2 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4-next.2 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.2 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20-next.2 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4-next.2 + - @backstage/plugin-auth-backend-module-oidc-provider@0.2.4-next.2 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.16-next.2 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4-next.2 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6-next.2 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18-next.2 + - @backstage/plugin-auth-backend-module-google-provider@0.1.20-next.2 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16-next.2 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-backend@0.22.10-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.6-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.2.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-backend@0.22.10-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/config@1.2.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.2.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + +### Patch Changes + +- 3fca643: Added method `listBranchesByRepository` to `BitbucketCloudClient` +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + +## @backstage/plugin-catalog-backend@1.24.1-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/backend-openapi-utils@0.1.16-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @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/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-aws@0.3.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-azure@0.1.43-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/backend-openapi-utils@0.1.16-next.2 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @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/plugin-catalog-backend-module-bitbucket-server@0.1.37-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/integration@1.14.0-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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.40-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-github@0.6.6-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @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/plugin-catalog-backend-module-github-org@0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-catalog-backend-module-github@0.6.6-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-ldap@0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-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-backend-module-logs@0.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.31-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.29-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @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.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.1 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-common@1.0.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/catalog-model@1.5.0 + +## @backstage/plugin-catalog-graph@0.4.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.12.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @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-components@0.14.10-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-node@1.12.5-next.2 + +### Patch Changes + +- 7c5f3b0: Explicit declare if the service ref accepts `single` or `multiple` implementations. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-catalog-common@1.0.26-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-react@1.12.3-next.2 + +### Patch Changes + +- 012e3eb: Entity page extensions created for the new frontend system via the `/alpha` exports will now be enabled by default. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-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/version-bridge@1.0.8 + +## @backstage/plugin-catalog-unprocessed-entities-common@0.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + +## @backstage/plugin-devtools@0.1.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-devtools-common@0.1.12-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-devtools-backend@0.3.9-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-devtools-common@0.1.12-next.1 + - @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.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-events-backend@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-events-backend-module-azure@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + +## @backstage/plugin-events-backend-module-gerrit@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + +## @backstage/plugin-events-backend-module-github@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + +## @backstage/plugin-events-backend-module-gitlab@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + +## @backstage/plugin-events-backend-test-utils@0.1.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.9-next.2 + +## @backstage/plugin-events-node@0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + +## @backstage/plugin-home@0.7.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-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-kubernetes@0.11.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-react@0.4.2-next.2 + +## @backstage/plugin-kubernetes-backend@0.18.4-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 8c1aa06: Add `kubernetes.clusterLocatorMethods[].clusters[].customResources` to the configuration schema. + This was already documented and supported by the plugin. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-kubernetes-node@0.1.17-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/integration-aws-node@0.1.12 + - @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-kubernetes-cluster@0.0.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-react@0.4.2-next.2 + +## @backstage/plugin-kubernetes-common@0.8.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-node@0.1.17-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-react@0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/catalog-model@1.5.0 + - @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-notifications-backend@0.3.4-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-notifications-node@0.2.4-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-signals-node@0.1.9-next.2 + - @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-common@0.0.5 + +## @backstage/plugin-notifications-backend-module-email@0.2.0-next.2 + +### Patch Changes + +- cdb630d: Add support for stream transport for debugging purposes +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-notifications-node@0.2.4-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/integration-aws-node@0.1.12 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + +## @backstage/plugin-notifications-node@0.2.4-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-signals-node@0.1.9-next.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-notifications-common@0.0.5 + +## @backstage/plugin-org@0.6.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-org-react@0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-permission-backend@0.5.47-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + +## @backstage/plugin-permission-common@0.8.1-next.1 + +### Patch Changes + +- df784fe: Add the MetadataResponse type from @backstage/plugin-permission-node, since this + type might be used in frontend code. +- Updated dependencies + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-permission-node@0.8.1-next.2 + +### Patch Changes + +- df784fe: The MetadataResponse type has been moved to @backstage/plugin-permission-common + to match the recent move of MetadataResponseSerializedRule, and should be + imported from there going forward. To avoid an immediate breaking change, this + type is still re-exported from this package, but is marked as deprecated and + will be removed in a future release. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-permission-react@0.4.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-proxy-backend@0.5.4-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend@1.23.1-next.2 + +### Patch Changes + +- c544f81: Add support for status filtering in scaffolder tasks endpoint +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.2 + - @backstage/plugin-scaffolder-common@1.5.5-next.1 + - @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/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 3fca643: Added autocompletion support for resource `branches` +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.47-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/plugin-notifications-node@0.2.4-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-notifications-common@0.0.5 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.31-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 382e868: Added test cases for sentry:project:create examples +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.2 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-common@1.5.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node@0.4.9-next.2 + +### Patch Changes + +- c544f81: Add support for status filtering in scaffolder tasks endpoint +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-scaffolder-common@1.5.5-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@0.4.5-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-react@1.11.0-next.2 + +### Patch Changes + +- 072c00c: Fixed a bug in `DefaultTableOutputs` where output elements overlapped on smaller screen sizes +- 04759f2: Fix null check in `isJsonObject` utility function for scaffolder review state component +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.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@1.4.15-next.2 + +### Patch Changes + +- 3123c16: Fix package metadata +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-search-react@1.7.14-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @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/version-bridge@1.0.8 + +## @backstage/plugin-search-backend@1.5.15-next.2 + +### Patch Changes + +- 3123c16: Fix package metadata +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.2 + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/backend-openapi-utils@0.1.16-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-search-backend-module-catalog@0.1.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-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-search-backend-module-elasticsearch@1.5.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/integration-aws-node@0.1.12 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-explore@0.1.29-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-pg@0.5.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.16-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-module-techdocs@0.1.28-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-techdocs-node@1.12.9-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-search-backend-node@1.2.28-next.2 + +### Patch Changes + +- 3123c16: Fix package metadata +- 7c5f3b0: Explicit declare if the service ref accepts `single` or `multiple` implementations. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-search-common@1.2.14-next.1 + +### Patch Changes + +- 3123c16: Fix package metadata +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/types@1.1.1 + +## @backstage/plugin-search-react@1.7.14-next.2 + +### Patch Changes + +- 3123c16: Fix package metadata +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @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-signals-backend@0.1.9-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-signals-node@0.1.9-next.2 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-signals-node@0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-techdocs@1.10.8-next.2 + +### Patch Changes + +- 67e76f2: TechDocs now supports the `mkdocs-redirects` plugin. Redirects defined using the `mkdocs-redirect` plugin will be handled automatically in TechDocs. Redirecting to external urls is not supported. In the case that an external redirect url is provided, TechDocs will redirect to the current documentation site home. +- bdc5471: Fixed issue where header styles were incorrectly generated when themes used CSS variables to define font size. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-search-react@1.7.14-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10-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-auth-react@0.1.5-next.0 + - @backstage/plugin-techdocs-common@0.1.0-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0-next.2 + - @backstage/plugin-search-react@1.7.14-next.2 + - @backstage/plugin-techdocs@1.10.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/test-utils@1.5.10-next.2 + - @backstage/core-app-api@1.14.2-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-backend@1.10.10-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-techdocs-node@1.12.9-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @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/errors@1.2.4 + - @backstage/plugin-techdocs-common@0.1.0-next.0 + +## @backstage/plugin-techdocs-node@1.12.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-techdocs-common@0.1.0-next.0 + +## @backstage/plugin-user-settings@0.8.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/core-app-api@1.14.2-next.0 + - @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-signals-react@0.0.4 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.2.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-signals-node@0.1.9-next.2 + - @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.100-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.11.0-next.2 + - @backstage/plugin-catalog@1.22.0-next.2 + - @backstage/cli@0.27.0-next.3 + - @backstage/frontend-app-api@0.7.5-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-search-react@1.7.14-next.2 + - @backstage/plugin-search@1.4.15-next.2 + - @backstage/plugin-api-docs@0.11.8-next.2 + - @backstage/plugin-techdocs@1.10.8-next.2 + - @backstage/plugin-scaffolder@1.24.0-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-graph@0.4.8-next.3 + - @backstage/plugin-catalog-import@0.12.2-next.2 + - @backstage/plugin-org@0.6.28-next.2 + - @backstage/plugin-user-settings@0.8.11-next.2 + - @backstage/plugin-devtools@0.1.17-next.2 + - @backstage/plugin-home@0.7.9-next.2 + - @backstage/plugin-kubernetes@0.11.13-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.2 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## example-app-next@0.0.14-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.11.0-next.2 + - @backstage/plugin-catalog@1.22.0-next.2 + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/cli@0.27.0-next.3 + - @backstage/frontend-app-api@0.7.5-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-app-visualizer@0.1.9-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-search-react@1.7.14-next.2 + - @backstage/plugin-search@1.4.15-next.2 + - @backstage/plugin-api-docs@0.11.8-next.2 + - @backstage/plugin-techdocs@1.10.8-next.2 + - @backstage/plugin-scaffolder@1.24.0-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-graph@0.4.8-next.3 + - @backstage/plugin-catalog-import@0.12.2-next.2 + - @backstage/plugin-org@0.6.28-next.2 + - @backstage/plugin-user-settings@0.8.11-next.2 + - @backstage/plugin-home@0.7.9-next.2 + - @backstage/plugin-kubernetes@0.11.13-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.2 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## app-next-example-plugin@0.0.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-components@0.14.10-next.0 + +## example-backend@0.0.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.2 + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-backend@1.23.1-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.2 + - @backstage/plugin-notifications-backend@0.3.4-next.2 + - @backstage/plugin-kubernetes-backend@0.18.4-next.2 + - @backstage/plugin-permission-backend@0.5.47-next.2 + - @backstage/plugin-devtools-backend@0.3.9-next.2 + - @backstage/plugin-techdocs-backend@1.10.10-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/plugin-signals-backend@0.1.9-next.2 + - @backstage/plugin-proxy-backend@0.5.4-next.2 + - @backstage/plugin-auth-backend@0.22.10-next.2 + - @backstage/plugin-app-backend@0.3.72-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-backend@1.5.15-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.2 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.2 + - @backstage/catalog-model@1.5.0 + +## example-backend-legacy@0.2.101-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.23.1-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.2 + - @backstage/plugin-kubernetes-backend@0.18.4-next.2 + - @backstage/plugin-permission-backend@0.5.47-next.2 + - @backstage/plugin-devtools-backend@0.3.9-next.2 + - @backstage/plugin-techdocs-backend@1.10.10-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/plugin-signals-backend@0.1.9-next.2 + - @backstage/plugin-proxy-backend@0.5.4-next.2 + - @backstage/plugin-auth-backend@0.22.10-next.2 + - @backstage/plugin-app-backend@0.3.72-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-backend@1.5.15-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.2 + - @backstage/plugin-events-backend@0.3.10-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.33-next.2 + - @backstage/plugin-signals-node@0.1.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## e2e-test@0.2.19-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.18-next.3 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.99-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0-next.2 + - @backstage/cli@0.27.0-next.3 + - @backstage/plugin-techdocs@1.10.8-next.2 + - @backstage/test-utils@1.5.10-next.2 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## @internal/plugin-todo-list-backend@1.0.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/errors@1.2.4 + +## @internal/plugin-todo-list-common@1.0.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 diff --git a/docs/releases/v1.30.0-next.4-changelog.md b/docs/releases/v1.30.0-next.4-changelog.md new file mode 100644 index 0000000000..433b5f894c --- /dev/null +++ b/docs/releases/v1.30.0-next.4-changelog.md @@ -0,0 +1,2113 @@ +# Release v1.30.0-next.4 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.30.0-next.4](https://backstage.github.io/upgrade-helper/?to=1.30.0-next.4) + +## @backstage/catalog-model@1.6.0-next.0 + +### Minor Changes + +- 34fa803: Introduce an optional spec.type attribute on the Domain and System entity kinds + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.2.0-next.3 + +### Minor Changes + +- 75d026a: Support for Cloudflare Custom Headers and Custom Cookie Auth Name + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-notifications@0.3.0-next.1 + +### Minor Changes + +- 0410fc9: By default, set notification as read when opening snackbar or web notification link + +### Patch Changes + +- 80b84f7: Fixed issue with notification reloading on page change +- 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-scaffolder@1.24.0-next.3 + +### Minor Changes + +- 1552c33: Changed the way to display entities in `MyGroupsPicker` to use `entityPresentationApi` and make it consistent across scaffolder pickers + +### Patch Changes + +- 47ed51b: Add an extra bit of height to the EntityPicker dropdown to make it clear there are more options to select from, and to remove the scroll bar when there is less than 10 options +- d18f4eb: Fix undefined in the title of Scaffolder Runs on the page load +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.2 + - @backstage/plugin-scaffolder-react@1.11.0-next.3 + +## @backstage/backend-app-api@0.8.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## @backstage/backend-common@0.23.4-next.3 + +### Patch Changes + +- ddde5fe: Internal type refactor. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/backend-defaults@0.4.2-next.3 + +### Patch Changes + +- 81f930a: use formatted query to prevent chance of SQL-injection +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-app-api@0.8.1-next.3 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## @backstage/backend-dynamic-feature-service@0.2.16-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-app-api@0.8.1-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.23-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-events-backend@0.3.10-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + +## @backstage/backend-openapi-utils@0.1.16-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/errors@1.2.4 + +## @backstage/backend-plugin-api@0.8.0-next.3 + +### Patch Changes + +- ddde5fe: Fixed a type issue where plugin and modules depending on multiton services would not receive the correct type. +- 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.5.0-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## @backstage/backend-tasks@0.5.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.4.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-defaults@0.4.2-next.3 + - @backstage/backend-app-api@0.8.1-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/catalog-client@1.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/errors@1.2.4 + +## @backstage/cli@0.27.0-next.4 + +### Patch Changes + +- 6d898d8: Switched the `process` polyfill to use `require.resolve` for greater compatability. +- 2ced236: Updated dependency `@module-federation/enhanced` to `0.3.1` +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.14.0-next.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/core-compat-api@0.2.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/create-app@0.5.18-next.4 + +### Patch Changes + +- bfeba46: Included permission config and enabled it out of the box +- Updated dependencies + - @backstage/cli-common@0.1.14 + +## @backstage/dev-utils@1.0.37-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.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.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @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/version-bridge@1.0.8 + +## @backstage/frontend-plugin-api@0.7.0-next.3 + +### Patch Changes + +- 6f72c2b: Fixing issue with extension blueprints `inputs` merging. + +- 99abb6b: Support overriding of plugin extensions using the new `plugin.withOverrides` method. + + ```tsx + import homePlugin from '@backstage/plugin-home'; + + export default homePlugin.withOverrides({ + extensions: [ + homePage.getExtension('page:home').override({ + *factory(originalFactory) { + yield* originalFactory(); + yield coreExtensionData.reactElement(

My custom home page

); + }, + }), + ], + }); + ``` + +- a65cfc8: Add support for accessing extensions definitions provided by a plugin via `plugin.getExtension(...)`. For this to work the extensions must be defined using the v2 format, typically using an extension blueprint. + +- 34f1b2a: Support merging of `inputs` in extension blueprints, but stop merging `output`. In addition, the original factory in extension blueprints now returns a data container that both provides access to the returned data, but can also be forwarded as output. + +- 2d21599: Added support for being able to override extension definitions. + + ```tsx + const TestCard = EntityCardBlueprint.make({ + ... + }); + + TestCard.override({ + // override attachment points + attachTo: { id: 'something-else', input: 'overridden' }, + // extend the config schema + config: { + schema: { + newConfig: z => z.string().optional(), + } + }, + // override factory + *factory(originalFactory, { inputs, config }){ + const originalOutput = originalFactory(); + + yield coreExentsionData.reactElement( + + {originalOutput.get(coreExentsionData.reactElement)} + + ); + } + }); + + ``` + +- 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.12-next.3 + +### Patch Changes + +- 2d21599: Added support for being able to override extension definitions. + + ```tsx + const TestCard = EntityCardBlueprint.make({ + ... + }); + + TestCard.override({ + // override attachment points + attachTo: { id: 'something-else', input: 'overridden' }, + // extend the config schema + config: { + schema: { + newConfig: z => z.string().optional(), + } + }, + // override factory + *factory(originalFactory, { inputs, config }){ + const originalOutput = originalFactory(); + + yield coreExentsionData.reactElement( + + {originalOutput.get(coreExentsionData.reactElement)} + + ); + } + }); + + ``` + +- 264e10f: Deprecate existing `ExtensionCreators` in favour of their new Blueprint counterparts. + +- 264e10f: Refactor `.make` method on Blueprints into two different methods, `.make` and `.makeWithOverrides`. + + When using `createExtensionBlueprint` you can define parameters for the factory function, if you wish to take advantage of these parameters you should use `.make` when creating an extension instance of a Blueprint. If you wish to override more things other than the standard `attachTo`, `name`, `namespace` then you should use `.makeWithOverrides` instead. + + `.make` is reserved for simple creation of extension instances from Blueprints using higher level parameters, whereas `.makeWithOverrides` is lower level and you have more control over the final extension. + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/frontend-app-api@0.7.5-next.3 + - @backstage/config@1.2.0 + - @backstage/test-utils@1.5.10-next.2 + - @backstage/types@1.1.1 + +## @backstage/repo-tools@0.9.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.3 + - @backstage/plugin-techdocs-node@1.12.9-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + +## @backstage/plugin-api-docs@0.11.8-next.3 + +### Patch Changes + +- 6582799: Add `tableOptions` to all tables and additionally `title` to API tables. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/plugin-catalog@1.22.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-permission-react@0.4.25-next.1 + +## @backstage/plugin-app-backend@0.3.72-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.23-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-app-node@0.1.23-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config-loader@1.9.0-next.2 + +## @backstage/plugin-app-visualizer@0.1.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-auth-backend@0.22.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.2.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.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.3 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15-next.3 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6-next.3 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6-next.3 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18-next.3 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.3 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20-next.3 + - @backstage/plugin-auth-backend-module-google-provider@0.1.20-next.3 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18-next.3 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4-next.3 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16-next.3 + - @backstage/plugin-auth-backend-module-oidc-provider@0.2.4-next.3 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.16-next.3 + - @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.2.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.15-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-backend@0.22.10-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.2.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.16-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.2.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/plugin-auth-backend@0.22.10-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.16-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.1.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.2.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## @backstage/plugin-auth-node@0.5.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog@1.22.0-next.3 + +### Patch Changes + +- 6582799: Add `tableOptions` to all tables and additionally `title` to API tables. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/plugin-search-react@1.7.14-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/core-components@0.14.10-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.26-next.2 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + +## @backstage/plugin-catalog-backend@1.24.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-openapi-utils@0.1.16-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.3 + +## @backstage/plugin-catalog-backend-module-aws@0.3.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-kubernetes-common@0.8.2-next.2 + +## @backstage/plugin-catalog-backend-module-azure@0.1.43-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-openapi-utils@0.1.16-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.2.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.37-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.24-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-kubernetes-common@0.8.2-next.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.40-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## @backstage/plugin-catalog-backend-module-github@0.6.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-backend-module-github@0.6.6-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.7.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## @backstage/plugin-catalog-backend-module-logs@0.0.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.31-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.29-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-scaffolder-common@1.5.5-next.2 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4-next.1 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## @backstage/plugin-catalog-common@1.0.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-search-common@1.2.14-next.1 + +## @backstage/plugin-catalog-graph@0.4.8-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.12.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + +## @backstage/plugin-catalog-node@1.12.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## @backstage/plugin-catalog-react@1.12.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/core-components@0.14.10-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/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## @backstage/plugin-devtools@0.1.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-devtools-common@0.1.12-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + +## @backstage/plugin-devtools-backend@0.3.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.12-next.1 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## @backstage/plugin-events-backend@0.3.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-events-backend-module-aws-sqs@0.3.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-events-backend-module-azure@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-events-backend-module-gerrit@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-events-backend-module-github@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-events-backend-module-gitlab@0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-events-backend-test-utils@0.1.33-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-events-node@0.3.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + +## @backstage/plugin-home@0.7.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-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-kubernetes@0.11.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2-next.2 + - @backstage/plugin-kubernetes-react@0.4.2-next.3 + +## @backstage/plugin-kubernetes-backend@0.18.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.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.5.0-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-kubernetes-common@0.8.2-next.2 + - @backstage/plugin-kubernetes-node@0.1.17-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## @backstage/plugin-kubernetes-cluster@0.0.14-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2-next.2 + - @backstage/plugin-kubernetes-react@0.4.2-next.3 + +## @backstage/plugin-kubernetes-common@0.8.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## @backstage/plugin-kubernetes-node@0.1.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.2 + +## @backstage/plugin-kubernetes-react@0.4.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @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-kubernetes-common@0.8.2-next.2 + +## @backstage/plugin-notifications-backend@0.3.4-next.3 + +### Patch Changes + +- 7a05f50: Allow using notifications without users in the catalog +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.3 + - @backstage/plugin-signals-node@0.1.9-next.3 + +## @backstage/plugin-notifications-backend-module-email@0.2.0-next.3 + +### Patch Changes + +- 83faf24: Notification email processor supports allowing or denying specific email addresses from receiving notifications +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.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.3 + +## @backstage/plugin-notifications-node@0.2.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.9-next.3 + +## @backstage/plugin-org@0.6.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26-next.2 + +## @backstage/plugin-org-react@0.1.27-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## @backstage/plugin-permission-backend@0.5.47-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## @backstage/plugin-permission-node@0.8.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## @backstage/plugin-proxy-backend@0.5.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend@1.23.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.3 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.3 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.3 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.3 + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.3 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.3 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.3 + - @backstage/plugin-scaffolder-common@1.5.5-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.3 + +### Patch Changes + +- 187f583: Added examples for publish:azure action and updated its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.3 + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.3 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.3 + +### Patch Changes + +- d57967c: Add ability to set the initial commit message when initializing a repository using the scaffolder action. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.47-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.1.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.3 + +### Patch Changes + +- 6d4cb97: Added examples for github:repo:create action and improved test cases +- cd203f1: Added examples for action github:pages and improved its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.3 + +### Patch Changes + +- da97131: Added test cases for gitlab:issues:create examples +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.0.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.3 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.31-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + - @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.3 + +## @backstage/plugin-scaffolder-common@1.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## @backstage/plugin-scaffolder-node@0.4.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.2 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-test-utils@0.4.5-next.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## @backstage/plugin-scaffolder-react@1.11.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/catalog-client@1.6.6-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-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.2 + +## @backstage/plugin-search@1.4.15-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/plugin-search-react@1.7.14-next.3 + - @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/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.14-next.1 + +## @backstage/plugin-search-backend@1.5.15-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-defaults@0.4.2-next.3 + - @backstage/backend-openapi-utils@0.1.16-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + +## @backstage/plugin-search-backend-module-catalog@0.1.29-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + +## @backstage/plugin-search-backend-module-explore@0.1.29-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + +## @backstage/plugin-search-backend-module-pg@0.5.33-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.16-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + +## @backstage/plugin-search-backend-module-techdocs@0.1.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/plugin-techdocs-node@1.12.9-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + +## @backstage/plugin-search-backend-node@1.2.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-search-common@1.2.14-next.1 + +## @backstage/plugin-search-react@1.7.14-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @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.14-next.1 + +## @backstage/plugin-signals-backend@0.1.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-signals-node@0.1.9-next.3 + +## @backstage/plugin-signals-node@0.1.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## @backstage/plugin-techdocs@1.10.8-next.3 + +### Patch Changes + +- 27794d1: Allow for more granular control of TechDocsReaderPage styling. Theme overrides can now be provided to TechDocs without affecting the theme in other areas of Backstage. +- 8543e72: TechDocs redirect feature now includes a notification to the user before they are redirected. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/plugin-search-react@1.7.14-next.3 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-techdocs-common@0.1.0-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.37-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.8-next.3 + - @backstage/plugin-catalog@1.22.0-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/plugin-search-react@1.7.14-next.3 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/test-utils@1.5.10-next.2 + - @backstage/plugin-techdocs-react@1.2.7-next.1 + +## @backstage/plugin-techdocs-backend@1.10.10-next.3 + +### Patch Changes + +- a16632c: Update configuration schema to match actual behavior +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/plugin-techdocs-node@1.12.9-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.3 + - @backstage/plugin-techdocs-common@0.1.0-next.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.1 + +## @backstage/plugin-techdocs-node@1.12.9-next.3 + +### Patch Changes + +- 5cedd9f: Fix TechDocs Edit URL for nested docs +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-techdocs-common@0.1.0-next.0 + +## @backstage/plugin-techdocs-react@1.2.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-user-settings@0.8.11-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/core-app-api@1.14.2-next.0 + - @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-signals-react@0.0.4 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.2.22-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-signals-node@0.1.9-next.3 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.100-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.24.0-next.3 + - @backstage/plugin-notifications@0.3.0-next.1 + - @backstage/cli@0.27.0-next.4 + - @backstage/plugin-techdocs@1.10.8-next.3 + - @backstage/plugin-api-docs@0.11.8-next.3 + - @backstage/plugin-catalog@1.22.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/frontend-app-api@0.7.5-next.3 + - @backstage/plugin-catalog-graph@0.4.8-next.4 + - @backstage/plugin-catalog-import@0.12.2-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/plugin-devtools@0.1.17-next.3 + - @backstage/plugin-home@0.7.9-next.3 + - @backstage/plugin-kubernetes@0.11.13-next.3 + - @backstage/plugin-org@0.6.28-next.3 + - @backstage/plugin-search@1.4.15-next.3 + - @backstage/plugin-search-react@1.7.14-next.3 + - @backstage/plugin-user-settings@0.8.11-next.3 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.3 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.1 + - @backstage/plugin-techdocs-react@1.2.7-next.1 + +## example-app-next@0.0.14-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.24.0-next.3 + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/plugin-notifications@0.3.0-next.1 + - @backstage/cli@0.27.0-next.4 + - @backstage/plugin-techdocs@1.10.8-next.3 + - @backstage/plugin-api-docs@0.11.8-next.3 + - @backstage/plugin-catalog@1.22.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/frontend-app-api@0.7.5-next.3 + - @backstage/plugin-app-visualizer@0.1.9-next.3 + - @backstage/plugin-catalog-graph@0.4.8-next.4 + - @backstage/plugin-catalog-import@0.12.2-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/plugin-home@0.7.9-next.3 + - @backstage/plugin-kubernetes@0.11.13-next.3 + - @backstage/plugin-org@0.6.28-next.3 + - @backstage/plugin-search@1.4.15-next.3 + - @backstage/plugin-search-react@1.7.14-next.3 + - @backstage/plugin-user-settings@0.8.11-next.3 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.3 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.1 + - @backstage/plugin-techdocs-react@1.2.7-next.1 + +## app-next-example-plugin@0.0.14-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/core-components@0.14.10-next.0 + +## example-backend@0.0.29-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.3 + - @backstage/plugin-notifications-backend@0.3.4-next.3 + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-techdocs-backend@1.10.10-next.3 + - @backstage/backend-defaults@0.4.2-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-scaffolder-backend@1.23.1-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/plugin-app-backend@0.3.72-next.3 + - @backstage/plugin-auth-backend@0.22.10-next.3 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.3 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.3 + - @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.3 + - @backstage/plugin-devtools-backend@0.3.9-next.3 + - @backstage/plugin-kubernetes-backend@0.18.4-next.3 + - @backstage/plugin-permission-backend@0.5.47-next.3 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + - @backstage/plugin-proxy-backend@0.5.4-next.3 + - @backstage/plugin-search-backend@1.5.15-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.3 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.3 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.3 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-signals-backend@0.1.9-next.3 + +## example-backend-legacy@0.2.101-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.3 + - @backstage/plugin-techdocs-backend@1.10.10-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-scaffolder-backend@1.23.1-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-app-backend@0.3.72-next.3 + - @backstage/plugin-auth-backend@0.22.10-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-devtools-backend@0.3.9-next.3 + - @backstage/plugin-events-backend@0.3.10-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-kubernetes-backend@0.18.4-next.3 + - @backstage/plugin-permission-backend@0.5.47-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + - @backstage/plugin-proxy-backend@0.5.4-next.3 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.3 + - @backstage/plugin-search-backend@1.5.15-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.3 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.3 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.3 + - @backstage/plugin-search-backend-module-pg@0.5.33-next.3 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.3 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-signals-backend@0.1.9-next.3 + - @backstage/plugin-signals-node@0.1.9-next.3 + +## e2e-test@0.2.19-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.18-next.4 + - @backstage/cli-common@0.1.14 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.99-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.4 + - @backstage/plugin-techdocs@1.10.8-next.3 + - @backstage/plugin-catalog@1.22.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/test-utils@1.5.10-next.2 + - @backstage/theme@0.5.6 + - @backstage/plugin-techdocs-react@1.2.7-next.1 + +## @internal/plugin-todo-list-backend@1.0.30-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 diff --git a/docs/releases/v1.30.0.md b/docs/releases/v1.30.0.md new file mode 100644 index 0000000000..0d3ad1dd5b --- /dev/null +++ b/docs/releases/v1.30.0.md @@ -0,0 +1,117 @@ +--- +id: v1.30.0 +title: v1.30.0 +description: Backstage Release v1.30.0 +--- + +These are the release notes for the v1.30.0 release of [Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the amazing Backstage Community for the hard work in getting this release developed and done. + +## Highlights + +### New Frontend System - Plugin Adoption + +This release marks another big milestone for the New Frontend System. **We encourage all plugin owners to [add support for the new frontend system](https://backstage.io/docs/frontend-system/building-plugins/migrating) to their plugins.** + +At the end of last year in the [1.21 release](https://backstage.io/docs/releases/v1.21.0), we shipped the New Frontend System Alpha. It marked a more stable release of the new system, but we knew there was still much more work left to be done. Since then we have received valuable feedback and identified key areas of improvement. In particular around the creation of new extension kinds as well as overriding and testing of extensions. + +Over the summer months we’ve been working hard towards addressing this feedback and getting the New Frontend System in shape for us to be confident in encouraging broader adoption by plugins. For a summary of the changes you can check out the [1.30 migration documentation](https://backstage.io/docs/frontend-system/architecture/migrations#130), or can see the ongoing progress in the [meta issue](https://github.com/backstage/backstage/issues/19545). With this release comes some new features, deprecations and breaking changes in the `@backstage/frontend-app-api`, `@backstage/frontend-plugin-api`, and `@backstage/core-compat-api` packages.``` + +**Breaking**: + +- All types of route references in the New Frontend System are now optional. This means that all usages of `useRouteRef` in the new system might return `undefined`, and your code must be able to act accordingly. Code that uses the old system (which will be the vast majority of code at this point) is **not** affected by this. + +**New**: + +- Blueprints and `createExtensionBlueprint` as a replacement for extension creators. You will find that old extension creators are marked as deprecated, and point to the corresponding blueprints which have a superior developer experience! +- Ability to override individual extensions using `extension.override(...)`, as well as overriding extensions in a plugin using a combination of `plugin.withOverrides([...])` and `plugin.getExtension(id)` to replace individual extensions or add new ones. +- `createExtensionTester` supports `.get` and `.query` to directly access extension data and streamline tests decoupled from React, as well as a `.reactElement` shorthand for accessing any output React elements. +- A new set of utilities that can patch support for the new frontend system for a plugin that otherwise only supports the old one. The new `convertLegacyPlugin` is used to convert a plugin instance, while `convertLegacyPageExtension` with friends can convert extensions from the old system. + +**Deprecations**: + +- `createPlugin` has been renamed to `createFrontendPlugin` +- `createExtension` with object keys for `inputs` and `outputs` has been deprecated in favor of the array form. +- `configSchema` in `createExtension` has been replaced with `config.schema` which is a better alternative to declaring config for extensions without having to use `createSchemaFromZod`. +- Existing `dataRefs` should now embed the ID using the `.with` method. +- The `render` method on `createExtensionTester` has been deprecated in favor of composing `.reactElement` with `renderInTestApp`. + +### BREAKING: Backend System deprecations and removals + +- Almost all service factories in `@backstage/backend-app-api` were marked deprecated some time back - those are now removed. Please import them from their new homes in `@backstage/backend-defaults/` instead. +- In our effort to migrate to the new backend system some backend-plugins `createRouter` exports have been marked as deprecated. Please make sure to update your backends accordingly as `createRouter` will eventually be removed from all plugin exports. +- Several deprecated methods and types have been removed from backend related packages. Most of these are either renamed and re-exported from other packages, see the CHANGELOG for the individual package for more information. + +Most notably the long deprecated `UrlReader` exports have been renamed: + +- `ReadUrlOptions`: Use `UrlReaderServiceReadUrlOptions` instead; +- `ReadUrlResponse`: Use `UrlReaderServiceReadUrlResponse` instead; +- `ReadTreeOptions`: Use `UrlReaderServiceReadTreeOptions` instead; +- `ReadTreeResponse`: Use `UrlReaderServiceReadTreeResponse` instead; +- `ReadTreeResponseFile`: Use `UrlReaderServiceReadTreeResponseFile` instead; +- `ReadTreeResponseDirOptions`: Use `UrlReaderServiceReadTreeResponseDirOptions` instead; +- `SearchOptions`: Use `UrlReaderServiceSearchOptions` instead; +- `SearchResponse`: Use `UrlReaderServiceSearchResponse` instead; +- `SearchResponseFile`: Use `UrlReaderServiceSearchResponseFile` instead. + +### BREAKING: Auth Sign In Resolver Priority + +Sign-in resolvers configured via `.signIn.resolvers` in your app-config now take precedence over sign-in resolvers passed to `signInResolver` option of `createOAuthProviderFactory`. This effectively makes sign-in resolvers passed via the `signInResolver` the default ones, which you can then override through configuration, simplifying deploying the same code in multiple environments. + +### BREAKING: `@backstage/cli` + +The lockfile (`yarn.lock`) dependency analysis and mutations have been removed from several commands. This means that `versions:bump` will no longer attempt to deduplicate after bumping and modifying the lockfile. + +The `versions:check` command has also been removed as its only purpose was to verify and mutate the lockfile. We recommend using the `yarn dedupe` command instead, or the `yarn-deduplicate` package if you're using Yarn classic, as a replacement. This change was made in order for us to support other Package Managers in the future and remove the coupling with `yarn`. + +### BREAKING: `@backstage/backend-test-utils` + +- `setupRequestMockHandlers` is removed; use `registerMswTestHooks` instead. +- `MockDirectoryOptions` is removed; use `CreateMockDirectoryOptions` instead. +- Stopped exporting the deprecated and internal `isDockerDisabledForTests` helper. +- Removed `get` method from `ServiceFactoryTester` which is replaced by `getSubject` + +### Scaffolder Internationalization + +Thanks to [@mario-mui](https://github.com/mario-mui) we now have i18n support for another core feature! Contributed in [#25827](https://github.com/backstage/backstage/pull/25827) + +### Dynamic Backend Feature Loaders + +You can now use `createBackendFeatureLoader` to dynamically load features in the backend, for example based on runtime configuration, and many other exciting possibilities. Check out [the docs](https://backstage.io/docs/backend-system/architecture/feature-loaders)! + +## Security Fixes + +The AWS ALB auth provider now has a configuration option `signer`, which should be set to the ARN of your ALB instance. We strongly recommend that you set this configuration value, since it will help strengthen your installation. + +Example: + +```diff + auth: + providers: + awsalb: + issuer: ... + # put your actual ARN here ++ signer: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456' + region: ... + signIn: + resolvers: + - resolver: ... +``` + +## 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.30.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.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/auth-service-migration.md b/docs/tutorials/auth-service-migration.md index 9e2633a12a..d12b0d5906 100644 --- a/docs/tutorials/auth-service-migration.md +++ b/docs/tutorials/auth-service-migration.md @@ -24,6 +24,8 @@ backend: dangerouslyDisableDefaultAuthPolicy: true ``` +Please note that this functionality will be removed in a future release, and you should migrate to using the new auth services as soon as possible or you would have to support your own service for issuing tokens. + In short, this will allow requests through to plugins in your backend, even if they do not include any credentials. The requests will still be treated as unauthenticated however, which not all plugin endpoints may accept. For more information on the impact of this configuration, see the [auth service documentation](../backend-system/core-services/auth.md). ### Migrating the backend 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/setup-opentelemetry.md b/docs/tutorials/setup-opentelemetry.md index faaeab49f4..cba8ba7049 100644 --- a/docs/tutorials/setup-opentelemetry.md +++ b/docs/tutorials/setup-opentelemetry.md @@ -83,6 +83,28 @@ CMD ["node", "--require", "./instrumentation.js", "packages/backend", "--config" If you need to disable/configure some OpenTelemetry feature there are lots of [environment variables](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/) which you can tweak. +### Available Metrics + +The following metrics are available: + +- `catalog_entities_count`: Total amount of entities in the catalog +- `catalog_registered_locations_count`: Total amount of registered locations in the catalog +- `catalog_relations_count`: Total amount of relations between entities +- `catalog.processed.entities.count`: Amount of entities processed +- `catalog.processing.duration`: Time spent executing the full processing flow +- `catalog.processors.duration`: Time spent executing catalog processors +- `catalog.processing.queue.delay`: The amount of delay between being scheduled for processing, and the start of actually being processed +- `catalog.stitched.entities.count`: Amount of entities stitched +- `catalog.stitching.duration`: Time spent executing the full stitching flow +- `catalog.stitching.queue.length`: Number of entities currently in the stitching queue +- `catalog.stitching.queue.delay`: The amount of delay between being scheduled for stitching, and the start of actually being stitched +- `scaffolder.task.count`: Count of task runs +- `scaffolder.task.duration`: Duration of a task run +- `scaffolder.step.count`: Count of step runs +- `scaffolder.step.duration`: Duration of a step runs +- `backend_tasks.task.runs.count`: Total number of times a task has been run +- `backend_tasks.task.runs.duration`: Histogram of task run durations + ## References - [Getting started with OpenTelemetry Node.js](https://opentelemetry.io/docs/instrumentation/js/getting-started/nodejs/) 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/docs/tutorials/yarn-migration.md b/docs/tutorials/yarn-migration.md index bc5980b2f5..5c87125277 100644 --- a/docs/tutorials/yarn-migration.md +++ b/docs/tutorials/yarn-migration.md @@ -85,7 +85,7 @@ FROM node:16-bullseye-slim # highlight-add-start # Set Python interpreter for `node-gyp` to use -ENV PYTHON /usr/bin/python3 +ENV PYTHON=/usr/bin/python3 # highlight-add-end # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, 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/data/plugins/azure-storage.yaml b/microsite/data/plugins/azure-storage.yaml index 655a6421ed..6e723c0eeb 100644 --- a/microsite/data/plugins/azure-storage.yaml +++ b/microsite/data/plugins/azure-storage.yaml @@ -4,9 +4,9 @@ author: Deepankumar authorUrl: https://github.com/deepan10 category: Infrastructure description: Explore Azure Storage Blobs in Backstage. -documentation: https://github.com/deepan10/backstage-plugins/tree/main/plugins/azure-storage +documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/azure-storage-explorer/plugins/azure-storage iconUrl: /img/azure-storage-folder.png -npmPackageName: 'backstage-plugin-azure-storage' +npmPackageName: '@backstage-community/plugin-azure-storage-explorer' tags: - Azure - Azure Storage diff --git a/microsite/data/plugins/blackduck.yaml b/microsite/data/plugins/blackduck.yaml index 4ef192d2fc..7a7858fff2 100644 --- a/microsite/data/plugins/blackduck.yaml +++ b/microsite/data/plugins/blackduck.yaml @@ -4,9 +4,9 @@ author: Deepankumar authorUrl: https://github.com/deepan10 category: Security description: BlackDuck scanned vulnerabilities in Backstage. -documentation: https://github.com/deepan10/backstage-plugin-blackduck +documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/blackduck/plugins/blackduck iconUrl: https://avatars.githubusercontent.com/u/431461?s=200&v=4 -npmPackageName: '@deepan10/backstage-plugin-blackduck' +npmPackageName: '@backstage-community/plugin-blackduck' tags: - security addedDate: '2022-12-03' diff --git a/microsite/data/plugins/catalog-backend-module-gcp.yaml b/microsite/data/plugins/catalog-backend-module-gcp.yaml new file mode 100644 index 0000000000..b5df858862 --- /dev/null +++ b/microsite/data/plugins/catalog-backend-module-gcp.yaml @@ -0,0 +1,16 @@ +--- +title: GCP Entity Providers +author: BackToStage +authorUrl: https://backtostage.app/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=catalog-backend-module-gcp +category: Catalog +description: Import your Infrastructure from GCP into Backstage as Resource Entities +documentation: https://github.com/backtostage/backstage-plugins/blob/main/plugins/catalog-backend-module-gcp/README.md +iconUrl: https://avatars1.githubusercontent.com/u/2810941?s=280&v=4 +npmPackageName: '@backtostage/plugin-catalog-backend-module-gcp' +tags: + - cloud + - project + - resources + - gcp + - cloudsql +addedDate: '2024-08-01' 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-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/infracost.yaml b/microsite/data/plugins/infracost.yaml new file mode 100644 index 0000000000..b6ead655d5 --- /dev/null +++ b/microsite/data/plugins/infracost.yaml @@ -0,0 +1,15 @@ +--- +title: VeeCode Infracost +author: VeeCode Platform +authorUrl: https://platform.vee.codes/ +category: FinOps +description: The Infracost plug-in works in conjunction with terraform and provides a graphical representation and cost estimate data for your entity at your respective provider. +documentation: https://platform.vee.codes/plugin/Infracost +iconUrl: https://veecode-platform.github.io/support/imgs/logo_5.svg +npmPackageName: '@veecode-platform/backstage-plugin-infracost' +tags: + - finops + - infracost + - estimate + - terraform +addedDate: '2024-07-25' diff --git a/microsite/data/plugins/kubernetes-gpt-analyzer.yaml b/microsite/data/plugins/kubernetes-gpt-analyzer.yaml new file mode 100644 index 0000000000..78bd0fe4c4 --- /dev/null +++ b/microsite/data/plugins/kubernetes-gpt-analyzer.yaml @@ -0,0 +1,16 @@ +--- +title: VeeCode Kubernetes GPT Analyzer +author: VeeCode Platform +authorUrl: https://platform.vee.codes/ +category: Monitoring +description: The Kubernetes GPT Analyzer plug-in uses artificial intelligence with the help of k8s-operator to analyze and optimize your Kubernetes entities, improving the management and performance of your cluster. It makes it easier to detect anomalies and suggest best practices. +documentation: https://platform.vee.codes/plugin/Kubernetes%20GPT%20Analyzer/ +iconUrl: https://veecode-platform.github.io/support/imgs/logo_4.svg +npmPackageName: '@veecode-platform/backstage-plugin-kubernetes-gpt-analyzer' +tags: + - monitor + - ai + - kubernetes + - k8soperator + - gpt +addedDate: '2024-07-31' 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/data/plugins/simple-icons.yaml b/microsite/data/plugins/simple-icons.yaml new file mode 100644 index 0000000000..45722ab421 --- /dev/null +++ b/microsite/data/plugins/simple-icons.yaml @@ -0,0 +1,10 @@ +--- +title: Simple Icons +author: dweber019 +authorUrl: https://github.com/dweber019 +category: Visualization +description: The Simple Icons plugin will add additional icons to be used as link icons. +documentation: https://github.com/dweber019/backstage-plugins/tree/main/plugins/simple-icons +iconUrl: https://raw.githubusercontent.com/dweber019/backstage-plugins/main/plugins/simple-icons/docs/pluginIcon.png +npmPackageName: '@dweber019/backstage-plugin-simple-icons' +addedDate: '2024-07-30' diff --git a/microsite/data/plugins/tech-insights.yaml b/microsite/data/plugins/tech-insights.yaml index fbf96df3e6..3bd40d576a 100644 --- a/microsite/data/plugins/tech-insights.yaml +++ b/microsite/data/plugins/tech-insights.yaml @@ -6,7 +6,7 @@ category: Discovery description: Visualize, understand and optimize your team's tech health. documentation: https://roadie.io/backstage/plugins/tech-insights/ iconUrl: https://roadie.io/images/logos/tech-insights.png -npmPackageName: '@backstage/plugin-tech-insights' +npmPackageName: '@backstage-community/plugin-tech-insights' tags: - tech-insights - reporting diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index b1a6533ab9..67018c6e33 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.30.0', label: 'Releases', position: 'left', }, diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 075025afbb..79a43e165b 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -1,6 +1,8 @@ { "releases": { "Release Notes": [ + "releases/v1.30.0", + "releases/v1.29.0", "releases/v1.28.0", "releases/v1.27.0", "releases/v1.26.0", @@ -120,7 +122,8 @@ "features/software-templates/writing-custom-step-layouts", "features/software-templates/authorizing-scaffolder-template-details", "features/software-templates/migrating-to-rjsf-v5", - "features/software-templates/migrating-from-v1beta2-to-v1beta3" + "features/software-templates/migrating-from-v1beta2-to-v1beta3", + "features/software-templates/dry-run-testing" ] }, { @@ -132,6 +135,7 @@ "features/search/concepts", "features/search/architecture", "features/search/search-engines", + "features/search/collators", "features/search/how-to-guides" ] }, @@ -295,6 +299,7 @@ "conf/writing", "conf/defining" ], + "Notifications": ["notifications/index"], "Auth and identity": [ "auth/index", { @@ -392,6 +397,7 @@ "backend-system/architecture/plugins", "backend-system/architecture/extension-points", "backend-system/architecture/modules", + "backend-system/architecture/feature-loaders", "backend-system/architecture/naming-patterns" ] }, @@ -429,6 +435,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", @@ -447,13 +454,15 @@ "items": [ "frontend-system/architecture/index", "frontend-system/architecture/app", - "frontend-system/architecture/extensions", "frontend-system/architecture/plugins", + "frontend-system/architecture/extensions", + "frontend-system/architecture/extension-blueprints", "frontend-system/architecture/extension-overrides", + "frontend-system/architecture/references", "frontend-system/architecture/utility-apis", "frontend-system/architecture/routes", "frontend-system/architecture/naming-patterns", - "frontend-system/architecture/references" + "frontend-system/architecture/migrations" ] }, { @@ -462,7 +471,7 @@ "items": [ "frontend-system/building-plugins/index", "frontend-system/building-plugins/testing", - "frontend-system/building-plugins/extension-types", + "frontend-system/building-plugins/common-extension-blueprints", "frontend-system/building-plugins/built-in-data-refs", "frontend-system/building-plugins/migrating" ] @@ -474,6 +483,7 @@ "frontend-system/building-apps/index", "frontend-system/building-apps/configuring-extensions", "frontend-system/building-apps/built-in-extensions", + "frontend-system/building-apps/plugin-conversion", "frontend-system/building-apps/migrating" ] }, 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/microsite/yarn.lock b/microsite/yarn.lock index 00732d79f2..f4e48096a2 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -9812,14 +9812,14 @@ __metadata: linkType: hard "react-dom@npm:^18.0.0": - version: 18.2.0 - resolution: "react-dom@npm:18.2.0" + version: 18.3.1 + resolution: "react-dom@npm:18.3.1" dependencies: loose-envify: ^1.1.0 - scheduler: ^0.23.0 + scheduler: ^0.23.2 peerDependencies: - react: ^18.2.0 - checksum: 7d323310bea3a91be2965f9468d552f201b1c27891e45ddc2d6b8f717680c95a75ae0bc1e3f5cf41472446a2589a75aed4483aee8169287909fcd59ad149e8cc + react: ^18.3.1 + checksum: 298954ecd8f78288dcaece05e88b570014d8f6dce5db6f66e6ee91448debeb59dcd31561dddb354eee47e6c1bb234669459060deb238ed0213497146e555a0b9 languageName: node linkType: hard @@ -9930,11 +9930,11 @@ __metadata: linkType: hard "react@npm:^18.0.0": - version: 18.2.0 - resolution: "react@npm:18.2.0" + version: 18.3.1 + resolution: "react@npm:18.3.1" dependencies: loose-envify: ^1.1.0 - checksum: 88e38092da8839b830cda6feef2e8505dec8ace60579e46aa5490fc3dc9bba0bd50336507dc166f43e3afc1c42939c09fe33b25fae889d6f402721dcd78fca1b + checksum: a27bcfa8ff7c15a1e50244ad0d0c1cb2ad4375eeffefd266a64889beea6f6b64c4966c9b37d14ee32d6c9fcd5aa6ba183b6988167ab4d127d13e7cb5b386a376 languageName: node linkType: hard @@ -10406,12 +10406,12 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:^0.23.0": - version: 0.23.0 - resolution: "scheduler@npm:0.23.0" +"scheduler@npm:^0.23.2": + version: 0.23.2 + resolution: "scheduler@npm:0.23.2" dependencies: loose-envify: ^1.1.0 - checksum: d79192eeaa12abef860c195ea45d37cbf2bbf5f66e3c4dcd16f54a7da53b17788a70d109ee3d3dde1a0fd50e6a8fc171f4300356c5aee4fc0171de526bf35f8a + checksum: 3e82d1f419e240ef6219d794ff29c7ee415fbdc19e038f680a10c067108e06284f1847450a210b29bbaf97b9d8a97ced5f624c31c681248ac84c80d56ad5a2c4 languageName: node linkType: hard diff --git a/mkdocs.yml b/mkdocs.yml index b9f6d41d46..33c9af3286 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -20,7 +20,7 @@ nav: - Support and community: 'overview/support.md' - Logo assets: 'overview/logos.md' - Getting Started: - - Installing a standalone server: 'getting-started/index.md' + - Creating your Backstage App: 'getting-started/index.md' - Configuring Backstage: - Database: 'getting-started/config/database.md' - Authentication: 'getting-started/config/authentication.md' @@ -64,12 +64,14 @@ nav: - Writing Custom Actions: 'features/software-templates/writing-custom-actions.md' - Writing Custom Step Layouts: 'features/software-templates/writing-custom-step-layouts.md' - Migrating from v1beta2 to v1beta3 templates: 'features/software-templates/migrating-from-v1beta2-to-v1beta3.md' + - Dry Run Testing: 'features/software-templates/dry-run-testing.md' - Backstage Search: - Overview: 'features/search/README.md' - Getting Started: 'features/search/getting-started.md' - Concepts: 'features/search/concepts.md' - Search Architecture: 'features/search/architecture.md' - Search Engines: 'features/search/search-engines.md' + - Collators: 'features/search/collators.md' - HOW TO guides: 'features/search/how-to-guides.md' - TechDocs: - Overview: 'features/techdocs/README.md' @@ -158,7 +160,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..740f7a412f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.29.0-next.0", + "version": "1.30.0", "private": true, "repository": { "type": "git", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 40ee32565f..72c172e395 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,78 @@ # @backstage/app-defaults +## 1.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-app-api@1.14.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-permission-react@0.4.25 + +## 1.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## 1.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + +## 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..ee21affccf 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.10", "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..af5b4a5c24 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,61 @@ # app-next-example-plugin +## 0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + +## 0.0.14-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/core-components@0.14.10-next.0 + +## 0.0.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-components@0.14.10-next.0 + +## 0.0.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-components@0.14.10-next.0 + +## 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/api-report.md b/packages/app-next-example-plugin/api-report.md index c89a7b599b..4698edec6b 100644 --- a/packages/app-next-example-plugin/api-report.md +++ b/packages/app-next-example-plugin/api-report.md @@ -3,11 +3,47 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { default as React_2 } from 'react'; +import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) -const examplePlugin: BackstagePlugin<{}, {}>; +const examplePlugin: BackstagePlugin< + {}, + {}, + { + 'page:example': ExtensionDefinition< + { + path: string | undefined; + }, + { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + {}, + { + kind: 'page'; + namespace: undefined; + name: undefined; + } + >; + } +>; export default examplePlugin; // @public (undocumented) diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index 9d2e76c418..5bb8318e2b 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", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", diff --git a/packages/app-next-example-plugin/src/plugin.tsx b/packages/app-next-example-plugin/src/plugin.tsx index 99c377b7e5..818e8abc84 100644 --- a/packages/app-next-example-plugin/src/plugin.tsx +++ b/packages/app-next-example-plugin/src/plugin.tsx @@ -16,17 +16,19 @@ import React from 'react'; import { - createPageExtension, - createPlugin, + PageBlueprint, + createFrontendPlugin, } from '@backstage/frontend-plugin-api'; -export const ExamplePage = createPageExtension({ - defaultPath: '/example', - loader: () => import('./Component').then(m => ), +export const ExamplePage = PageBlueprint.make({ + params: { + defaultPath: '/example', + loader: () => import('./Component').then(m => ), + }, }); /** @public */ -export const examplePlugin = createPlugin({ +export const examplePlugin = createFrontendPlugin({ id: 'example', extensions: [ExamplePage], }); diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 979284c459..5d3a1d5a3c 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,384 @@ # example-app-next +## 0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/plugin-scaffolder@1.24.0 + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-scaffolder-react@1.11.0 + - @backstage/cli@0.27.0 + - @backstage/plugin-notifications@0.3.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/plugin-home@0.7.9 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-components@0.14.10 + - @backstage/plugin-api-docs@0.11.8 + - @backstage/frontend-app-api@0.8.0 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-app-visualizer@0.1.9 + - @backstage/plugin-catalog-graph@0.4.8 + - @backstage/plugin-catalog-import@0.12.2 + - @backstage/plugin-org@0.6.28 + - @backstage/plugin-search@1.4.15 + - @backstage/plugin-user-settings@0.8.11 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-kubernetes@0.11.13 + - @backstage/core-app-api@1.14.2 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-kubernetes-cluster@0.0.14 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-signals@0.0.9 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13 + - @backstage/plugin-techdocs-react@1.2.7 + +## 0.0.14-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.24.0-next.3 + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/plugin-notifications@0.3.0-next.1 + - @backstage/cli@0.27.0-next.4 + - @backstage/plugin-techdocs@1.10.8-next.3 + - @backstage/plugin-api-docs@0.11.8-next.3 + - @backstage/plugin-catalog@1.22.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/frontend-app-api@0.7.5-next.3 + - @backstage/plugin-app-visualizer@0.1.9-next.3 + - @backstage/plugin-catalog-graph@0.4.8-next.4 + - @backstage/plugin-catalog-import@0.12.2-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/plugin-home@0.7.9-next.3 + - @backstage/plugin-kubernetes@0.11.13-next.3 + - @backstage/plugin-org@0.6.28-next.3 + - @backstage/plugin-search@1.4.15-next.3 + - @backstage/plugin-search-react@1.7.14-next.3 + - @backstage/plugin-user-settings@0.8.11-next.3 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.3 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.1 + - @backstage/plugin-techdocs-react@1.2.7-next.1 + +## 0.0.14-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.11.0-next.2 + - @backstage/plugin-catalog@1.22.0-next.2 + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/cli@0.27.0-next.3 + - @backstage/frontend-app-api@0.7.5-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-app-visualizer@0.1.9-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-search-react@1.7.14-next.2 + - @backstage/plugin-search@1.4.15-next.2 + - @backstage/plugin-api-docs@0.11.8-next.2 + - @backstage/plugin-techdocs@1.10.8-next.2 + - @backstage/plugin-scaffolder@1.24.0-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-graph@0.4.8-next.3 + - @backstage/plugin-catalog-import@0.12.2-next.2 + - @backstage/plugin-org@0.6.28-next.2 + - @backstage/plugin-user-settings@0.8.11-next.2 + - @backstage/plugin-home@0.7.9-next.2 + - @backstage/plugin-kubernetes@0.11.13-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.2 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## 0.0.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.2 + - @backstage/plugin-catalog-graph@0.4.8-next.2 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/frontend-app-api@0.7.5-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.8-next.1 + - @backstage/plugin-app-visualizer@0.1.9-next.1 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-import@0.12.2-next.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-home@0.7.9-next.1 + - @backstage/plugin-kubernetes@0.11.13-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.1 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-org@0.6.28-next.1 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-scaffolder@1.23.1-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.1 + - @backstage/plugin-search@1.4.15-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs@1.10.8-next.1 + - @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.11-next.1 + +## 0.0.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.8-next.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-app-api@0.7.5-next.1 + - @backstage/plugin-kubernetes@0.11.13-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/cli@0.27.0-next.1 + - @backstage/plugin-scaffolder@1.23.1-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-user-settings@0.8.11-next.1 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/plugin-search@1.4.15-next.1 + - @backstage/plugin-api-docs@0.11.8-next.1 + - @backstage/plugin-catalog-graph@0.4.8-next.1 + - @backstage/plugin-catalog-import@0.12.2-next.1 + - @backstage/plugin-home@0.7.9-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.1 + - @backstage/plugin-org@0.6.28-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/plugin-app-visualizer@0.1.9-next.1 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## 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..fa37cce5c8 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", "private": true, "repository": { "type": "git", diff --git a/packages/app-next/src/App.tsx b/packages/app-next/src/App.tsx index c911a01258..18b91409e1 100644 --- a/packages/app-next/src/App.tsx +++ b/packages/app-next/src/App.tsx @@ -26,28 +26,33 @@ import homePlugin, { import { coreExtensionData, createExtension, - createApiExtension, createExtensionOverrides, + ApiBlueprint, } from '@backstage/frontend-plugin-api'; -import techdocsPlugin from '@backstage/plugin-techdocs/alpha'; +import { + techdocsPlugin, + TechDocsIndexPage, + TechDocsReaderPage, + EntityTechdocsContent, +} from '@backstage/plugin-techdocs'; import appVisualizerPlugin from '@backstage/plugin-app-visualizer'; import { homePage } from './HomePage'; 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, + scmAuthApiRef, 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'; +import { convertLegacyPlugin } from '@backstage/core-compat-api'; +import { convertLegacyPageExtension } from '@backstage/core-compat-api'; +import { convertLegacyEntityContentExtension } from '@backstage/plugin-catalog-react/alpha'; /* @@ -78,34 +83,48 @@ TODO: /* app.tsx */ +const convertedTechdocsPlugin = convertLegacyPlugin(techdocsPlugin, { + extensions: [ + // TODO: We likely also need a way to convert an entire tree similar to collectLegacyRoutes + convertLegacyPageExtension(TechDocsIndexPage, { + name: 'index', + defaultPath: '/docs', + }), + convertLegacyPageExtension(TechDocsReaderPage, { + defaultPath: '/docs/:namespace/:kind/:name/*', + }), + convertLegacyEntityContentExtension(EntityTechdocsContent), + ], +}); + const homePageExtension = createExtension({ name: 'myhomepage', attachTo: { id: 'page:home', input: 'props' }, - output: { - children: coreExtensionData.reactElement, - title: titleExtensionDataRef, - }, + output: [coreExtensionData.reactElement, titleExtensionDataRef], factory() { - return { children: homePage, title: 'just a title' }; + return [ + coreExtensionData.reactElement(homePage), + titleExtensionDataRef('just a title'), + ]; }, }); -const signInPage = createSignInPageExtension({ - name: 'guest', - loader: async () => (props: SignInPageProps) => - , +const scmAuthExtension = ApiBlueprint.make({ + namespace: scmAuthApiRef.id, + params: { + factory: ScmAuth.createDefaultApiFactory(), + }, }); -const scmAuthExtension = createApiExtension({ - factory: ScmAuth.createDefaultApiFactory(), -}); - -const scmIntegrationApi = createApiExtension({ - factory: createApiFactory({ - api: scmIntegrationsApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), - }), +const scmIntegrationApi = ApiBlueprint.make({ + namespace: scmIntegrationsApiRef.id, + params: { + factory: createApiFactory({ + api: scmIntegrationsApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), + }), + }, }); const collectedLegacyPlugins = convertLegacyApp( @@ -117,17 +136,18 @@ const collectedLegacyPlugins = convertLegacyApp( const app = createApp({ features: [ pagesPlugin, - techdocsPlugin, + convertedTechdocsPlugin, userSettingsPlugin, homePlugin, appVisualizerPlugin, + kubernetesPlugin, + signInPageOverrides, ...collectedLegacyPlugins, createExtensionOverrides({ extensions: [ homePageExtension, scmAuthExtension, scmIntegrationApi, - signInPage, notFoundErrorPage, ], }), diff --git a/packages/app-next/src/examples/pagesPlugin.tsx b/packages/app-next/src/examples/pagesPlugin.tsx index 826040a6cb..8eca7d1e6c 100644 --- a/packages/app-next/src/examples/pagesPlugin.tsx +++ b/packages/app-next/src/examples/pagesPlugin.tsx @@ -17,11 +17,11 @@ import React from 'react'; import { Link } from '@backstage/core-components'; import { - createPageExtension, - createPlugin, + createFrontendPlugin, createRouteRef, createExternalRouteRef, useRouteRef, + PageBlueprint, } from '@backstage/frontend-plugin-api'; import { Route, Routes } from 'react-router-dom'; @@ -37,93 +37,101 @@ export const pageXRouteRef = createRouteRef(); // path: '/page2', // }); -const IndexPage = createPageExtension({ +const IndexPage = PageBlueprint.make({ name: 'index', - defaultPath: '/', - routeRef: indexRouteRef, - loader: async () => { - const Component = () => { - const page1Link = useRouteRef(page1RouteRef); - return ( -
- op + params: { + defaultPath: '/', + routeRef: indexRouteRef, + loader: async () => { + const Component = () => { + const page1Link = useRouteRef(page1RouteRef); + return (
- Page 1 -
-
- Home -
-
- GraphiQL -
-
- Search -
-
- Settings -
-
- ); - }; - return ; - }, -}); - -const Page1 = createPageExtension({ - name: 'page1', - defaultPath: '/page1', - routeRef: page1RouteRef, - loader: async () => { - const Component = () => { - const indexLink = useRouteRef(indexRouteRef); - const xLink = useRouteRef(externalPageXRouteRef); - // const page2Link = useRouteRef(page2RouteRef); - - return ( -
-

This is page 1

- Go back - Page 2 - {/* Page 2 */} - Page X - -
- Sub-page content: + op + {page1Link && ( +
+ Page 1 +
+ )}
- - This is also page 1} /> - This is page 2} /> - + Home +
+
+ GraphiQL +
+
+ Search +
+
+ Settings
-
- ); - }; - return ; + ); + }; + return ; + }, }, }); -const ExternalPage = createPageExtension({ +const Page1 = PageBlueprint.make({ + name: 'page1', + params: { + defaultPath: '/page1', + routeRef: page1RouteRef, + loader: async () => { + const Component = () => { + const indexLink = useRouteRef(indexRouteRef); + const xLink = useRouteRef(externalPageXRouteRef); + // const page2Link = useRouteRef(page2RouteRef); + + return ( +
+

This is page 1

+ {indexLink && Go back} + Page 2 + {/* Page 2 */} + {xLink && Page X} + +
+ Sub-page content: +
+ + This is also page 1} /> + This is page 2} /> + +
+
+
+ ); + }; + return ; + }, + }, +}); + +const ExternalPage = PageBlueprint.make({ name: 'pageX', - defaultPath: '/pageX', - routeRef: pageXRouteRef, - loader: async () => { - const Component = () => { - const indexLink = useRouteRef(indexRouteRef); - // const pageXLink = useRouteRef(pageXRouteRef); + params: { + defaultPath: '/pageX', + routeRef: pageXRouteRef, + loader: async () => { + const Component = () => { + const indexLink = useRouteRef(indexRouteRef); + // const pageXLink = useRouteRef(pageXRouteRef); - return ( -
-

This is page X

- Go back -
- ); - }; - return ; + return ( +
+

This is page X

+ {indexLink && Go back} +
+ ); + }; + return ; + }, }, }); -export const pagesPlugin = createPlugin({ +export const pagesPlugin = createFrontendPlugin({ id: 'pages', // routes: { // index: indexRouteRef, 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..27b2af7d91 --- /dev/null +++ b/packages/app-next/src/index-public-experimental.tsx @@ -0,0 +1,45 @@ +/* + * 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: [coreExtensionData.reactElement], + factory: () => [coreExtensionData.reactElement()], +}); + +const app = createApp({ + features: [ + signInPageOverrides, + createExtensionOverrides({ + extensions: [authRedirectExtension], + }), + ], +}); + +ReactDOM.createRoot(document.getElementById('root')!).render(app.createRoot()); diff --git a/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts b/packages/app-next/src/overrides/SignInPage.tsx similarity index 57% rename from packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts rename to packages/app-next/src/overrides/SignInPage.tsx index 373dd10ead..52dd01fcde 100644 --- a/packages/backend-app-api/src/services/implementations/httpAuth/httpAuthServiceFactory.ts +++ b/packages/app-next/src/overrides/SignInPage.tsx @@ -14,11 +14,21 @@ * limitations under the License. */ -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { httpAuthServiceFactory as _httpAuthServiceFactory } from '../../../../../backend-defaults/src/entrypoints/httpAuth'; +import React from 'react'; +import { SignInPage } from '@backstage/core-components'; +import { + SignInPageBlueprint, + createExtensionOverrides, +} from '@backstage/frontend-plugin-api'; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/httpAuth` instead. - */ -export const httpAuthServiceFactory = _httpAuthServiceFactory; +const signInPage = SignInPageBlueprint.make({ + name: 'guest', + params: { + loader: async () => props => + , + }, +}); + +export const signInPageOverrides = createExtensionOverrides({ + extensions: [signInPage], +}); diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index ca64c31049..f9cebb78e6 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,366 @@ # example-app +## 0.2.100 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.22.0 + - @backstage/plugin-scaffolder@1.24.0 + - @backstage/plugin-scaffolder-react@1.11.0 + - @backstage/cli@0.27.0 + - @backstage/plugin-notifications@0.3.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/plugin-home@0.7.9 + - @backstage/plugin-techdocs@1.10.8 + - @backstage/core-components@0.14.10 + - @backstage/plugin-api-docs@0.11.8 + - @backstage/frontend-app-api@0.8.0 + - @backstage/plugin-catalog-graph@0.4.8 + - @backstage/plugin-catalog-import@0.12.2 + - @backstage/plugin-devtools@0.1.17 + - @backstage/plugin-org@0.6.28 + - @backstage/plugin-search@1.4.15 + - @backstage/plugin-user-settings@0.8.11 + - @backstage/plugin-search-common@1.2.14 + - @backstage/plugin-kubernetes@0.11.13 + - @backstage/core-app-api@1.14.2 + - @backstage/plugin-auth-react@0.1.5 + - @backstage/catalog-model@1.6.0 + - @backstage/app-defaults@1.5.10 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30 + - @backstage/theme@0.5.6 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7 + - @backstage/plugin-kubernetes-cluster@0.0.14 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-signals@0.0.9 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13 + - @backstage/plugin-techdocs-react@1.2.7 + +## 0.2.100-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.24.0-next.3 + - @backstage/plugin-notifications@0.3.0-next.1 + - @backstage/cli@0.27.0-next.4 + - @backstage/plugin-techdocs@1.10.8-next.3 + - @backstage/plugin-api-docs@0.11.8-next.3 + - @backstage/plugin-catalog@1.22.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/frontend-app-api@0.7.5-next.3 + - @backstage/plugin-catalog-graph@0.4.8-next.4 + - @backstage/plugin-catalog-import@0.12.2-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/plugin-devtools@0.1.17-next.3 + - @backstage/plugin-home@0.7.9-next.3 + - @backstage/plugin-kubernetes@0.11.13-next.3 + - @backstage/plugin-org@0.6.28-next.3 + - @backstage/plugin-search@1.4.15-next.3 + - @backstage/plugin-search-react@1.7.14-next.3 + - @backstage/plugin-user-settings@0.8.11-next.3 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.3 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.1 + - @backstage/plugin-techdocs-react@1.2.7-next.1 + +## 0.2.100-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.11.0-next.2 + - @backstage/plugin-catalog@1.22.0-next.2 + - @backstage/cli@0.27.0-next.3 + - @backstage/frontend-app-api@0.7.5-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-search-react@1.7.14-next.2 + - @backstage/plugin-search@1.4.15-next.2 + - @backstage/plugin-api-docs@0.11.8-next.2 + - @backstage/plugin-techdocs@1.10.8-next.2 + - @backstage/plugin-scaffolder@1.24.0-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-graph@0.4.8-next.3 + - @backstage/plugin-catalog-import@0.12.2-next.2 + - @backstage/plugin-org@0.6.28-next.2 + - @backstage/plugin-user-settings@0.8.11-next.2 + - @backstage/plugin-devtools@0.1.17-next.2 + - @backstage/plugin-home@0.7.9-next.2 + - @backstage/plugin-kubernetes@0.11.13-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/app-defaults@1.5.10-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.2 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## 0.2.100-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.27.0-next.2 + - @backstage/plugin-catalog-graph@0.4.8-next.2 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/frontend-app-api@0.7.5-next.1 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-api-docs@0.11.8-next.1 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-import@0.12.2-next.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-devtools@0.1.17-next.1 + - @backstage/plugin-home@0.7.9-next.1 + - @backstage/plugin-kubernetes@0.11.13-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.1 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-org@0.6.28-next.1 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-scaffolder@1.23.1-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.1 + - @backstage/plugin-search@1.4.15-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs@1.10.8-next.1 + - @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.11-next.1 + +## 0.2.100-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.10.8-next.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-app-api@0.7.5-next.1 + - @backstage/plugin-kubernetes@0.11.13-next.1 + - @backstage/cli@0.27.0-next.1 + - @backstage/plugin-scaffolder@1.23.1-next.1 + - @backstage/plugin-user-settings@0.8.11-next.1 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-catalog@1.21.2-next.1 + - @backstage/plugin-search@1.4.15-next.1 + - @backstage/plugin-api-docs@0.11.8-next.1 + - @backstage/plugin-catalog-graph@0.4.8-next.1 + - @backstage/plugin-catalog-import@0.12.2-next.1 + - @backstage/plugin-home@0.7.9-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.14-next.1 + - @backstage/plugin-org@0.6.28-next.1 + - @backstage/plugin-scaffolder-react@1.11.0-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/plugin-devtools@0.1.17-next.1 + - @backstage/app-defaults@1.5.10-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/theme@0.5.6 + - @backstage/plugin-auth-react@0.1.5-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.7-next.0 + - @backstage/plugin-notifications@0.2.4-next.0 + - @backstage/plugin-signals@0.0.9-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.13-next.0 + - @backstage/plugin-techdocs-react@1.2.7-next.0 + +## 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..c27540f63e 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", "backstage": { "role": "frontend" }, diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index c9fe21c89e..076892c3b1 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,148 @@ # @backstage/backend-app-api +## 0.9.0 + +### Minor Changes + +- da4fde5: **BREAKING**: Removed several deprecated service factories. These can instead be imported from `@backstage/backend-defaults` package. +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. +- 389f5a4: Remove deprecated `urlReaderServiceFactory`, please import from `@backstage/backend-defaults/urlReader` instead. + +### Patch Changes + +- 8b13183: Added support for the latest version of `BackendFeature`s from `@backstage/backend-plugin-api`, including feature loaders. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 7c5f3b0: Update the `ServiceRegister` implementation to enable registering multiple service implementations for a given service ref. +- 80a0737: Added configuration for the `packages` options to config schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @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 + +## 0.8.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## 0.8.1-next.2 + +### Patch Changes + +- 8b13183: Added support for the latest version of `BackendFeature`s from `@backstage/backend-plugin-api`, including feature loaders. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 7c5f3b0: Update the `ServiceRegister` implementation to enable registering multiple service implementations for a given service ref. +- 80a0737: Added configuration for the `packages` options to config schema +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/cli-node@0.2.7 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @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.1 + +## 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..2633321472 100644 --- a/packages/backend-app-api/api-report-alpha.md +++ b/packages/backend-app-api/api-report-alpha.md @@ -7,9 +7,10 @@ import { FeatureDiscoveryService } from '@backstage/backend-plugin-api/alpha'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @alpha (undocumented) -export const featureDiscoveryServiceFactory: () => ServiceFactory< +export const featureDiscoveryServiceFactory: ServiceFactory< FeatureDiscoveryService, - 'root' + 'root', + 'singleton' >; // (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..88fa34e6e4 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -3,50 +3,8 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -/// - -import type { AppConfig } from '@backstage/config'; -import { AuthService } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; -import { CacheService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; -import { ConfigSchema } from '@backstage/config-loader'; -import { CorsOptions } from 'cors'; -import { DatabaseService } from '@backstage/backend-plugin-api'; -import { DiscoveryService } from '@backstage/backend-plugin-api'; -import { ErrorRequestHandler } from 'express'; -import { Express as Express_2 } from 'express'; -import { Format } from 'logform'; -import { Handler } from 'express'; -import { HelmetOptions } from 'helmet'; -import * as http from 'http'; -import { HttpAuthService } from '@backstage/backend-plugin-api'; -import { HttpRouterService } from '@backstage/backend-plugin-api'; -import { HumanDuration } from '@backstage/types'; -import { IdentityService } from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; -import { LifecycleService } from '@backstage/backend-plugin-api'; -import { LoadConfigOptionsRemote } from '@backstage/config-loader'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { PermissionsService } from '@backstage/backend-plugin-api'; -import { RemoteConfigSourceOptions } from '@backstage/config-loader'; -import { RequestHandler } from 'express'; -import { RequestListener } from 'http'; -import { RootConfigService } from '@backstage/backend-plugin-api'; -import { RootHttpRouterService } from '@backstage/backend-plugin-api'; -import { RootLifecycleService } from '@backstage/backend-plugin-api'; -import { RootLoggerService } from '@backstage/backend-plugin-api'; -import { SchedulerService } from '@backstage/backend-plugin-api'; -import type { Server } from 'node:http'; import { ServiceFactory } from '@backstage/backend-plugin-api'; -import { ServiceFactoryOrFunction } 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; // @public (undocumented) export interface Backend { @@ -54,9 +12,8 @@ export interface Backend { add( feature: | BackendFeature - | (() => BackendFeature) | Promise<{ - default: BackendFeature | (() => BackendFeature); + default: BackendFeature; }>, ): void; // (undocumented) @@ -65,24 +22,6 @@ export interface Backend { stop(): Promise; } -// @public @deprecated (undocumented) -export const cacheServiceFactory: () => ServiceFactory; - -// Warning: (ae-forgotten-export) The symbol "createConfigSecretEnumerator_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const createConfigSecretEnumerator: typeof createConfigSecretEnumerator_2; - -// Warning: (ae-forgotten-export) The symbol "createHttpServer_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const createHttpServer: typeof createHttpServer_2; - -// Warning: (ae-forgotten-export) The symbol "createLifecycleMiddleware_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated -export const createLifecycleMiddleware: typeof createLifecycleMiddleware_2; - // @public (undocumented) export function createSpecializedBackend( options: CreateSpecializedBackendOptions, @@ -91,249 +30,6 @@ export function createSpecializedBackend( // @public (undocumented) export interface CreateSpecializedBackendOptions { // (undocumented) - defaultServiceFactories: ServiceFactoryOrFunction[]; + defaultServiceFactories: ServiceFactory[]; } - -// @public @deprecated (undocumented) -export const databaseServiceFactory: () => ServiceFactory< - DatabaseService, - 'plugin' ->; - -// @public @deprecated -export class DefaultRootHttpRouter implements RootHttpRouterService { - // (undocumented) - static create(options?: DefaultRootHttpRouterOptions): DefaultRootHttpRouter; - // (undocumented) - handler(): Handler; - // (undocumented) - use(path: string, handler: Handler): void; -} - -// Warning: (ae-forgotten-export) The symbol "DefaultRootHttpRouterOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated -export type DefaultRootHttpRouterOptions = DefaultRootHttpRouterOptions_2; - -// @public @deprecated (undocumented) -export const discoveryServiceFactory: () => ServiceFactory< - DiscoveryService, - 'plugin' ->; - -// Warning: (ae-forgotten-export) The symbol "ExtendedHttpServer_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type ExtendedHttpServer = ExtendedHttpServer_2; - -// @public @deprecated -export class HostDiscovery implements DiscoveryService { - static fromConfig( - config: Config, - options?: { - basePath?: string; - }, - ): HostDiscovery; - // (undocumented) - getBaseUrl(pluginId: string): Promise; - // (undocumented) - getExternalBaseUrl(pluginId: string): Promise; -} - -// @public @deprecated (undocumented) -export const httpAuthServiceFactory: () => ServiceFactory< - HttpAuthService, - 'plugin' ->; - -// 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; - -// Warning: (ae-forgotten-export) The symbol "HttpServerCertificateOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type HttpServerCertificateOptions = HttpServerCertificateOptions_2; - -// Warning: (ae-forgotten-export) The symbol "HttpServerOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type HttpServerOptions = HttpServerOptions_2; - -// @public @deprecated -export type IdentityFactoryOptions = { - issuer?: string; - algorithms?: string[]; -}; - -// @public @deprecated (undocumented) -export const identityServiceFactory: ( - options?: IdentityFactoryOptions | undefined, -) => ServiceFactory; - -// Warning: (ae-forgotten-export) The symbol "LifecycleMiddlewareOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated -export type LifecycleMiddlewareOptions = LifecycleMiddlewareOptions_2; - -// @public @deprecated -export const lifecycleServiceFactory: () => ServiceFactory< - LifecycleService, - 'plugin' ->; - -// @public @deprecated -export function loadBackendConfig(options: { - remote?: LoadConfigOptionsRemote; - argv: string[]; - additionalConfigs?: AppConfig[]; - watch?: boolean; -}): Promise<{ - config: Config; -}>; - -// @public @deprecated -export const loggerServiceFactory: () => ServiceFactory< - LoggerService, - 'plugin' ->; - -// @public @deprecated (undocumented) -export class MiddlewareFactory { - compression(): RequestHandler; - cors(): RequestHandler; - static create(options: MiddlewareFactoryOptions): MiddlewareFactory; - error(options?: MiddlewareFactoryErrorOptions): ErrorRequestHandler; - helmet(): RequestHandler; - logging(): RequestHandler; - notFound(): RequestHandler; -} - -// Warning: (ae-forgotten-export) The symbol "MiddlewareFactoryErrorOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type MiddlewareFactoryErrorOptions = MiddlewareFactoryErrorOptions_2; - -// Warning: (ae-forgotten-export) The symbol "MiddlewareFactoryOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type MiddlewareFactoryOptions = MiddlewareFactoryOptions_2; - -// @public @deprecated (undocumented) -export const permissionsServiceFactory: () => ServiceFactory< - PermissionsService, - 'plugin' ->; - -// Warning: (ae-forgotten-export) The symbol "readCorsOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const readCorsOptions: typeof readCorsOptions_2; - -// Warning: (ae-forgotten-export) The symbol "readHelmetOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const readHelmetOptions: typeof readHelmetOptions_2; - -// Warning: (ae-forgotten-export) The symbol "readHttpServerOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const readHttpServerOptions: typeof readHttpServerOptions_2; - -// @public @deprecated (undocumented) -export interface RootConfigFactoryOptions { - argv?: string[]; - remote?: Pick; - // (undocumented) - watch?: boolean; -} - -// @public @deprecated (undocumented) -export const rootConfigServiceFactory: ( - options?: RootConfigFactoryOptions | undefined, -) => ServiceFactory; - -// Warning: (ae-forgotten-export) The symbol "RootHttpRouterConfigureContext_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type RootHttpRouterConfigureContext = RootHttpRouterConfigureContext_2; - -// Warning: (ae-forgotten-export) The symbol "RootHttpRouterFactoryOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated -export type RootHttpRouterFactoryOptions = RootHttpRouterFactoryOptions_2; - -// @public @deprecated (undocumented) -export const rootHttpRouterServiceFactory: ( - options?: RootHttpRouterFactoryOptions_2 | undefined, -) => ServiceFactory; - -// @public @deprecated -export const rootLifecycleServiceFactory: () => ServiceFactory< - RootLifecycleService, - 'root' ->; - -// @public @deprecated -export const rootLoggerServiceFactory: () => ServiceFactory< - RootLoggerService, - 'root' ->; - -// @public @deprecated (undocumented) -export const schedulerServiceFactory: () => ServiceFactory< - SchedulerService, - 'plugin' ->; - -// @public @deprecated (undocumented) -export const tokenManagerServiceFactory: () => ServiceFactory< - TokenManagerService, - 'plugin' ->; - -// @public @deprecated (undocumented) -export const urlReaderServiceFactory: () => ServiceFactory< - UrlReaderService, - 'plugin' ->; - -// @public @deprecated (undocumented) -export const userInfoServiceFactory: () => ServiceFactory< - UserInfoService, - 'plugin' ->; - -// @public @deprecated -export class WinstonLogger implements RootLoggerService { - // (undocumented) - addRedactions(redactions: Iterable): void; - // (undocumented) - child(meta: JsonObject): LoggerService; - static colorFormat(): Format; - static create(options: WinstonLoggerOptions): WinstonLogger; - // (undocumented) - debug(message: string, meta?: JsonObject): void; - // (undocumented) - error(message: string, meta?: JsonObject): void; - // (undocumented) - info(message: string, meta?: JsonObject): void; - static redacter(): { - format: Format; - add: (redactions: Iterable) => void; - }; - // (undocumented) - warn(message: string, meta?: JsonObject): void; -} - -// Warning: (ae-forgotten-export) The symbol "WinstonLoggerOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type WinstonLoggerOptions = WinstonLoggerOptions_2; ``` diff --git a/packages/backend-app-api/config.d.ts b/packages/backend-app-api/config.d.ts index a23bca447b..76d22db8b2 100644 --- a/packages/backend-app-api/config.d.ts +++ b/packages/backend-app-api/config.d.ts @@ -71,7 +71,7 @@ export interface Config { * the Backstage ecosystem to get authorized for access to APIs that do * not permit unauthorized access. */ - externalAccess: Array< + externalAccess?: Array< | { /** * This is the legacy service-to-service access method, where a set @@ -287,6 +287,7 @@ export interface Config { } >; }; + packages?: 'all' | { include?: string[]; exclude?: string[] }; }; /** Discovery options. */ diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 51bbca14bc..27cbb0b916 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.9.0", "description": "Core API used by Backstage backend apps", "backstage": { "role": "node-library" @@ -52,7 +52,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/cli-common": "workspace:^", "@backstage/cli-node": "workspace:^", "@backstage/config": "workspace:^", @@ -62,8 +61,6 @@ "@backstage/plugin-permission-node": "workspace:^", "@backstage/types": "workspace:^", "@manypkg/get-packages": "^1.1.3", - "@types/cors": "^2.8.6", - "@types/express": "^4.17.6", "compression": "^1.7.4", "cookie": "^0.6.0", "cors": "^2.8.5", @@ -79,7 +76,7 @@ "minimatch": "^9.0.0", "minimist": "^1.2.5", "morgan": "^1.10.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "node-forge": "^1.3.1", "path-to-regexp": "^6.2.1", "selfsigned": "^2.0.0", diff --git a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts index 254b6a47f1..6550a6b1d0 100644 --- a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts +++ b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts @@ -147,7 +147,7 @@ describe('featureDiscoveryServiceFactory', () => { await startTestBackend({ features: [ mock.factory, - featureDiscoveryServiceFactory(), + featureDiscoveryServiceFactory, mockServices.rootConfig.factory({ data: { backend: { packages: 'all' } }, }), @@ -166,7 +166,7 @@ describe('featureDiscoveryServiceFactory', () => { await startTestBackend({ features: [ mock.factory, - featureDiscoveryServiceFactory(), + featureDiscoveryServiceFactory, mockServices.rootConfig.factory({ data: { backend: { @@ -195,7 +195,7 @@ describe('featureDiscoveryServiceFactory', () => { await startTestBackend({ features: [ mock.factory, - featureDiscoveryServiceFactory(), + featureDiscoveryServiceFactory, mockServices.rootConfig.factory({ data: { backend: { @@ -220,7 +220,7 @@ describe('featureDiscoveryServiceFactory', () => { await startTestBackend({ features: [ mock.factory, - featureDiscoveryServiceFactory(), + featureDiscoveryServiceFactory, mockServices.rootConfig.factory({ data: { backend: { @@ -245,7 +245,7 @@ describe('featureDiscoveryServiceFactory', () => { await startTestBackend({ features: [ mock.factory, - featureDiscoveryServiceFactory(), + featureDiscoveryServiceFactory, mockServices.rootConfig.factory({ data: { backend: { @@ -270,7 +270,7 @@ describe('featureDiscoveryServiceFactory', () => { await startTestBackend({ features: [ mock.factory, - featureDiscoveryServiceFactory(), + featureDiscoveryServiceFactory, mockServices.rootConfig.factory({ data: { backend: { @@ -300,7 +300,7 @@ describe('featureDiscoveryServiceFactory', () => { await startTestBackend({ features: [ mock.factory, - featureDiscoveryServiceFactory(), + featureDiscoveryServiceFactory, mockServices.rootConfig.factory({ data: { backend: { packages: {} } }, }), @@ -316,7 +316,7 @@ describe('featureDiscoveryServiceFactory', () => { await startTestBackend({ features: [ mock.factory, - featureDiscoveryServiceFactory(), + featureDiscoveryServiceFactory, mockServices.rootConfig.factory({ data: { backend: {} }, }), diff --git a/packages/backend-app-api/src/http/index.ts b/packages/backend-app-api/src/http/index.ts deleted file mode 100644 index a056ed2d61..0000000000 --- a/packages/backend-app-api/src/http/index.ts +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ErrorRequestHandler, RequestHandler } from 'express'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - readHttpServerOptions as _readHttpServerOptions, - createHttpServer as _createHttpServer, - MiddlewareFactory as _MiddlewareFactory, - readCorsOptions as _readCorsOptions, - readHelmetOptions as _readHelmetOptions, - type MiddlewareFactoryErrorOptions as _MiddlewareFactoryErrorOptions, - type MiddlewareFactoryOptions as _MiddlewareFactoryOptions, - type ExtendedHttpServer as _ExtendedHttpServer, - type HttpServerCertificateOptions as _HttpServerCertificateOptions, - type HttpServerOptions as _HttpServerOptions, -} from '../../../backend-defaults/src/entrypoints/rootHttpRouter/http'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export const readHttpServerOptions = _readHttpServerOptions; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export const createHttpServer = _createHttpServer; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export const readCorsOptions = _readCorsOptions; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export const readHelmetOptions = _readHelmetOptions; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export class MiddlewareFactory { - /** - * Creates a new {@link MiddlewareFactory}. - */ - static create(options: MiddlewareFactoryOptions) { - return new MiddlewareFactory(_MiddlewareFactory.create(options)); - } - - private constructor(private readonly impl: _MiddlewareFactory) {} - - /** - * Returns a middleware that unconditionally produces a 404 error response. - * - * @remarks - * - * Typically you want to place this middleware at the end of the chain, such - * that it's the last one attempted after no other routes matched. - * - * @returns An Express request handler - */ - notFound(): RequestHandler { - return this.impl.notFound(); - } - - /** - * Returns the compression middleware. - * - * @remarks - * - * The middleware will attempt to compress response bodies for all requests - * that traverse through the middleware. - */ - compression(): RequestHandler { - return this.impl.compression(); - } - - /** - * Returns a request logging middleware. - * - * @remarks - * - * Typically you want to place this middleware at the start of the chain, such - * that it always logs requests whether they are "caught" by handlers farther - * down or not. - * - * @returns An Express request handler - */ - logging(): RequestHandler { - return this.impl.logging(); - } - - /** - * Returns a middleware that implements the helmet library. - * - * @remarks - * - * This middleware applies security policies to incoming requests and outgoing - * responses. It is configured using config keys such as `backend.csp`. - * - * @see {@link https://helmetjs.github.io/} - * - * @returns An Express request handler - */ - helmet(): RequestHandler { - return this.impl.helmet(); - } - - /** - * Returns a middleware that implements the cors library. - * - * @remarks - * - * This middleware handles CORS. It is configured using the config key - * `backend.cors`. - * - * @see {@link https://github.com/expressjs/cors} - * - * @returns An Express request handler - */ - cors(): RequestHandler { - return this.impl.cors(); - } - - /** - * Express middleware to handle errors during request processing. - * - * @remarks - * - * This is commonly the very last middleware in the chain. - * - * Its primary purpose is not to do translation of business logic exceptions, - * but rather to be a global catch-all for uncaught "fatal" errors that are - * expected to result in a 500 error. However, it also does handle some common - * error types (such as http-error exceptions, and the well-known error types - * in the `@backstage/errors` package) and returns the enclosed status code - * accordingly. - * - * It will also produce a response body with a serialized form of the error, - * unless a previous handler already did send a body. See - * {@link @backstage/errors#ErrorResponseBody} for the response shape used. - * - * @returns An Express error request handler - */ - error(options: MiddlewareFactoryErrorOptions = {}): ErrorRequestHandler { - return this.impl.error(options); - } -} -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type MiddlewareFactoryErrorOptions = _MiddlewareFactoryErrorOptions; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type MiddlewareFactoryOptions = _MiddlewareFactoryOptions; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type ExtendedHttpServer = _ExtendedHttpServer; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type HttpServerCertificateOptions = _HttpServerCertificateOptions; -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type HttpServerOptions = _HttpServerOptions; diff --git a/packages/backend-app-api/src/index.ts b/packages/backend-app-api/src/index.ts index 13864a3470..c58379c3e4 100644 --- a/packages/backend-app-api/src/index.ts +++ b/packages/backend-app-api/src/index.ts @@ -20,8 +20,4 @@ * @packageDocumentation */ -export * from './config'; -export * from './http'; -export * from './logging'; export * from './wiring'; -export * from './services/implementations'; diff --git a/packages/backend-app-api/src/logging/WinstonLogger.ts b/packages/backend-app-api/src/logging/WinstonLogger.ts deleted file mode 100644 index e5403906ce..0000000000 --- a/packages/backend-app-api/src/logging/WinstonLogger.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - WinstonLogger as _WinstonLogger, - type WinstonLoggerOptions as _WinstonLoggerOptions, -} from '../../../backend-defaults/src/entrypoints/rootLogger'; - -import { - LoggerService, - RootLoggerService, -} from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; -import { Format } from 'logform'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootLogger` instead. - */ -export type WinstonLoggerOptions = _WinstonLoggerOptions; - -/** - * A {@link @backstage/backend-plugin-api#LoggerService} implementation based on winston. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootLogger` instead. - */ -export class WinstonLogger implements RootLoggerService { - /** - * Creates a {@link WinstonLogger} instance. - */ - static create(options: WinstonLoggerOptions): WinstonLogger { - return new WinstonLogger(_WinstonLogger.create(options)); - } - - /** - * Creates a winston log formatter for redacting secrets. - */ - static redacter(): { - format: Format; - add: (redactions: Iterable) => void; - } { - return _WinstonLogger.redacter(); - } - - /** - * Creates a pretty printed winston log formatter. - */ - static colorFormat(): Format { - return _WinstonLogger.colorFormat(); - } - - private constructor(private readonly impl: _WinstonLogger) {} - - error(message: string, meta?: JsonObject): void { - this.impl.error(message, meta); - } - - warn(message: string, meta?: JsonObject): void { - this.impl.warn(message, meta); - } - - info(message: string, meta?: JsonObject): void { - this.impl.info(message, meta); - } - - debug(message: string, meta?: JsonObject): void { - this.impl.debug(message, meta); - } - - child(meta: JsonObject): LoggerService { - return this.impl.child(meta); - } - - addRedactions(redactions: Iterable) { - this.impl.addRedactions(redactions); - } -} diff --git a/packages/backend-app-api/src/logging/index.ts b/packages/backend-app-api/src/logging/index.ts deleted file mode 100644 index 14fe33f898..0000000000 --- a/packages/backend-app-api/src/logging/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { WinstonLogger } from './WinstonLogger'; -export type { WinstonLoggerOptions } from './WinstonLogger'; diff --git a/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts b/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts deleted file mode 100644 index 9b5e5ab16a..0000000000 --- a/packages/backend-app-api/src/services/implementations/cache/cacheServiceFactory.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CacheManager } from '@backstage/backend-common'; -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/cache` instead. - */ -export const cacheServiceFactory = createServiceFactory({ - service: coreServices.cache, - deps: { - config: coreServices.rootConfig, - logger: coreServices.rootLogger, - plugin: coreServices.pluginMetadata, - }, - async createRootContext({ config, logger }) { - return CacheManager.fromConfig(config, { logger }); - }, - async factory({ plugin }, manager) { - return manager.forPlugin(plugin.getId()).getClient(); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/config/index.ts b/packages/backend-app-api/src/services/implementations/config/index.ts deleted file mode 100644 index 1775ef2efc..0000000000 --- a/packages/backend-app-api/src/services/implementations/config/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { rootConfigServiceFactory } from './rootConfigServiceFactory'; -export type { RootConfigFactoryOptions } from './rootConfigServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts b/packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts deleted file mode 100644 index c74474e629..0000000000 --- a/packages/backend-app-api/src/services/implementations/config/rootConfigServiceFactory.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { - ConfigSources, - RemoteConfigSourceOptions, -} from '@backstage/config-loader'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootConfig` instead. - */ -export interface RootConfigFactoryOptions { - /** - * Process arguments to use instead of the default `process.argv()`. - */ - argv?: string[]; - - /** - * Enables and sets options for remote configuration loading. - */ - remote?: Pick; - watch?: boolean; -} - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootConfig` instead. - */ -export const rootConfigServiceFactory = createServiceFactory( - (options?: RootConfigFactoryOptions) => ({ - service: coreServices.rootConfig, - deps: {}, - async factory() { - const source = ConfigSources.default({ - argv: options?.argv, - remote: options?.remote, - watch: options?.watch, - }); - console.log(`Loading config from ${source}`); - return await ConfigSources.toConfig(source); - }, - }), -); diff --git a/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts b/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts deleted file mode 100644 index 972d8dd4ec..0000000000 --- a/packages/backend-app-api/src/services/implementations/database/databaseServiceFactory.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { DatabaseManager } from '@backstage/backend-common'; -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { ConfigReader } from '@backstage/config'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/database` instead. - */ -export const databaseServiceFactory = createServiceFactory({ - service: coreServices.database, - deps: { - config: coreServices.rootConfig, - lifecycle: coreServices.lifecycle, - pluginMetadata: coreServices.pluginMetadata, - }, - async createRootContext({ config }) { - return config.getOptional('backend.database') - ? DatabaseManager.fromConfig(config) - : DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { client: 'better-sqlite3', connection: ':memory:' }, - }, - }), - ); - }, - async factory({ pluginMetadata, lifecycle }, databaseManager) { - return databaseManager.forPlugin(pluginMetadata.getId(), { - pluginMetadata, - lifecycle, - }); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.test.ts b/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.test.ts deleted file mode 100644 index 4e6aff5853..0000000000 --- a/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.test.ts +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ConfigReader } from '@backstage/config'; -import { HostDiscovery } from './HostDiscovery'; - -describe('HostDiscovery', () => { - it('is created from config', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - }), - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://localhost:80/api/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://localhost:40/api/catalog', - ); - }); - - it('strips trailing slashes in config', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40//', - listen: { port: 80, host: 'localhost' }, - }, - }), - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://localhost:80/api/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://localhost:40/api/catalog', - ); - }); - - it('can configure the base path', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - }), - { basePath: '/service' }, - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://localhost:80/service/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://localhost:40/service/catalog', - ); - }); - - it.each([ - [{ listen: ':80' }, 'http://localhost:80'], - [{ listen: ':40', https: true }, 'https://localhost:40'], - [{ listen: '127.0.0.1:80' }, 'http://127.0.0.1:80'], - [{ listen: '127.0.0.1:80', https: true }, 'https://127.0.0.1:80'], - [{ listen: '0.0.0.0:40' }, 'http://127.0.0.1:40'], - [{ listen: { port: 80 } }, 'http://localhost:80'], - [{ listen: { port: 8000 } }, 'http://localhost:8000'], - [{ listen: { port: 80, host: '0.0.0.0' } }, 'http://127.0.0.1:80'], - [{ listen: { port: 80, host: '::' } }, 'http://localhost:80'], - [{ listen: { port: 80, host: '::1' } }, 'http://[::1]:80'], - [{ listen: { port: 90, host: '::2' }, https: true }, 'https://[::2]:90'], - ])('resolves internal baseUrl for %j as %s', async (config, expected) => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - ...config, - }, - }), - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - `${expected}/api/catalog`, - ); - }); - - it('uses plugin specific targets from config if provided', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - discovery: { - endpoints: [ - { - target: { - internal: 'http://catalog-backend-internal:8080/api/catalog', - external: 'http://catalog-backend-external:8080/api/catalog', - }, - plugins: ['catalog'], - }, - ], - }, - }), - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://catalog-backend-internal:8080/api/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://catalog-backend-external:8080/api/catalog', - ); - }); - - it('uses a single target for internal and external for a plugin', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - discovery: { - endpoints: [ - { - target: 'http://catalog-backend:8080/api/catalog', - plugins: ['catalog'], - }, - ], - }, - }), - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://catalog-backend:8080/api/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://catalog-backend:8080/api/catalog', - ); - }); - - it('defaults to the backend baseUrl when there is not an endpoint for a plugin', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - discovery: { - endpoints: [ - { - target: 'http://catalog-backend:8080/api/catalog', - plugins: ['catalog'], - }, - ], - }, - }), - ); - - await expect(discovery.getBaseUrl('scaffolder')).resolves.toBe( - 'http://localhost:80/api/scaffolder', - ); - await expect(discovery.getExternalBaseUrl('scaffolder')).resolves.toBe( - 'http://localhost:40/api/scaffolder', - ); - }); - - it('replaces {{pluginId}} or {{ pluginId }} in the target', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - discovery: { - endpoints: [ - { - target: 'http://common-backend:8080/api/{{pluginId}}', - plugins: ['catalog', 'docs'], - }, - { - target: { - internal: 'http://scaffolder-internal:8080/api/{{ pluginId }}', - external: 'http://scaffolder-external:8080/api/{{ pluginId }}', - }, - plugins: ['scaffolder'], - }, - ], - }, - }), - ); - - await expect(discovery.getBaseUrl('catalog')).resolves.toBe( - 'http://common-backend:8080/api/catalog', - ); - await expect(discovery.getExternalBaseUrl('catalog')).resolves.toBe( - 'http://common-backend:8080/api/catalog', - ); - await expect(discovery.getBaseUrl('docs')).resolves.toBe( - 'http://common-backend:8080/api/docs', - ); - await expect(discovery.getExternalBaseUrl('docs')).resolves.toBe( - 'http://common-backend:8080/api/docs', - ); - await expect(discovery.getBaseUrl('scaffolder')).resolves.toBe( - 'http://scaffolder-internal:8080/api/scaffolder', - ); - await expect(discovery.getExternalBaseUrl('scaffolder')).resolves.toBe( - 'http://scaffolder-external:8080/api/scaffolder', - ); - }); - - it('encodes the pluginId', async () => { - const discovery = HostDiscovery.fromConfig( - new ConfigReader({ - backend: { - baseUrl: 'http://localhost:40', - listen: { port: 80, host: 'localhost' }, - }, - discovery: { - endpoints: [ - { - target: 'http://common-backend:8080/api/{{pluginId}}', - plugins: ['plugin/beta'], - }, - ], - }, - }), - ); - - await expect(discovery.getBaseUrl('plugin/beta')).resolves.toBe( - 'http://common-backend:8080/api/plugin%2Fbeta', - ); - await expect(discovery.getBaseUrl('plugin/alpha')).resolves.toBe( - 'http://localhost:80/api/plugin%2Falpha', - ); - await expect(discovery.getExternalBaseUrl('plugin/alpha')).resolves.toBe( - 'http://localhost:40/api/plugin%2Falpha', - ); - }); -}); diff --git a/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.ts b/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.ts deleted file mode 100644 index 5909dd1ae6..0000000000 --- a/packages/backend-app-api/src/services/implementations/discovery/HostDiscovery.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Config } from '@backstage/config'; -import { DiscoveryService } from '@backstage/backend-plugin-api'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { HostDiscovery as _HostDiscovery } from '../../../../../backend-defaults/src/entrypoints/discovery'; - -/** - * HostDiscovery is a basic PluginEndpointDiscovery implementation - * that can handle plugins that are hosted in a single or multiple deployments. - * - * The deployment may be scaled horizontally, as long as the external URL - * is the same for all instances. However, internal URLs will always be - * resolved to the same host, so there won't be any balancing of internal traffic. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/discovery` instead. - */ -export class HostDiscovery implements DiscoveryService { - /** - * Creates a new HostDiscovery discovery instance by reading - * from the `backend` config section, specifically the `.baseUrl` for - * discovering the external URL, and the `.listen` and `.https` config - * for the internal one. - * - * Can be overridden in config by providing a target and corresponding plugins in `discovery.endpoints`. - * eg. - * ```yaml - * discovery: - * endpoints: - * - target: https://internal.example.com/internal-catalog - * plugins: [catalog] - * - target: https://internal.example.com/secure/api/{{pluginId}} - * plugins: [auth, permission] - * - target: - * internal: https://internal.example.com/search - * external: https://example.com/search - * plugins: [search] - * ``` - * - * The basePath defaults to `/api`, meaning the default full internal - * path for the `catalog` plugin will be `http://localhost:7007/api/catalog`. - */ - static fromConfig(config: Config, options?: { basePath?: string }) { - return new HostDiscovery(_HostDiscovery.fromConfig(config, options)); - } - - private constructor(private readonly impl: _HostDiscovery) {} - - async getBaseUrl(pluginId: string): Promise { - return this.impl.getBaseUrl(pluginId); - } - - async getExternalBaseUrl(pluginId: string): Promise { - return this.impl.getExternalBaseUrl(pluginId); - } -} diff --git a/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts b/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts deleted file mode 100644 index b29a589438..0000000000 --- a/packages/backend-app-api/src/services/implementations/discovery/discoveryServiceFactory.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { HostDiscovery } from './HostDiscovery'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/discovery` instead. - */ -export const discoveryServiceFactory = createServiceFactory({ - service: coreServices.discovery, - deps: { - config: coreServices.rootConfig, - }, - async factory({ config }) { - return HostDiscovery.fromConfig(config); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/discovery/index.ts b/packages/backend-app-api/src/services/implementations/discovery/index.ts deleted file mode 100644 index ee4851271a..0000000000 --- a/packages/backend-app-api/src/services/implementations/discovery/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { discoveryServiceFactory } from './discoveryServiceFactory'; -export { HostDiscovery } from './HostDiscovery'; diff --git a/packages/backend-app-api/src/services/implementations/httpRouter/createLifecycleMiddleware.ts b/packages/backend-app-api/src/services/implementations/httpRouter/createLifecycleMiddleware.ts deleted file mode 100644 index d4864a23a6..0000000000 --- a/packages/backend-app-api/src/services/implementations/httpRouter/createLifecycleMiddleware.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - createLifecycleMiddleware as _createLifecycleMiddleware, - type LifecycleMiddlewareOptions as _LifecycleMiddlewareOptions, -} from '../../../../../backend-defaults/src/entrypoints/httpRouter/createLifecycleMiddleware'; - -/** - * Options for {@link createLifecycleMiddleware}. - * @public - * @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead. - */ -export type LifecycleMiddlewareOptions = _LifecycleMiddlewareOptions; - -/** - * Creates a middleware that pauses requests until the service has started. - * - * @remarks - * - * Requests that arrive before the service has started will be paused until startup is complete. - * If the service does not start within the provided timeout, the request will be rejected with a - * {@link @backstage/errors#ServiceUnavailableError}. - * - * If the service is shutting down, all requests will be rejected with a - * {@link @backstage/errors#ServiceUnavailableError}. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead. - */ -export const createLifecycleMiddleware = _createLifecycleMiddleware; diff --git a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts b/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts deleted file mode 100644 index 9d2b74fb43..0000000000 --- a/packages/backend-app-api/src/services/implementations/httpRouter/httpRouterServiceFactory.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// 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; - -/** - * HTTP route registration for plugins. - * - * See {@link @backstage/code-plugin-api#HttpRouterService} - * and {@link https://backstage.io/docs/backend-system/core-services/http-router | the service docs} - * for more information. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/httpRouter` instead. - */ -export const httpRouterServiceFactory = _httpRouterServiceFactory; diff --git a/packages/backend-app-api/src/services/implementations/httpRouter/index.ts b/packages/backend-app-api/src/services/implementations/httpRouter/index.ts deleted file mode 100644 index 928a232131..0000000000 --- a/packages/backend-app-api/src/services/implementations/httpRouter/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -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/identity/identityServiceFactory.ts b/packages/backend-app-api/src/services/implementations/identity/identityServiceFactory.ts deleted file mode 100644 index c2f1a61007..0000000000 --- a/packages/backend-app-api/src/services/implementations/identity/identityServiceFactory.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; - -/** - * An identity client options object which allows extra configurations - * - * @public - * @deprecated Please migrate to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead - */ -export type IdentityFactoryOptions = { - issuer?: string; - - /** - * JWS "alg" (Algorithm) Header Parameter values. Defaults to an array containing just ES256. - * More info on supported algorithms: https://github.com/panva/jose - */ - algorithms?: string[]; -}; - -/** - * @public - * @deprecated Please migrate to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead - */ -export const identityServiceFactory = createServiceFactory( - (options?: IdentityFactoryOptions) => ({ - service: coreServices.identity, - deps: { - discovery: coreServices.discovery, - }, - async factory({ discovery }) { - return DefaultIdentityClient.create({ discovery, ...options }); - }, - }), -); diff --git a/packages/backend-app-api/src/services/implementations/identity/index.ts b/packages/backend-app-api/src/services/implementations/identity/index.ts deleted file mode 100644 index bf2f4436dd..0000000000 --- a/packages/backend-app-api/src/services/implementations/identity/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { identityServiceFactory } from './identityServiceFactory'; -export type { IdentityFactoryOptions } from './identityServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/lifecycle/index.ts b/packages/backend-app-api/src/services/implementations/lifecycle/index.ts deleted file mode 100644 index 8dac4c26b4..0000000000 --- a/packages/backend-app-api/src/services/implementations/lifecycle/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { lifecycleServiceFactory } from './lifecycleServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleServiceFactory.ts b/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleServiceFactory.ts deleted file mode 100644 index b3b0135a7c..0000000000 --- a/packages/backend-app-api/src/services/implementations/lifecycle/lifecycleServiceFactory.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - LifecycleService, - LifecycleServiceShutdownHook, - LifecycleServiceShutdownOptions, - LifecycleServiceStartupHook, - LifecycleServiceStartupOptions, - LoggerService, - PluginMetadataService, - RootLifecycleService, - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; - -/** - * @internal - * @deprecated - */ -export class BackendPluginLifecycleImpl implements LifecycleService { - constructor( - private readonly logger: LoggerService, - private readonly rootLifecycle: RootLifecycleService, - private readonly pluginMetadata: PluginMetadataService, - ) {} - - #hasStarted = false; - #startupTasks: Array<{ - hook: LifecycleServiceStartupHook; - options?: LifecycleServiceStartupOptions; - }> = []; - - addStartupHook( - hook: LifecycleServiceStartupHook, - options?: LifecycleServiceStartupOptions, - ): void { - if (this.#hasStarted) { - throw new Error('Attempted to add startup hook after startup'); - } - this.#startupTasks.push({ hook, options }); - } - - async startup(): Promise { - if (this.#hasStarted) { - return; - } - this.#hasStarted = true; - - this.logger.debug( - `Running ${this.#startupTasks.length} plugin startup tasks...`, - ); - await Promise.all( - this.#startupTasks.map(async ({ hook, options }) => { - const logger = options?.logger ?? this.logger; - try { - await hook(); - logger.debug(`Plugin startup hook succeeded`); - } catch (error) { - logger.error(`Plugin startup hook failed, ${error}`); - } - }), - ); - } - - addShutdownHook( - hook: LifecycleServiceShutdownHook, - options?: LifecycleServiceShutdownOptions, - ): void { - const plugin = this.pluginMetadata.getId(); - this.rootLifecycle.addShutdownHook(hook, { - logger: options?.logger?.child({ plugin }) ?? this.logger, - }); - } -} - -/** - * Allows plugins to register shutdown hooks that are run when the process is about to exit. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/lifecycle` instead. - */ -export const lifecycleServiceFactory = createServiceFactory({ - service: coreServices.lifecycle, - deps: { - logger: coreServices.logger, - rootLifecycle: coreServices.rootLifecycle, - pluginMetadata: coreServices.pluginMetadata, - }, - async factory({ rootLifecycle, logger, pluginMetadata }) { - return new BackendPluginLifecycleImpl( - logger, - rootLifecycle, - pluginMetadata, - ); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/logger/index.ts b/packages/backend-app-api/src/services/implementations/logger/index.ts deleted file mode 100644 index ca52bf5193..0000000000 --- a/packages/backend-app-api/src/services/implementations/logger/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { loggerServiceFactory } from './loggerServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/logger/loggerServiceFactory.ts b/packages/backend-app-api/src/services/implementations/logger/loggerServiceFactory.ts deleted file mode 100644 index 7e7b4b98b0..0000000000 --- a/packages/backend-app-api/src/services/implementations/logger/loggerServiceFactory.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { loggerServiceFactory as _loggerServiceFactory } from '../../../../../backend-defaults/src/entrypoints/logger/loggerServiceFactory'; - -/** - * Plugin-level logging. - * - * See {@link @backstage/code-plugin-api#LoggerService} - * and {@link https://backstage.io/docs/backend-system/core-services/logger | the service docs} - * for more information. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/logger` instead. - */ -export const loggerServiceFactory = _loggerServiceFactory; diff --git a/packages/backend-app-api/src/services/implementations/permissions/index.ts b/packages/backend-app-api/src/services/implementations/permissions/index.ts deleted file mode 100644 index 781dda31a0..0000000000 --- a/packages/backend-app-api/src/services/implementations/permissions/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { permissionsServiceFactory } from './permissionsServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts b/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts deleted file mode 100644 index c8fa0e7bbf..0000000000 --- a/packages/backend-app-api/src/services/implementations/permissions/permissionsServiceFactory.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { ServerPermissionClient } from '@backstage/plugin-permission-node'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/permissions` instead. - */ -export const permissionsServiceFactory = createServiceFactory({ - service: coreServices.permissions, - deps: { - auth: coreServices.auth, - config: coreServices.rootConfig, - discovery: coreServices.discovery, - tokenManager: coreServices.tokenManager, - }, - async factory({ auth, config, discovery, tokenManager }) { - return ServerPermissionClient.fromConfig(config, { - auth, - discovery, - tokenManager, - }); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts deleted file mode 100644 index fc1de9b205..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import express from 'express'; -import request from 'supertest'; -import { DefaultRootHttpRouter } from './DefaultRootHttpRouter'; - -describe('DefaultRootHttpRouter', () => { - it.each([ - [['/b'], '/a'], - [['/a'], '/aa/b'], - [['/aa'], '/a/b'], - [['/a/b'], '/aa'], - [['/b/a'], '/a'], - [['/a'], '/aa'], - ])(`with existing paths %s, adds %s without conflict`, (existing, added) => { - const router = DefaultRootHttpRouter.create(); - for (const path of existing) { - router.use(path, () => {}); - } - expect(() => router.use(added, () => {})).not.toThrow(); - }); - - it.each([ - [['/a'], '/a', '/a'], - [['/a'], '/a/b', '/a'], - [['/a/b'], '/a', '/a/b'], - ])( - `find conflict when existing paths %s, adds %s`, - (existing, added, conflict) => { - const router = DefaultRootHttpRouter.create(); - for (const path of existing) { - router.use(path, () => {}); - } - expect(() => router.use(added, () => {})).toThrow( - `Path ${added} conflicts with the existing path ${conflict}`, - ); - }, - ); - - it('should not be possible to supply an empty indexPath', () => { - expect(() => DefaultRootHttpRouter.create({ indexPath: '' })).toThrow( - 'indexPath option may not be an empty string', - ); - }); - - it('will always prioritize non-index paths', async () => { - const router = DefaultRootHttpRouter.create({ indexPath: '/x' }); - const app = express(); - app.use(router.handler()); - - const routerX = express.Router(); - routerX.get('/a', (_req, res) => res.status(201).end()); - - const routerY = express.Router(); - routerY.get('/a', (_req, res) => res.status(202).end()); - - await request(app).get('/').expect(404); - await request(app).get('/a').expect(404); - await request(app).get('/x/a').expect(404); - await request(app).get('/y/a').expect(404); - - router.use('/x', routerX); - - await request(app).get('/').expect(404); - await request(app).get('/a').expect(201); - await request(app).get('/x/a').expect(201); - await request(app).get('/y/a').expect(404); - - router.use('/y', routerY); - - await request(app).get('/').expect(404); - await request(app).get('/a').expect(201); - await request(app).get('/x/a').expect(201); - await request(app).get('/y/a').expect(202); - - expect('test').toBe('test'); - }); - - it('should treat unknown /api/ routes as 404', async () => { - const router = DefaultRootHttpRouter.create(); - const app = express(); - app.use(router.handler()); - - router.use('/api/app', (_req, res) => res.status(201).end()); - router.use('/api/catalog', (_req, res) => res.status(202).end()); - - await request(app).get('/').expect(201); - await request(app).get('/api/catalog').expect(202); - await request(app).get('/unknown').expect(201); - await request(app).get('/api/unknown').expect(404); - - expect('test').toBe('test'); - }); - - it('should treat unknown /api/ routes as 404 without an index path', async () => { - const router = DefaultRootHttpRouter.create({ indexPath: false }); - const app = express(); - app.use(router.handler()); - - router.use('/api/app', (_req, res) => res.status(201).end()); - router.use('/api/catalog', (_req, res) => res.status(202).end()); - - await request(app).get('/').expect(404); - await request(app).get('/api/catalog').expect(202); - await request(app).get('/unknown').expect(404); - await request(app).get('/api/unknown').expect(404); - - expect('test').toBe('test'); - }); -}); diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts deleted file mode 100644 index 617d5055db..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/DefaultRootHttpRouter.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { RootHttpRouterService } from '@backstage/backend-plugin-api'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - DefaultRootHttpRouter as _DefaultRootHttpRouter, - DefaultRootHttpRouterOptions as _DefaultRootHttpRouterOptions, -} from '../../../../../backend-defaults/src/entrypoints/rootHttpRouter/DefaultRootHttpRouter'; -import { Handler } from 'express'; - -/** - * Options for the {@link DefaultRootHttpRouter} class. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type DefaultRootHttpRouterOptions = _DefaultRootHttpRouterOptions; - -/** - * The default implementation of the {@link @backstage/backend-plugin-api#RootHttpRouterService} interface for - * {@link @backstage/backend-plugin-api#coreServices.rootHttpRouter}. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export class DefaultRootHttpRouter implements RootHttpRouterService { - static create(options?: DefaultRootHttpRouterOptions) { - return new DefaultRootHttpRouter(_DefaultRootHttpRouter.create(options)); - } - - private constructor(private readonly impl: RootHttpRouterService) {} - - use(path: string, handler: Handler) { - this.impl.use(path, handler); - } - - handler(): Handler { - return (this.impl as any).handler(); - } -} diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts deleted file mode 100644 index 8437399fec..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { - rootHttpRouterServiceFactory, - type RootHttpRouterFactoryOptions, - type RootHttpRouterConfigureContext, -} from './rootHttpRouterServiceFactory'; -export { - DefaultRootHttpRouter, - type DefaultRootHttpRouterOptions, -} from './DefaultRootHttpRouter'; diff --git a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterServiceFactory.ts b/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterServiceFactory.ts deleted file mode 100644 index bef1913645..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootHttpRouter/rootHttpRouterServiceFactory.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - rootHttpRouterServiceFactory as _rootHttpRouterServiceFactory, - RootHttpRouterFactoryOptions as _RootHttpRouterFactoryOptions, - RootHttpRouterConfigureContext as _RootHttpRouterConfigureContext, -} from '../../../../../backend-defaults/src/entrypoints/rootHttpRouter/rootHttpRouterServiceFactory'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type RootHttpRouterConfigureContext = _RootHttpRouterConfigureContext; - -/** - * HTTP route registration for root services. - * - * See {@link @backstage/code-plugin-api#RootHttpRouterService} - * and {@link https://backstage.io/docs/backend-system/core-services/root-http-router | the service docs} - * for more information. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export type RootHttpRouterFactoryOptions = _RootHttpRouterFactoryOptions; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootHttpRouter` instead. - */ -export const rootHttpRouterServiceFactory = _rootHttpRouterServiceFactory; diff --git a/packages/backend-app-api/src/services/implementations/rootLifecycle/index.ts b/packages/backend-app-api/src/services/implementations/rootLifecycle/index.ts deleted file mode 100644 index 86589cd23e..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootLifecycle/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { rootLifecycleServiceFactory } from './rootLifecycleServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.test.ts b/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.test.ts deleted file mode 100644 index 5992e38d50..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { BackendLifecycleImpl } from './rootLifecycleServiceFactory'; -import { mockServices } from '@backstage/backend-test-utils'; - -describe('lifecycleService', () => { - it('should execute registered shutdown hook', async () => { - const service = new BackendLifecycleImpl(mockServices.logger.mock()); - const hook = jest.fn(); - service.addShutdownHook(() => hook()); - // should not execute the hook more than once. - await service.shutdown(); - await service.shutdown(); - await service.shutdown(); - expect(hook).toHaveBeenCalledTimes(1); - }); - - it('should not throw errors', async () => { - const service = new BackendLifecycleImpl(mockServices.logger.mock()); - service.addShutdownHook(() => { - throw new Error('oh no'); - }); - await expect(service.shutdown()).resolves.toBeUndefined(); - }); - - it('should not throw async errors', async () => { - const service = new BackendLifecycleImpl(mockServices.logger.mock()); - service.addShutdownHook(async () => { - throw new Error('oh no'); - }); - await expect(service.shutdown()).resolves.toBeUndefined(); - }); - - it('should reject hooks after trigger', async () => { - const service = new BackendLifecycleImpl(mockServices.logger.mock()); - await service.startup(); - expect(() => { - service.addStartupHook(() => {}); - }).toThrow('Attempted to add startup hook after startup'); - - await service.shutdown(); - expect(() => { - service.addShutdownHook(() => {}); - }).toThrow('Attempted to add shutdown hook after shutdown'); - }); -}); diff --git a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.ts b/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.ts deleted file mode 100644 index bf5dc09b80..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootLifecycle/rootLifecycleServiceFactory.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createServiceFactory, - coreServices, - LifecycleServiceStartupHook, - LifecycleServiceStartupOptions, - LifecycleServiceShutdownHook, - LifecycleServiceShutdownOptions, - RootLifecycleService, - LoggerService, -} from '@backstage/backend-plugin-api'; - -/** - * @internal - * @deprecated - */ -export class BackendLifecycleImpl implements RootLifecycleService { - constructor(private readonly logger: LoggerService) {} - - #hasStarted = false; - #startupTasks: Array<{ - hook: LifecycleServiceStartupHook; - options?: LifecycleServiceStartupOptions; - }> = []; - - addStartupHook( - hook: LifecycleServiceStartupHook, - options?: LifecycleServiceStartupOptions, - ): void { - if (this.#hasStarted) { - throw new Error('Attempted to add startup hook after startup'); - } - this.#startupTasks.push({ hook, options }); - } - - async startup(): Promise { - if (this.#hasStarted) { - return; - } - this.#hasStarted = true; - - this.logger.debug(`Running ${this.#startupTasks.length} startup tasks...`); - await Promise.all( - this.#startupTasks.map(async ({ hook, options }) => { - const logger = options?.logger ?? this.logger; - try { - await hook(); - logger.debug(`Startup hook succeeded`); - } catch (error) { - logger.error(`Startup hook failed, ${error}`); - } - }), - ); - } - - #hasShutdown = false; - #shutdownTasks: Array<{ - hook: LifecycleServiceShutdownHook; - options?: LifecycleServiceShutdownOptions; - }> = []; - - addShutdownHook( - hook: LifecycleServiceShutdownHook, - options?: LifecycleServiceShutdownOptions, - ): void { - if (this.#hasShutdown) { - throw new Error('Attempted to add shutdown hook after shutdown'); - } - this.#shutdownTasks.push({ hook, options }); - } - - async shutdown(): Promise { - if (this.#hasShutdown) { - return; - } - this.#hasShutdown = true; - - this.logger.debug( - `Running ${this.#shutdownTasks.length} shutdown tasks...`, - ); - await Promise.all( - this.#shutdownTasks.map(async ({ hook, options }) => { - const logger = options?.logger ?? this.logger; - try { - await hook(); - logger.debug(`Shutdown hook succeeded`); - } catch (error) { - logger.error(`Shutdown hook failed, ${error}`); - } - }), - ); - } -} - -/** - * Allows plugins to register shutdown hooks that are run when the process is about to exit. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootLifecycle` instead. - */ -export const rootLifecycleServiceFactory = createServiceFactory({ - service: coreServices.rootLifecycle, - deps: { - logger: coreServices.rootLogger, - }, - async factory({ logger }) { - return new BackendLifecycleImpl(logger); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/rootLogger/index.ts b/packages/backend-app-api/src/services/implementations/rootLogger/index.ts deleted file mode 100644 index 366cd53163..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootLogger/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { rootLoggerServiceFactory } from './rootLoggerServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/rootLogger/rootLoggerServiceFactory.ts b/packages/backend-app-api/src/services/implementations/rootLogger/rootLoggerServiceFactory.ts deleted file mode 100644 index af1931c438..0000000000 --- a/packages/backend-app-api/src/services/implementations/rootLogger/rootLoggerServiceFactory.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { rootLoggerServiceFactory as _rootLoggerServiceFactory } from '../../../../../backend-defaults/src/entrypoints/rootLogger/rootLoggerServiceFactory'; - -/** - * Root-level logging. - * - * See {@link @backstage/code-plugin-api#RootLoggerService} - * and {@link https://backstage.io/docs/backend-system/core-services/root-logger | the service docs} - * for more information. - * - * @public - * @deprecated Please import from `@backstage/backend-defaults/rootLogger` instead. - */ -export const rootLoggerServiceFactory = _rootLoggerServiceFactory; diff --git a/packages/backend-app-api/src/services/implementations/scheduler/index.ts b/packages/backend-app-api/src/services/implementations/scheduler/index.ts deleted file mode 100644 index 8b67006a4d..0000000000 --- a/packages/backend-app-api/src/services/implementations/scheduler/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { schedulerServiceFactory } from './schedulerServiceFactory'; 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 deleted file mode 100644 index 7b889b3a0e..0000000000 --- a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { coreServices } from '@backstage/backend-plugin-api'; -import { ServiceFactoryTester } from '@backstage/backend-test-utils'; -import { schedulerServiceFactory } from './schedulerServiceFactory'; - -describe('schedulerFactory', () => { - it('creates sidecar database features', async () => { - const tester = ServiceFactoryTester.from(schedulerServiceFactory()); - - const scheduler = await tester.get(); - await scheduler.scheduleTask({ - id: 'task1', - timeout: { seconds: 1 }, - frequency: { seconds: 1 }, - fn: async () => {}, - }); - - const database = await tester.getService(coreServices.database); - - const client = await database.getClient(); - await expect( - client.from('backstage_backend_tasks__tasks').count(), - ).resolves.toEqual([{ 'count(*)': 1 }]); - await expect( - client.from('backstage_backend_tasks__knex_migrations').count(), - ).resolves.toEqual([{ 'count(*)': expect.any(Number) }]); - await expect( - client.from('backstage_backend_tasks__knex_migrations_lock').count(), - ).resolves.toEqual([{ 'count(*)': expect.any(Number) }]); - }); -}); diff --git a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.ts b/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.ts deleted file mode 100644 index b4370761d9..0000000000 --- a/packages/backend-app-api/src/services/implementations/scheduler/schedulerServiceFactory.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { TaskScheduler } from '@backstage/backend-tasks'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/scheduler` instead. - */ -export const schedulerServiceFactory = createServiceFactory({ - service: coreServices.scheduler, - deps: { - plugin: coreServices.pluginMetadata, - databaseManager: coreServices.database, - logger: coreServices.logger, - }, - async factory({ plugin, databaseManager, logger }) { - return TaskScheduler.forPlugin({ - pluginId: plugin.getId(), - databaseManager, - logger, - }); - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/tokenManager/index.ts b/packages/backend-app-api/src/services/implementations/tokenManager/index.ts deleted file mode 100644 index 3ae3d45d2f..0000000000 --- a/packages/backend-app-api/src/services/implementations/tokenManager/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { tokenManagerServiceFactory } from './tokenManagerServiceFactory'; 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 deleted file mode 100644 index f603c6295e..0000000000 --- a/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { tokenManagerServiceFactory } from './tokenManagerServiceFactory'; -import { ServiceFactoryTester } from '@backstage/backend-test-utils'; - -describe('tokenManagerFactory', () => { - it('should create a disabled manager without configuration', async () => { - const tokenManager = await ServiceFactoryTester.from( - tokenManagerServiceFactory, - ).get(); - - await expect(tokenManager.authenticate('abc')).rejects.toThrow( - 'no legacy keys are configured', - ); - await expect(tokenManager.getToken()).rejects.toThrow( - 'no legacy keys are configured', - ); - }); -}); diff --git a/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.ts b/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.ts deleted file mode 100644 index e7c4ce7af0..0000000000 --- a/packages/backend-app-api/src/services/implementations/tokenManager/tokenManagerServiceFactory.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; -import { ServerTokenManager } from '@backstage/backend-common'; - -/** - * @public - * @deprecated Please migrate to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead - */ -export const tokenManagerServiceFactory = createServiceFactory({ - service: coreServices.tokenManager, - deps: { - config: coreServices.rootConfig, - logger: coreServices.rootLogger, - }, - createRootContext({ config, logger }) { - return ServerTokenManager.fromConfig(config, { - logger, - allowDisabledTokenManager: true, - }); - }, - async factory(_deps, tokenManager) { - return tokenManager; - }, -}); diff --git a/packages/backend-app-api/src/services/implementations/urlReader/index.ts b/packages/backend-app-api/src/services/implementations/urlReader/index.ts deleted file mode 100644 index 6a4b9f65be..0000000000 --- a/packages/backend-app-api/src/services/implementations/urlReader/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { urlReaderServiceFactory } from './urlReaderServiceFactory'; diff --git a/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts b/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts deleted file mode 100644 index 44caf25ad6..0000000000 --- a/packages/backend-app-api/src/services/implementations/urlReader/urlReaderServiceFactory.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { UrlReaders } from '@backstage/backend-common'; -import { - coreServices, - createServiceFactory, -} from '@backstage/backend-plugin-api'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/urlReader` instead. - */ -export const urlReaderServiceFactory = createServiceFactory({ - service: coreServices.urlReader, - deps: { - config: coreServices.rootConfig, - logger: coreServices.logger, - }, - async factory({ config, logger }) { - return UrlReaders.default({ - config, - logger, - }); - }, -}); diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index 3bbef5a4c0..955e463bb0 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -24,6 +24,7 @@ import { createBackendPlugin, createBackendModule, createExtensionPoint, + createBackendFeatureLoader, } from '@backstage/backend-plugin-api'; import { BackendInitializer } from './BackendInitializer'; @@ -38,14 +39,14 @@ class MockLogger { } const baseFactories = [ - lifecycleServiceFactory(), - rootLifecycleServiceFactory(), + lifecycleServiceFactory, + rootLifecycleServiceFactory, createServiceFactory({ service: coreServices.rootLogger, deps: {}, factory: () => new MockLogger(), - })(), - loggerServiceFactory(), + }), + loggerServiceFactory, ]; const testPlugin = createBackendPlugin({ @@ -56,7 +57,7 @@ const testPlugin = createBackendPlugin({ async init() {}, }); }, -})(); +}); describe('BackendInitializer', () => { it('should initialize root scoped services', async () => { @@ -83,18 +84,18 @@ describe('BackendInitializer', () => { initialization: 'always', deps: {}, factory: factory1, - })(), + }), createServiceFactory({ service: ref2, deps: {}, factory: factory2, - })(), + }), createServiceFactory({ service: ref3, initialization: 'lazy', deps: {}, factory: factory3, - })(), + }), ]; const init = new BackendInitializer(services); @@ -105,6 +106,128 @@ describe('BackendInitializer', () => { expect(factory3).not.toHaveBeenCalled(); }); + it('should discover features from feature loader', async () => { + const ref1 = createServiceRef<{ x: number }>({ + id: '1', + scope: 'root', + }); + const ref2 = createServiceRef<{ x: number }>({ + id: '2', + scope: 'plugin', + }); + const factory1 = jest.fn(); + const factory2 = jest.fn(); + + const pluginInit = jest.fn(async () => {}); + const moduleInit = jest.fn(async () => {}); + + const init = new BackendInitializer(baseFactories); + init.add( + createBackendFeatureLoader({ + *loader() { + yield createServiceFactory({ + service: ref1, + deps: {}, + factory: factory1, + }); + yield createServiceFactory({ + service: ref2, + initialization: 'always', + deps: {}, + factory: factory2, + }); + yield createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: {}, + init: pluginInit, + }); + }, + }); + yield createBackendModule({ + pluginId: 'test', + moduleId: 'tester', + register(reg) { + reg.registerInit({ + deps: {}, + init: moduleInit, + }); + }, + }); + }, + }), + ); + await init.start(); + + expect(factory1).toHaveBeenCalled(); + expect(factory2).toHaveBeenCalled(); + expect(pluginInit).toHaveBeenCalled(); + expect(moduleInit).toHaveBeenCalled(); + }); + + it('should refuse to override already initialized services through loaded features', async () => { + const ref1 = createServiceRef<{ x: number }>({ + id: '1', + scope: 'root', + }); + + const init = new BackendInitializer([ + ...baseFactories, + createServiceFactory({ + service: ref1, + deps: {}, + factory: () => ({ x: 1 }), + }), + ]); + init.add( + createBackendFeatureLoader({ + deps: { service1: ref1 }, + *loader() { + yield createServiceFactory({ + service: ref1, + deps: {}, + factory: jest.fn(), + }); + }, + }), + ); + await expect(init.start()).rejects.toThrow( + 'Unable to set service factory with id 1, service has already been instantiated', + ); + }); + + it('should refuse feature loader that depends on a plugin scoped service', async () => { + const ref1 = createServiceRef<{ x: number }>({ + id: '1', + }); + + const init = new BackendInitializer([ + ...baseFactories, + createServiceFactory({ + service: ref1, + deps: {}, + factory: () => ({ x: 1 }), + }), + ]); + init.add( + createBackendFeatureLoader({ + // @ts-expect-error + deps: { service1: ref1 }, + *loader() { + yield createServiceFactory({ + service: ref1, + deps: {}, + factory: jest.fn(), + }); + }, + }), + ); + await expect(init.start()).rejects.toThrow( + /^Feature loaders can only depend on root scoped services, but 'service1' is scoped to 'plugin'. Offending loader is created at '.*'$/, + ); + }); + it('should initialize plugin scoped services with eager initialization', async () => { const ref1 = createServiceRef<{ x: number }>({ id: '1', @@ -126,18 +249,18 @@ describe('BackendInitializer', () => { initialization: 'always', deps: {}, factory: factory1, - })(), + }), createServiceFactory({ service: ref2, deps: {}, factory: factory2, - })(), + }), createServiceFactory({ service: ref3, initialization: 'lazy', deps: {}, factory: factory3, - })(), + }), ]; const init = new BackendInitializer(services); @@ -219,6 +342,62 @@ describe('BackendInitializer', () => { await init.start(); }); + it('should allow plugins and modules depend on multiton services', async () => { + expect.assertions(2); + + const multiServiceRef = createServiceRef({ + id: 'a', + multiton: true, + }); + const init = new BackendInitializer(baseFactories); + + init.add( + createServiceFactory({ + service: multiServiceRef, + deps: {}, + factory: () => 'x', + }), + ); + init.add( + createServiceFactory({ + service: multiServiceRef, + deps: {}, + factory: () => 'y', + }), + ); + + init.add( + createBackendPlugin({ + pluginId: 'test', + register(reg) { + reg.registerInit({ + deps: { multi: multiServiceRef }, + async init({ multi }) { + expect(multi).toEqual(['x', 'y']); + }, + }); + }, + }), + ); + + init.add( + createBackendModule({ + pluginId: 'test', + moduleId: 'test', + register(reg) { + reg.registerInit({ + deps: { multi: multiServiceRef }, + async init({ multi }) { + expect(multi).toEqual(['x', 'y']); + }, + }); + }, + }), + ); + + await init.start(); + }); + it('should forward errors when plugins fail to start', async () => { const init = new BackendInitializer([]); init.add( @@ -326,12 +505,12 @@ describe('BackendInitializer', () => { const extA = createExtensionPoint({ id: 'a' }); const extB = createExtensionPoint({ id: 'b' }); const init = new BackendInitializer([ - rootLifecycleServiceFactory(), + rootLifecycleServiceFactory, createServiceFactory({ service: coreServices.rootLogger, deps: {}, factory: () => new MockLogger(), - })(), + }), ]); init.add(testPlugin); init.add( @@ -397,4 +576,59 @@ describe('BackendInitializer', () => { "Illegal dependency: Module 'mod' for plugin 'test' attempted to depend on extension point 'a' for plugin 'test-a'. Extension points can only be used within their plugin's scope.", ); }); + + it('should properly load double-default CJS modules', async () => { + expect.assertions(3); + + const init = new BackendInitializer(baseFactories); + init.add( + createBackendFeatureLoader({ + loader() { + return [ + createBackendPlugin({ + pluginId: 'no-double-wrapping', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + expect(true).toBeTruthy(); + }, + }); + }, + }), + { + default: createBackendPlugin({ + pluginId: 'single-wrapping', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + expect(true).toBeTruthy(); + }, + }); + }, + }), + }, + { + default: { + default: createBackendPlugin({ + pluginId: 'double-wrapping', + register(reg) { + reg.registerInit({ + deps: {}, + async init() { + expect(true).toBeTruthy(); + }, + }); + }, + }), + }, + } as any, // not typescript valid, but can happen at runtime + ]; + }, + }), + ); + + await init.start(); + }); }); diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index ddad94de14..b2173fb105 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -25,13 +25,20 @@ import { } from '@backstage/backend-plugin-api'; import { ServiceOrExtensionPoint } from './types'; // 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'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import type { + InternalBackendFeature, + InternalBackendFeatureLoader, + InternalBackendRegistrations, +} from '../../../backend-plugin-api/src/wiring/types'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import type { InternalServiceFactory } from '../../../backend-plugin-api/src/services/system/types'; import { ForwardedError, ConflictError } from '@backstage/errors'; import { featureDiscoveryServiceRef } from '@backstage/backend-plugin-api/alpha'; import { DependencyGraph } from '../lib/DependencyGraph'; import { ServiceRegistry } from './ServiceRegistry'; import { createInitializationLogger } from './createInitializationLogger'; +import { unwrapFeature } from './helpers'; export interface BackendRegisterInit { consumes: Set; @@ -44,10 +51,11 @@ export interface BackendRegisterInit { export class BackendInitializer { #startPromise?: Promise; - #features = new Array(); + #registrations = new Array(); #extensionPoints = new Map(); #serviceRegistry: ServiceRegistry; #registeredFeatures = new Array>(); + #registeredFeatureLoaders = new Array(); constructor(defaultApiFactories: ServiceFactory[]) { this.#serviceRegistry = ServiceRegistry.create([...defaultApiFactories]); @@ -101,21 +109,12 @@ export class BackendInitializer { } #addFeature(feature: BackendFeature) { - if (feature.$$type !== '@backstage/BackendFeature') { - throw new Error( - `Failed to add feature, invalid type '${feature.$$type}'`, - ); - } - if (isServiceFactory(feature)) { this.#serviceRegistry.add(feature); - } else if (isInternalBackendFeature(feature)) { - if (feature.version !== 'v1') { - throw new Error( - `Failed to add feature, invalid version '${feature.version}'`, - ); - } - this.#features.push(feature); + } else if (isBackendFeatureLoader(feature)) { + this.#registeredFeatureLoaders.push(feature); + } else if (isBackendRegistrations(feature)) { + this.#registrations.push(feature); } else { throw new Error( `Failed to add feature, invalid feature ${JSON.stringify(feature)}`, @@ -165,19 +164,21 @@ export class BackendInitializer { if (featureDiscovery) { const { features } = await featureDiscovery.getBackendFeatures(); for (const feature of features) { - this.#addFeature(feature); + this.#addFeature(unwrapFeature(feature)); } this.#serviceRegistry.checkForCircularDeps(); } + await this.#applyBackendFeatureLoaders(this.#registeredFeatureLoaders); + // Initialize all root scoped services await this.#serviceRegistry.initializeEagerServicesWithScope('root'); const pluginInits = new Map(); const moduleInits = new Map>(); - // Enumerate all features - for (const feature of this.#features) { + // Enumerate all registrations + for (const feature of this.#registrations) { for (const r of feature.getRegistrations()) { const provides = new Set>(); @@ -205,7 +206,7 @@ export class BackendInitializer { consumes: new Set(Object.values(r.init.deps)), init: r.init, }); - } else { + } else if (r.type === 'module') { let modules = moduleInits.get(r.pluginId); if (!modules) { modules = new Map(); @@ -221,6 +222,8 @@ export class BackendInitializer { consumes: new Set(Object.values(r.init.deps)), init: r.init, }); + } else { + throw new Error(`Invalid registration type '${(r as any).type}'`); } } } @@ -383,16 +386,110 @@ export class BackendInitializer { throw new Error('Unexpected plugin lifecycle service implementation'); } + + async #applyBackendFeatureLoaders(loaders: InternalBackendFeatureLoader[]) { + for (const loader of loaders) { + const deps = new Map(); + const missingRefs = new Set(); + + for (const [name, ref] of Object.entries(loader.deps ?? {})) { + if (ref.scope !== 'root') { + throw new Error( + `Feature loaders can only depend on root scoped services, but '${name}' is scoped to '${ref.scope}'. Offending loader is ${loader.description}`, + ); + } + const impl = await this.#serviceRegistry.get( + ref as ServiceRef, + 'root', + ); + if (impl) { + deps.set(name, impl); + } else { + missingRefs.add(ref); + } + } + + if (missingRefs.size > 0) { + const missing = Array.from(missingRefs).join(', '); + throw new Error( + `No service available for the following ref(s): ${missing}, depended on by feature loader ${loader.description}`, + ); + } + + const result = await loader + .loader(Object.fromEntries(deps)) + .then(features => features.map(unwrapFeature)) + .catch(error => { + throw new ForwardedError( + `Feature loader ${loader.description} failed`, + error, + ); + }); + + let didAddServiceFactory = false; + const newLoaders = new Array(); + + for await (const feature of result) { + if (isBackendFeatureLoader(feature)) { + newLoaders.push(feature); + } else { + didAddServiceFactory ||= isServiceFactory(feature); + this.#addFeature(feature); + } + } + + // Every time we add a new service factory we need to make sure that we don't have circular dependencies + if (didAddServiceFactory) { + this.#serviceRegistry.checkForCircularDeps(); + } + + // Apply loaders recursively, depth-first + if (newLoaders.length > 0) { + await this.#applyBackendFeatureLoaders(newLoaders); + } + } + } } -function isServiceFactory(feature: BackendFeature): feature is ServiceFactory { - return !!(feature as ServiceFactory).service; -} - -function isInternalBackendFeature( +function toInternalBackendFeature( feature: BackendFeature, -): feature is InternalBackendFeature { - return ( - typeof (feature as InternalBackendFeature).getRegistrations === 'function' - ); +): InternalBackendFeature { + if (feature.$$type !== '@backstage/BackendFeature') { + throw new Error(`Invalid BackendFeature, bad type '${feature.$$type}'`); + } + const internal = feature as InternalBackendFeature; + if (internal.version !== 'v1') { + throw new Error( + `Invalid BackendFeature, bad version '${internal.version}'`, + ); + } + return internal; +} + +function isServiceFactory( + feature: BackendFeature, +): feature is InternalServiceFactory { + const internal = toInternalBackendFeature(feature); + if (internal.featureType === 'service') { + return true; + } + // Backwards compatibility for v1 registrations that use duck typing + return 'service' in internal; +} + +function isBackendRegistrations( + feature: BackendFeature, +): feature is InternalBackendRegistrations { + const internal = toInternalBackendFeature(feature); + if (internal.featureType === 'registrations') { + return true; + } + // Backwards compatibility for v1 registrations that use duck typing + return 'getRegistrations' in internal; +} + +function isBackendFeatureLoader( + feature: BackendFeature, +): feature is InternalBackendFeatureLoader { + return toInternalBackendFeature(feature).featureType === 'loader'; } diff --git a/packages/backend-app-api/src/wiring/BackstageBackend.ts b/packages/backend-app-api/src/wiring/BackstageBackend.ts index 0dd3ba4423..49a8bbbbfb 100644 --- a/packages/backend-app-api/src/wiring/BackstageBackend.ts +++ b/packages/backend-app-api/src/wiring/BackstageBackend.ts @@ -16,6 +16,7 @@ import { BackendFeature, ServiceFactory } from '@backstage/backend-plugin-api'; import { BackendInitializer } from './BackendInitializer'; +import { unwrapFeature } from './helpers'; import { Backend } from './types'; export class BackstageBackend implements Backend { @@ -25,12 +26,7 @@ export class BackstageBackend implements Backend { this.#initializer = new BackendInitializer(defaultServiceFactories); } - add( - feature: - | BackendFeature - | (() => BackendFeature) - | Promise<{ default: BackendFeature | (() => BackendFeature) }>, - ): void { + add(feature: BackendFeature | Promise<{ default: BackendFeature }>): void { if (isPromise(feature)) { this.#initializer.add(feature.then(f => unwrapFeature(f.default))); } else { @@ -55,31 +51,3 @@ function isPromise(value: unknown | Promise): value is Promise { typeof value.then === 'function' ); } - -function unwrapFeature( - feature: - | BackendFeature - | (() => BackendFeature) - | { default: BackendFeature | (() => BackendFeature) }, -): BackendFeature { - if (typeof feature === 'function') { - return feature(); - } - - if ('$$type' in feature) { - return feature; - } - - // This is a workaround where default exports get transpiled to `exports['default'] = ...` - // in CommonJS modules, which in turn results in a double `{ default: { default: ... } }` nesting - // when importing using a dynamic import. - // TODO: This is a broader issue than just this piece of code, and should move away from CommonJS. - if ('default' in feature) { - const defaultFeature = feature.default; - return typeof defaultFeature === 'function' - ? defaultFeature() - : defaultFeature; - } - - return feature; -} diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts index 32807ca826..90ee6344e5 100644 --- a/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.test.ts @@ -95,7 +95,7 @@ describe('ServiceRegistry', () => { }); it('should return an implementation for a registered ref', async () => { - const registry = ServiceRegistry.create([sf1()]); + const registry = ServiceRegistry.create([sf1]); await expect(registry.get(ref1, 'catalog')).resolves.toEqual({ x: 1 }); await expect(registry.get(ref1, 'scaffolder')).resolves.toEqual({ x: 1 }); expect(await registry.get(ref1, 'catalog')).toBe( @@ -110,7 +110,7 @@ describe('ServiceRegistry', () => { }); it('should handle multiple factories with different serviceRefs', async () => { - const registry = ServiceRegistry.create([sf1(), sf2()]); + const registry = ServiceRegistry.create([sf1, sf2]); await expect(registry.get(ref1, 'catalog')).resolves.toEqual({ x: 1, @@ -124,15 +124,15 @@ describe('ServiceRegistry', () => { }); it('should not be possible for root scoped services to depend on plugin scoped services', async () => { + // @ts-expect-error const factory = createServiceFactory({ - // @ts-expect-error service: ref2, deps: { pluginDep: ref1 }, async factory() { return { x: 2 }; }, }); - const registry = ServiceRegistry.create([factory(), sf1()]); + const registry = ServiceRegistry.create([factory, sf1]); await expect(registry.get(ref2, 'catalog')).rejects.toThrow( "Failed to instantiate 'root' scoped service '2' because it depends on 'plugin' scoped service '1'.", ); @@ -146,7 +146,7 @@ describe('ServiceRegistry', () => { return { x: rootDep.x }; }, }); - const registry = ServiceRegistry.create([factory(), sf2()]); + const registry = ServiceRegistry.create([factory, sf2]); await expect(registry.get(ref1, 'catalog')).resolves.toEqual({ x: 2, }); @@ -161,7 +161,7 @@ describe('ServiceRegistry', () => { return { x: rootDep.x }; }, }); - const registry = ServiceRegistry.create([factory(), sf2()]); + const registry = ServiceRegistry.create([factory, sf2]); await expect(registry.get(ref, 'catalog')).resolves.toEqual({ x: 2, }); @@ -176,41 +176,41 @@ describe('ServiceRegistry', () => { return { pluginId: meta.getId() }; }, }); - const registry = ServiceRegistry.create([factory()]); + const registry = ServiceRegistry.create([factory]); await expect(registry.get(ref, 'catalog')).resolves.toEqual({ pluginId: 'catalog', }); }); it('should use the last factory for each ref', async () => { - const registry = ServiceRegistry.create([sf2(), sf2b()]); + const registry = ServiceRegistry.create([sf2, sf2b]); await expect(registry.get(ref2, 'catalog')).resolves.toEqual({ x: 22, }); }); it('should use added service factories for each ref', async () => { - const registry = ServiceRegistry.create([sf2()]); - registry.add(sf2b()); + const registry = ServiceRegistry.create([sf2]); + registry.add(sf2b); await expect(registry.get(ref2, 'catalog')).resolves.toEqual({ x: 22, }); }); it('should not allow factories to be added after instantiation', async () => { - const registry = ServiceRegistry.create([sf2()]); + const registry = ServiceRegistry.create([sf2]); await expect(registry.get(ref2, 'catalog')).resolves.toEqual({ x: 2, }); - expect(() => registry.add(sf2b())).toThrow( + expect(() => registry.add(sf2b)).toThrow( 'Unable to set service factory with id 2, service has already been instantiated', ); }); it('should not allow the same factory to be added twice', async () => { - const registry = ServiceRegistry.create([sf2()]); - registry.add(sf2b()); - expect(() => registry.add(sf2b())).toThrow( + const registry = ServiceRegistry.create([sf2]); + registry.add(sf2b); + expect(() => registry.add(sf2b)).toThrow( 'Duplicate service implementations provided for 2', ); }); @@ -223,7 +223,7 @@ describe('ServiceRegistry', () => { }); it('should not use the defaultFactory from the ref if provided to the registry', async () => { - const registry = ServiceRegistry.create([sf1()]); + const registry = ServiceRegistry.create([sf1]); await expect(registry.get(refDefault1, 'catalog')).resolves.toEqual({ x: 1, }); @@ -279,7 +279,7 @@ describe('ServiceRegistry', () => { factory, }); - const registry = ServiceRegistry.create([myFactory()]); + const registry = ServiceRegistry.create([myFactory]); await Promise.all([ registry.get(ref1, 'catalog')!, @@ -301,7 +301,7 @@ describe('ServiceRegistry', () => { factory, }); - const registry = ServiceRegistry.create([myFactory()]); + const registry = ServiceRegistry.create([myFactory]); await Promise.all([ registry.get(ref1, 'catalog')!, @@ -323,7 +323,7 @@ describe('ServiceRegistry', () => { }, }); - const registry = ServiceRegistry.create([myFactory()]); + const registry = ServiceRegistry.create([myFactory]); await expect(registry.get(ref1, 'catalog')).rejects.toThrow( "Failed to instantiate service '1' for 'catalog' because the following dependent services are missing: '2'", @@ -350,7 +350,7 @@ describe('ServiceRegistry', () => { }, }); - const registry = ServiceRegistry.create([factoryA(), factoryB()]); + const registry = ServiceRegistry.create([factoryA, factoryB]); await expect(registry.get(refA, 'catalog')).rejects.toThrow( "Failed to instantiate service 'a' for 'catalog' because the factory function threw an error, Error: Failed to instantiate service 'b' for 'catalog' because the following dependent services are missing: 'c', 'd'", @@ -374,7 +374,7 @@ describe('ServiceRegistry', () => { factory: async ({ a }) => a, }); - expect(() => ServiceRegistry.create([factoryA(), factoryB()])).toThrow( + expect(() => ServiceRegistry.create([factoryA, factoryB])).toThrow( `Circular dependencies detected: 'a' -> 'b' -> 'a'`, ); @@ -411,12 +411,7 @@ describe('ServiceRegistry', () => { }); expect(() => - ServiceRegistry.create([ - factoryA(), - factoryB(), - factoryC(), - factoryD(), - ]), + ServiceRegistry.create([factoryA, factoryB, factoryC, factoryD]), ).toThrow( `Circular dependencies detected: 'a' -> 'b' -> 'a' @@ -448,7 +443,7 @@ describe('ServiceRegistry', () => { }); expect(() => - ServiceRegistry.create([factoryA(), factoryB(), factoryC()]), + ServiceRegistry.create([factoryA, factoryB, factoryC]), ).toThrow( `Circular dependencies detected: 'a' -> 'b' -> 'c' -> 'a'`, @@ -486,12 +481,7 @@ describe('ServiceRegistry', () => { }); expect(() => - ServiceRegistry.create([ - factoryA(), - factoryB(), - factoryC(), - factoryD(), - ]), + ServiceRegistry.create([factoryA, factoryB, factoryC, factoryD]), ).toThrow( `Circular dependencies detected: 'a' -> 'b' -> 'c' -> 'a'`, @@ -522,7 +512,7 @@ describe('ServiceRegistry', () => { }); expect(() => - ServiceRegistry.create([factoryA(), factoryB(), factoryC()]), + ServiceRegistry.create([factoryA, factoryB, factoryC]), ).toThrow( `Circular dependencies detected: 'a' -> 'c' -> 'a'`, @@ -553,7 +543,7 @@ describe('ServiceRegistry', () => { }); expect(() => - ServiceRegistry.create([factoryA(), factoryB(), factoryC()]), + ServiceRegistry.create([factoryA, factoryB, factoryC]), ).toThrow( `Circular dependencies detected: 'b' -> 'c' -> 'b'`, @@ -573,7 +563,7 @@ describe('ServiceRegistry', () => { }, }); - const registry = ServiceRegistry.create([myFactory()]); + const registry = ServiceRegistry.create([myFactory]); await expect(registry.get(ref1, 'catalog')).rejects.toThrow( "Failed to instantiate service '1' because createRootContext threw an error, Error: top-level error", @@ -589,7 +579,7 @@ describe('ServiceRegistry', () => { }, }); - const registry = ServiceRegistry.create([myFactory()]); + const registry = ServiceRegistry.create([myFactory]); await expect(registry.get(ref1, 'catalog')).rejects.toThrow( "Failed to instantiate service '1' for 'catalog' because the factory function threw an error, Error: error in plugin", diff --git a/packages/backend-app-api/src/wiring/ServiceRegistry.ts b/packages/backend-app-api/src/wiring/ServiceRegistry.ts index 6e20dd98e8..0abd5605ae 100644 --- a/packages/backend-app-api/src/wiring/ServiceRegistry.ts +++ b/packages/backend-app-api/src/wiring/ServiceRegistry.ts @@ -48,22 +48,34 @@ 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 { - const registry = new ServiceRegistry(factories); + const factoryMap = new Map(); + for (const factory of factories) { + if (factory.service.multiton) { + const existing = factoryMap.get(factory.service.id) ?? []; + factoryMap.set( + factory.service.id, + existing.concat(toInternalServiceFactory(factory)), + ); + } else { + factoryMap.set(factory.service.id, [toInternalServiceFactory(factory)]); + } + } + const registry = new ServiceRegistry(factoryMap); registry.checkForCircularDeps(); return registry; } - readonly #providedFactories: Map; + readonly #providedFactories: Map; readonly #loadedDefaultFactories: Map< Function, Promise @@ -82,10 +94,8 @@ export class ServiceRegistry { readonly #addedFactoryIds = new Set(); readonly #instantiatedFactories = new Set(); - private constructor(factories: Array) { - this.#providedFactories = new Map( - factories.map(sf => [sf.service.id, toInternalServiceFactory(sf)]), - ); + private constructor(factories: Map) { + this.#providedFactories = factories; this.#loadedDefaultFactories = new Map(); this.#implementations = new Map(); } @@ -93,17 +103,17 @@ export class ServiceRegistry { #resolveFactory( ref: ServiceRef, pluginId: string, - ): Promise | undefined { + ): Promise | undefined { // 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 })), - ); + return Promise.resolve([ + toInternalServiceFactory(createPluginMetadataServiceFactory(pluginId)), + ]); } let resolvedFactory: - | Promise - | InternalServiceFactory + | Promise + | InternalServiceFactory[] | undefined = this.#providedFactories.get(ref.id); const { __defaultFactory: defaultFactory } = ref as InternalServiceRef; if (!resolvedFactory && !defaultFactory) { @@ -120,15 +130,18 @@ export class ServiceRegistry { ); this.#loadedDefaultFactories.set(defaultFactory!, loadedFactory); } - resolvedFactory = loadedFactory.catch(error => { - throw new Error( - `Failed to instantiate service '${ - ref.id - }' because the default factory loader threw an error, ${stringifyError( - error, - )}`, - ); - }); + resolvedFactory = loadedFactory.then( + factory => [factory], + error => { + throw new Error( + `Failed to instantiate service '${ + ref.id + }' because the default factory loader threw an error, ${stringifyError( + error, + )}`, + ); + }, + ); } return Promise.resolve(resolvedFactory); @@ -142,6 +155,9 @@ export class ServiceRegistry { if (this.#providedFactories.get(ref.id)) { return false; } + if (ref.multiton) { + return false; + } return !(ref as InternalServiceRef).__defaultFactory; }); @@ -156,13 +172,13 @@ export class ServiceRegistry { checkForCircularDeps(): void { const graph = DependencyGraph.fromIterable( - Array.from(this.#providedFactories).map( - ([serviceId, serviceFactory]) => ({ - value: serviceId, - provides: [serviceId], - consumes: Object.values(serviceFactory.deps).map(d => d.id), - }), - ), + Array.from(this.#providedFactories).map(([serviceId, factories]) => ({ + value: serviceId, + provides: [serviceId], + consumes: factories.flatMap(factory => + Object.values(factory.deps).map(d => d.id), + ), + })), ); const circularDependencies = Array.from(graph.detectCircularDependencies()); @@ -183,27 +199,36 @@ export class ServiceRegistry { ); } - if (this.#addedFactoryIds.has(factoryId)) { - throw new Error( - `Duplicate service implementations provided for ${factoryId}`, - ); - } - if (this.#instantiatedFactories.has(factoryId)) { throw new Error( `Unable to set service factory with id ${factoryId}, service has already been instantiated`, ); } - this.#addedFactoryIds.add(factoryId); - this.#providedFactories.set(factoryId, toInternalServiceFactory(factory)); + if (factory.service.multiton) { + const newFactories = ( + this.#providedFactories.get(factoryId) ?? [] + ).concat(toInternalServiceFactory(factory)); + this.#providedFactories.set(factoryId, newFactories); + } else { + if (this.#addedFactoryIds.has(factoryId)) { + throw new Error( + `Duplicate service implementations provided for ${factoryId}`, + ); + } + + this.#addedFactoryIds.add(factoryId); + this.#providedFactories.set(factoryId, [ + toInternalServiceFactory(factory), + ]); + } } async initializeEagerServicesWithScope( scope: 'root' | 'plugin', pluginId: string = 'root', ) { - for (const factory of this.#providedFactories.values()) { + for (const [factory] of this.#providedFactories.values()) { if (factory.service.scope === scope) { // Root-scoped services are eager by default, plugin-scoped are lazy by default if (scope === 'root' && factory.initialization !== 'lazy') { @@ -215,88 +240,112 @@ export class ServiceRegistry { } } - get(ref: ServiceRef, pluginId: string): Promise | undefined { + get( + ref: ServiceRef, + pluginId: string, + ): Promise | undefined { this.#instantiatedFactories.add(ref.id); - return this.#resolveFactory(ref, pluginId)?.then(factory => { - if (factory.service.scope === 'root') { - let existing = this.#rootServiceImplementations.get(factory); - if (!existing) { - this.#checkForMissingDeps(factory, pluginId); - const rootDeps = new Array>(); + const resolvedFactory = this.#resolveFactory(ref, pluginId); - for (const [name, serviceRef] of Object.entries(factory.deps)) { - if (serviceRef.scope !== 'root') { - throw new Error( - `Failed to instantiate 'root' scoped service '${ref.id}' because it depends on '${serviceRef.scope}' scoped service '${serviceRef.id}'.`, - ); + if (!resolvedFactory) { + return ref.multiton + ? (Promise.resolve([]) as + | Promise + | undefined) + : undefined; + } + + return resolvedFactory + .then(factories => { + return Promise.all( + factories.map(factory => { + if (factory.service.scope === 'root') { + let existing = this.#rootServiceImplementations.get(factory); + if (!existing) { + this.#checkForMissingDeps(factory, pluginId); + const rootDeps = new Array< + Promise<[name: string, impl: unknown]> + >(); + + for (const [name, serviceRef] of Object.entries(factory.deps)) { + if (serviceRef.scope !== 'root') { + throw new Error( + `Failed to instantiate 'root' scoped service '${ref.id}' because it depends on '${serviceRef.scope}' scoped service '${serviceRef.id}'.`, + ); + } + const target = this.get(serviceRef, pluginId)!; + rootDeps.push(target.then(impl => [name, impl])); + } + + existing = Promise.all(rootDeps).then(entries => + factory.factory(Object.fromEntries(entries), undefined), + ); + this.#rootServiceImplementations.set(factory, existing); + } + return existing as Promise; } - const target = this.get(serviceRef, pluginId)!; - rootDeps.push(target.then(impl => [name, impl])); - } - existing = Promise.all(rootDeps).then(entries => - factory.factory(Object.fromEntries(entries), undefined), - ); - this.#rootServiceImplementations.set(factory, existing); - } - return existing as Promise; - } + let implementation = this.#implementations.get(factory); + if (!implementation) { + this.#checkForMissingDeps(factory, pluginId); + const rootDeps = new Array< + Promise<[name: string, impl: unknown]> + >(); - let implementation = this.#implementations.get(factory); - if (!implementation) { - this.#checkForMissingDeps(factory, pluginId); - const rootDeps = new Array>(); + for (const [name, serviceRef] of Object.entries(factory.deps)) { + if (serviceRef.scope === 'root') { + const target = this.get(serviceRef, pluginId)!; + rootDeps.push(target.then(impl => [name, impl])); + } + } - for (const [name, serviceRef] of Object.entries(factory.deps)) { - if (serviceRef.scope === 'root') { - const target = this.get(serviceRef, pluginId)!; - rootDeps.push(target.then(impl => [name, impl])); - } - } + implementation = { + context: Promise.all(rootDeps) + .then(entries => + factory.createRootContext?.(Object.fromEntries(entries)), + ) + .catch(error => { + const cause = stringifyError(error); + throw new Error( + `Failed to instantiate service '${ref.id}' because createRootContext threw an error, ${cause}`, + ); + }), + byPlugin: new Map(), + }; - implementation = { - context: Promise.all(rootDeps) - .then(entries => - factory.createRootContext?.(Object.fromEntries(entries)), - ) - .catch(error => { - const cause = stringifyError(error); - throw new Error( - `Failed to instantiate service '${ref.id}' because createRootContext threw an error, ${cause}`, - ); - }), - byPlugin: new Map(), - }; + this.#implementations.set(factory, implementation); + } - this.#implementations.set(factory, implementation); - } + let result = implementation.byPlugin.get(pluginId) as Promise; + if (!result) { + const allDeps = new Array< + Promise<[name: string, impl: unknown]> + >(); - let result = implementation.byPlugin.get(pluginId) as Promise; - if (!result) { - const allDeps = new Array>(); + for (const [name, serviceRef] of Object.entries(factory.deps)) { + const target = this.get(serviceRef, pluginId)!; + allDeps.push(target.then(impl => [name, impl])); + } - for (const [name, serviceRef] of Object.entries(factory.deps)) { - const target = this.get(serviceRef, pluginId)!; - allDeps.push(target.then(impl => [name, impl])); - } - - result = implementation.context - .then(context => - Promise.all(allDeps).then(entries => - factory.factory(Object.fromEntries(entries), context), - ), - ) - .catch(error => { - const cause = stringifyError(error); - throw new Error( - `Failed to instantiate service '${ref.id}' for '${pluginId}' because the factory function threw an error, ${cause}`, - ); - }); - implementation.byPlugin.set(pluginId, result); - } - - return result; - }); + result = implementation.context + .then(context => + Promise.all(allDeps).then(entries => + factory.factory(Object.fromEntries(entries), context), + ), + ) + .catch(error => { + const cause = stringifyError(error); + throw new Error( + `Failed to instantiate service '${ref.id}' for '${pluginId}' because the factory function threw an error, ${cause}`, + ); + }); + implementation.byPlugin.set(pluginId, result); + } + return result; + }), + ); + }) + .then(results => (ref.multiton ? results : results[0])); } } 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/helpers.ts b/packages/backend-app-api/src/wiring/helpers.ts new file mode 100644 index 0000000000..ffb7e3b079 --- /dev/null +++ b/packages/backend-app-api/src/wiring/helpers.ts @@ -0,0 +1,36 @@ +/* + * 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 { BackendFeature } from '@backstage/backend-plugin-api'; + +/** @internal */ +export function unwrapFeature( + feature: BackendFeature | { default: BackendFeature }, +): BackendFeature { + if ('$$type' in feature) { + return feature; + } + + // This is a workaround where default exports get transpiled to `exports['default'] = ...` + // in CommonJS modules, which in turn results in a double `{ default: { default: ... } }` nesting + // when importing using a dynamic import. + // TODO: This is a broader issue than just this piece of code, and should move away from CommonJS. + if ('default' in feature) { + return feature.default; + } + + return feature; +} diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts index 027b2bf254..bd761ff3c8 100644 --- a/packages/backend-app-api/src/wiring/types.ts +++ b/packages/backend-app-api/src/wiring/types.ts @@ -18,19 +18,14 @@ import { BackendFeature, ExtensionPoint, ServiceRef, - ServiceFactoryOrFunction, + ServiceFactory, } from '@backstage/backend-plugin-api'; /** * @public */ export interface Backend { - add( - feature: - | BackendFeature - | (() => BackendFeature) - | Promise<{ default: BackendFeature | (() => BackendFeature) }>, - ): void; + add(feature: BackendFeature | Promise<{ default: BackendFeature }>): void; start(): Promise; stop(): Promise; } @@ -39,7 +34,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..40ba8fb687 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,188 @@ # @backstage/backend-common +## 0.24.0 + +### Minor Changes + +- 389f5a4: **BREAKING**: Removed the following `Url Reader` deprecated exports: + + - UrlReader: Use `UrlReaderService` from `@backstage/backend-plugin-api` instead; + - AzureUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - BitbucketUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - BitbucketCloudUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - BitbucketServerUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GithubUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GitlabUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GerritUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - GiteaUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - HarnessUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - AwsS3UrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - FetchUrlReader: Import from `@backstage/backend-defaults/urlReader` instead; + - UrlReaders: Import from `@backstage/backend-defaults/urlReader` instead; + - UrlReadersOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - UrlReaderPredicateTuple: Import from `@backstage/backend-defaults/urlReader` instead; + - FromReadableArrayOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - ReaderFactory: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadUrlOptions:Use `UrlReaderServiceReadUrlOptions` from `@backstage/backend-plugin-api` instead; + - ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` from `@backstage/backend-plugin-api` instead; + - ReadUrlResponseFactory: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadUrlResponseFactoryFromStreamOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadTreeOptions: Use `UrlReaderServiceReadTreeOptions` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponse: Use `UrlReaderServiceReadTreeResponse` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponseFile: Use `UrlReaderServiceReadTreeResponseFile` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponseDirOptions: Use `UrlReaderServiceReadTreeResponseDirOptions` from `@backstage/backend-plugin-api` instead; + - ReadTreeResponseFactory: Import from `@backstage/backend-defaults/urlReader` instead; + - ReadTreeResponseFactoryOptions: Import from `@backstage/backend-defaults/urlReader` instead; + - SearchOptions: Use `UrlReaderServiceSearchOptions` from `@backstage/backend-plugin-api` instead; + - SearchResponse: Use `UrlReaderServiceSearchResponse` from `@backstage/backend-plugin-api` instead; + - SearchResponseFile: Use `UrlReaderServiceSearchResponseFile` from `@backstage/backend-plugin-api` instead. + +### 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. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 6795e33: This package is marked as `deprecated` and will be removed in a near future, please follow the deprecated instructions for the exports you still use. +- 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). + +- ddde5fe: Internal type refactor. +- b63d378: export `createConfigSecretEnumerator` from `@backstage/backend-common` instead of `@backstage/backend-app-api`. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/integration@1.14.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 + +## 0.23.4-next.3 + +### Patch Changes + +- ddde5fe: Internal type refactor. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 0.23.4-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.23.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/backend-dev-utils@0.1.4 + - @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.1 + +## 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/README.md b/packages/backend-common/README.md index 71a15bfb5a..e9f2f43e83 100644 --- a/packages/backend-common/README.md +++ b/packages/backend-common/README.md @@ -1,5 +1,8 @@ # @backstage/backend-common +> [!CAUTION] +> This package is deprecated and will be removed in a near future, so please follow the deprecated instructions for the exports you still use. + Common functionality library for Backstage backends, implementing logging, error handling and similar. diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 52b661bcaf..7de2731518 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -9,32 +9,19 @@ import { AppConfig } from '@backstage/config'; import { AuthCallback } from 'isomorphic-git'; import { AuthService } from '@backstage/backend-plugin-api'; -import { AwsCredentialsManager } from '@backstage/integration-aws-node'; -import { AwsS3Integration } from '@backstage/integration'; -import { AzureDevOpsCredentialsProvider } from '@backstage/integration'; -import { AzureIntegration } from '@backstage/integration'; -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; -import { BitbucketCloudIntegration } from '@backstage/integration'; -import { BitbucketIntegration } from '@backstage/integration'; -import { BitbucketServerIntegration } from '@backstage/integration'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CacheService } from '@backstage/backend-plugin-api'; import { CacheServiceOptions } from '@backstage/backend-plugin-api'; import { CacheServiceSetOptions } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; +import { ConfigSchema } from '@backstage/config-loader'; import cors from 'cors'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import Docker from 'dockerode'; import { ErrorRequestHandler } from 'express'; import express from 'express'; -import { GerritIntegration } from '@backstage/integration'; -import { GiteaIntegration } from '@backstage/integration'; -import { GithubCredentialsProvider } from '@backstage/integration'; -import { GithubIntegration } from '@backstage/integration'; -import { GitLabIntegration } from '@backstage/integration'; -import { HarnessIntegration } from '@backstage/integration'; import { HttpAuthService } from '@backstage/backend-plugin-api'; -import { IdentityService } from '@backstage/backend-plugin-api'; import { isChildPath as isChildPath_2 } from '@backstage/backend-plugin-api'; import { isDatabaseConflictError as isDatabaseConflictError_2 } from '@backstage/backend-plugin-api'; import { KubeConfig } from '@kubernetes/client-node'; @@ -46,34 +33,18 @@ import { MergeResult } from 'isomorphic-git'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { PluginMetadataService } from '@backstage/backend-plugin-api'; import { PushResult } from 'isomorphic-git'; -import { Readable } from 'stream'; import { ReadCommitResult } from 'isomorphic-git'; -import { ReadTreeOptions as ReadTreeOptions_2 } from '@backstage/backend-plugin-api'; -import { ReadTreeResponse as ReadTreeResponse_2 } from '@backstage/backend-plugin-api'; -import { ReadTreeResponseDirOptions as ReadTreeResponseDirOptions_2 } from '@backstage/backend-plugin-api'; -import { ReadTreeResponseFile as ReadTreeResponseFile_2 } from '@backstage/backend-plugin-api'; -import { ReadUrlOptions as ReadUrlOptions_2 } from '@backstage/backend-plugin-api'; -import { ReadUrlResponse as ReadUrlResponse_2 } from '@backstage/backend-plugin-api'; +import { Request as Request_2 } from 'express'; import { RequestHandler } from 'express'; import { resolvePackagePath as resolvePackagePath_2 } from '@backstage/backend-plugin-api'; import { resolveSafeChildPath as resolveSafeChildPath_2 } from '@backstage/backend-plugin-api'; import { RootConfigService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; import { SchedulerService } from '@backstage/backend-plugin-api'; -import { SearchOptions as SearchOptions_2 } from '@backstage/backend-plugin-api'; -import { SearchResponse as SearchResponse_2 } from '@backstage/backend-plugin-api'; -import { SearchResponseFile as SearchResponseFile_2 } from '@backstage/backend-plugin-api'; import { Server } from 'http'; import { ServiceRef } from '@backstage/backend-plugin-api'; -import { TokenManagerService } from '@backstage/backend-plugin-api'; import { TransportStreamOptions } from 'winston-transport'; import { UrlReaderService } from '@backstage/backend-plugin-api'; -import { UrlReaderServiceReadTreeOptions } from '@backstage/backend-plugin-api'; -import { UrlReaderServiceReadTreeResponse } from '@backstage/backend-plugin-api'; -import { UrlReaderServiceReadUrlOptions } from '@backstage/backend-plugin-api'; -import { UrlReaderServiceReadUrlResponse } from '@backstage/backend-plugin-api'; -import { UrlReaderServiceSearchOptions } from '@backstage/backend-plugin-api'; -import { UrlReaderServiceSearchResponse } from '@backstage/backend-plugin-api'; import { UserInfoService } from '@backstage/backend-plugin-api'; import { V1PodTemplateSpec } from '@kubernetes/client-node'; import * as winston from 'winston'; @@ -85,31 +56,6 @@ export type AuthCallbackOptions = { logger?: LoggerService; }; -// Warning: (ae-forgotten-export) The symbol "AwsS3UrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class AwsS3UrlReader extends AwsS3UrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "AzureUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class AzureUrlReader extends AzureUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "BitbucketCloudUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class BitbucketCloudUrlReader extends BitbucketCloudUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "BitbucketServerUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class BitbucketServerUrlReader extends BitbucketServerUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "BitbucketUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class BitbucketUrlReader extends BitbucketUrlReader_2 {} - // @public @deprecated (undocumented) export type CacheClient = CacheService; @@ -129,7 +75,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,13 +88,18 @@ export interface ContainerRunner { runContainer(opts: RunContainerOptions): Promise; } -// @public +// Warning: (ae-forgotten-export) The symbol "createConfigSecretEnumerator_2" needs to be exported by the entry point index.d.ts +// +// @public @deprecated (undocumented) +export const createConfigSecretEnumerator: typeof createConfigSecretEnumerator_2; + +// @public @deprecated export function createLegacyAuthAdapters< TOptions extends { auth?: AuthService; httpAuth?: HttpAuthService; userInfo?: UserInfoService; - identity?: IdentityService; + identity?: LegacyIdentityService; tokenManager?: TokenManager; discovery: PluginEndpointDiscovery; }, @@ -239,21 +190,6 @@ export type ErrorHandlerOptions = { logClientErrors?: boolean; }; -// Warning: (ae-forgotten-export) The symbol "FetchUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class FetchUrlReader extends FetchUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "FromReadableArrayOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type FromReadableArrayOptions = FromReadableArrayOptions_2; - -// Warning: (ae-forgotten-export) The symbol "GerritUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class GerritUrlReader extends GerritUrlReader_2 {} - // @public @deprecated export function getRootLogger(): winston.Logger; @@ -336,26 +272,6 @@ export class Git { resolveRef(options: { dir: string; ref: string }): Promise; } -// Warning: (ae-forgotten-export) The symbol "GiteaUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class GiteaUrlReader extends GiteaUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "GithubUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class GithubUrlReader extends GithubUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "GitlabUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class GitlabUrlReader extends GitlabUrlReader_2 {} - -// Warning: (ae-forgotten-export) The symbol "HarnessUrlReader_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class HarnessUrlReader extends HarnessUrlReader_2 {} - // @public @deprecated class HostDiscovery implements DiscoveryService { static fromConfig( @@ -401,10 +317,27 @@ export type KubernetesContainerRunnerOptions = { timeoutMs?: number; }; -// @public (undocumented) +// @public @deprecated (undocumented) export type LegacyCreateRouter = (deps: TEnv) => Promise; -// @public +// @public @deprecated +export interface LegacyIdentityService { + // (undocumented) + getIdentity(options: { request: Request_2 }): Promise< + | { + expiresInSeconds?: number; + token: string; + identity: { + type: 'user'; + userEntityRef: string; + ownershipEntityRefs: string[]; + }; + } + | undefined + >; +} + +// @public @deprecated export const legacyPlugin: ( name: string, createRouterImport: Promise<{ @@ -418,9 +351,7 @@ export const legacyPlugin: ( logger: LoggerService; permissions: PermissionsService; scheduler: SchedulerService; - tokenManager: TokenManagerService; reader: UrlReaderService; - identity: IdentityService; }, { logger: (log: LoggerService) => Logger; @@ -428,10 +359,13 @@ export const legacyPlugin: ( getClient(options?: CacheServiceOptions | undefined): CacheService; }; } - > + > & { + tokenManager: TokenManager; + identity: LegacyIdentityService; + } >; }>, -) => BackendFeatureCompat; +) => BackendFeature; // Warning: (ae-forgotten-export) The symbol "LegacyRootDatabaseService_2" needs to be exported by the entry point index.d.ts // @@ -447,13 +381,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 { @@ -467,9 +401,14 @@ export function makeLegacyPlugin< ): ( name: string, createRouterImport: Promise<{ - default: LegacyCreateRouter>; + default: LegacyCreateRouter< + TransformedEnv & { + tokenManager: TokenManager; + identity: LegacyIdentityService; + } + >; }>, -) => BackendFeatureCompat; +) => BackendFeature; // @public @deprecated export function notFoundHandler(): RequestHandler; @@ -500,50 +439,6 @@ export interface PullOptions { }; } -// Warning: (ae-forgotten-export) The symbol "ReaderFactory_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type ReaderFactory = ReaderFactory_2; - -// @public @deprecated (undocumented) -export type ReadTreeOptions = ReadTreeOptions_2; - -// @public @deprecated (undocumented) -export type ReadTreeResponse = ReadTreeResponse_2; - -// @public @deprecated (undocumented) -export type ReadTreeResponseDirOptions = ReadTreeResponseDirOptions_2; - -// Warning: (ae-forgotten-export) The symbol "ReadTreeResponseFactory_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type ReadTreeResponseFactory = ReadTreeResponseFactory_2; - -// Warning: (ae-forgotten-export) The symbol "ReadTreeResponseFactoryOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type ReadTreeResponseFactoryOptions = ReadTreeResponseFactoryOptions_2; - -// @public @deprecated (undocumented) -export type ReadTreeResponseFile = ReadTreeResponseFile_2; - -// @public @deprecated (undocumented) -export type ReadUrlOptions = ReadUrlOptions_2; - -// @public @deprecated (undocumented) -export type ReadUrlResponse = ReadUrlResponse_2; - -// Warning: (ae-forgotten-export) The symbol "ReadUrlResponseFactory_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class ReadUrlResponseFactory extends ReadUrlResponseFactory_2 {} - -// Warning: (ae-forgotten-export) The symbol "ReadUrlResponseFactoryFromStreamOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type ReadUrlResponseFactoryFromStreamOptions = - ReadUrlResponseFactoryFromStreamOptions_2; - // @public @deprecated export function redactWinstonLogLine( info: winston.Logform.TransformableInfo, @@ -577,15 +472,6 @@ export type RunContainerOptions = { pullOptions?: PullOptions; }; -// @public @deprecated (undocumented) -export type SearchOptions = SearchOptions_2; - -// @public @deprecated (undocumented) -export type SearchResponse = SearchResponse_2; - -// @public @deprecated (undocumented) -export type SearchResponseFile = SearchResponseFile_2; - // @public @deprecated export class ServerTokenManager implements TokenManager { // (undocumented) @@ -659,25 +545,12 @@ export interface StatusCheckHandlerOptions { } // @public @deprecated (undocumented) -export type TokenManager = TokenManagerService; - -// @public @deprecated (undocumented) -export type UrlReader = UrlReaderService; - -// Warning: (ae-forgotten-export) The symbol "UrlReaderPredicateTuple_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type UrlReaderPredicateTuple = UrlReaderPredicateTuple_2; - -// Warning: (ae-forgotten-export) The symbol "UrlReaders_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export class UrlReaders extends UrlReaders_2 {} - -// Warning: (ae-forgotten-export) The symbol "UrlReadersOptions_2" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export type UrlReadersOptions = UrlReadersOptions_2; +export interface TokenManager { + authenticate(token: string): Promise; + getToken(): Promise<{ + token: string; + }>; +} // @public @deprecated export function useHotCleanup( diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 28378f6c41..ce26958aaf 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.24.0", "description": "Common functionality library for Backstage backends", "backstage": { "role": "node-library" @@ -102,7 +102,7 @@ "minimist": "^1.2.5", "morgan": "^1.10.0", "mysql2": "^3.0.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "node-forge": "^1.3.1", "p-limit": "^3.1.0", "path-to-regexp": "^6.2.1", diff --git a/packages/backend-common/src/compat/auth/createLegacyAuthAdapters.test.ts b/packages/backend-common/src/compat/auth/createLegacyAuthAdapters.test.ts index 354b4a57dc..49494761bf 100644 --- a/packages/backend-common/src/compat/auth/createLegacyAuthAdapters.test.ts +++ b/packages/backend-common/src/compat/auth/createLegacyAuthAdapters.test.ts @@ -14,18 +14,28 @@ * limitations under the License. */ -import { mockServices } from '@backstage/backend-test-utils'; import { createLegacyAuthAdapters } from './createLegacyAuthAdapters'; import { Request } from 'express'; +import { TokenManager } from '../../deprecated'; + +const mockTokenManager: TokenManager = { + async getToken(): Promise<{ token: string }> { + return { token: 'mock-token' }; + }, + async authenticate(token: string): Promise { + if (token !== 'mock-token') { + throw new Error('Invalid token'); + } + }, +}; describe('createLegacyAuthAdapters', () => { it('should pass through auth if only auth is provided', () => { const auth = {}; const ret = createLegacyAuthAdapters({ auth: auth as any, - tokenManager: mockServices.tokenManager(), + tokenManager: mockTokenManager, discovery: {} as any, - identity: mockServices.identity(), }); expect(ret.auth).toBe(auth); @@ -35,9 +45,8 @@ describe('createLegacyAuthAdapters', () => { const httpAuth = {}; const ret = createLegacyAuthAdapters({ httpAuth: httpAuth as any, - tokenManager: mockServices.tokenManager(), + tokenManager: mockTokenManager, discovery: {} as any, - identity: mockServices.identity(), }); expect(ret.httpAuth).toBe(httpAuth); @@ -49,9 +58,8 @@ describe('createLegacyAuthAdapters', () => { const ret = createLegacyAuthAdapters({ auth: auth as any, httpAuth: httpAuth as any, - tokenManager: mockServices.tokenManager(), + tokenManager: mockTokenManager, discovery: {} as any, - identity: mockServices.identity(), }); expect(ret.auth).toBe(auth); @@ -64,9 +72,8 @@ describe('createLegacyAuthAdapters', () => { const ret = createLegacyAuthAdapters({ auth: auth as any, userInfo: userInfo as any, - tokenManager: mockServices.tokenManager(), + tokenManager: mockTokenManager, discovery: {} as any, - identity: mockServices.identity(), }); expect(ret.auth).toBe(auth); @@ -77,9 +84,8 @@ describe('createLegacyAuthAdapters', () => { const ret = createLegacyAuthAdapters({ auth: undefined, httpAuth: undefined, - tokenManager: mockServices.tokenManager(), + tokenManager: mockTokenManager, discovery: {} as any, - identity: mockServices.identity(), }); expect(ret).toEqual({ @@ -94,7 +100,6 @@ describe('createLegacyAuthAdapters', () => { auth: undefined, httpAuth: undefined, discovery: {} as any, - identity: mockServices.identity(), }); const credentials = await httpAuth.credentials({ @@ -116,13 +121,12 @@ describe('createLegacyAuthAdapters', () => { auth: undefined, httpAuth: undefined, tokenManager: { - ...mockServices.tokenManager(), + ...mockTokenManager, async getToken() { return { token: 'new-token' }; }, }, discovery: {} as any, - identity: mockServices.identity(), }); const credentials = await httpAuth.credentials({ diff --git a/packages/backend-common/src/compat/auth/createLegacyAuthAdapters.ts b/packages/backend-common/src/compat/auth/createLegacyAuthAdapters.ts index 0f591eac6c..fc02d936d1 100644 --- a/packages/backend-common/src/compat/auth/createLegacyAuthAdapters.ts +++ b/packages/backend-common/src/compat/auth/createLegacyAuthAdapters.ts @@ -23,8 +23,6 @@ import { BackstageUserInfo, BackstageUserPrincipal, HttpAuthService, - IdentityService, - TokenManagerService, UserInfoService, } from '@backstage/backend-plugin-api'; import { AuthenticationError, NotAllowedError } from '@backstage/errors'; @@ -44,11 +42,12 @@ import { import { decodeJwt } from 'jose'; import { TokenManager, PluginEndpointDiscovery } from '../../deprecated'; import { JsonObject } from '@backstage/types'; +import { LegacyIdentityService } from '../legacy'; class AuthCompat implements AuthService { constructor( - private readonly identity: IdentityService, - private readonly tokenManager?: TokenManagerService, + private readonly identity: LegacyIdentityService, + private readonly tokenManager?: TokenManager, ) {} isPrincipal( @@ -292,13 +291,14 @@ 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 { auth?: AuthService; httpAuth?: HttpAuthService; userInfo?: UserInfoService; - identity?: IdentityService; + identity?: LegacyIdentityService; tokenManager?: TokenManager; discovery: PluginEndpointDiscovery; }, 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/index.ts b/packages/backend-common/src/compat/legacy/index.ts index 1172084cd7..7e0646342f 100644 --- a/packages/backend-common/src/compat/legacy/index.ts +++ b/packages/backend-common/src/compat/legacy/index.ts @@ -15,4 +15,4 @@ */ export { legacyPlugin, makeLegacyPlugin } from './legacy'; -export type { LegacyCreateRouter } from './legacy'; +export type { LegacyCreateRouter, LegacyIdentityService } from './legacy'; diff --git a/packages/backend-common/src/compat/legacy/legacy.test.ts b/packages/backend-common/src/compat/legacy/legacy.test.ts index e5d395a8f9..b7a5ff1a78 100644 --- a/packages/backend-common/src/compat/legacy/legacy.test.ts +++ b/packages/backend-common/src/compat/legacy/legacy.test.ts @@ -18,15 +18,18 @@ import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; -import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { + mockCredentials, + mockServices, + startTestBackend, +} from '@backstage/backend-test-utils'; import { EventEmitter } from 'events'; import { Router } from 'express'; +import request from 'supertest'; import { createLegacyAuthAdapters } from '..'; import { legacyPlugin } from './legacy'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { authServiceFactory } from '../../../../backend-app-api/src/services/implementations/auth'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { tokenManagerServiceFactory } from '../../../../backend-app-api/src/services/implementations/tokenManager'; +import { authServiceFactory } from '../../../../backend-defaults/src/entrypoints/auth'; describe('legacyPlugin', () => { it('can auth across the new and old systems', async () => { @@ -41,7 +44,6 @@ describe('legacyPlugin', () => { await startTestBackend({ features: [ authServiceFactory, - tokenManagerServiceFactory, mockServices.rootConfig.factory({ data: { backend: { @@ -115,4 +117,54 @@ describe('legacyPlugin', () => { await done; }); + + it('can auth users with the identity service shim', async () => { + const backend = await startTestBackend({ + features: [ + mockServices.rootConfig.factory({ + data: { + backend: { + auth: { + keys: [ + { + secret: 'test', + }, + ], + }, + }, + }, + }), + legacyPlugin( + 'test', + Promise.resolve({ + async default({ identity }) { + const router = Router(); + + router.get('/', async (req, res) => { + const user = await identity.getIdentity({ request: req }); + res.json(user); + }); + + return router; + }, + }), + ), + ], + }); + + const res = await request(backend.server) + .get('/api/test') + .set('authorization', mockCredentials.user.header()); + + const mockUserRef = mockCredentials.user().principal.userEntityRef; + expect(res.status).toBe(200); + expect(res.body).toEqual({ + token: mockCredentials.user.token(), + identity: { + type: 'user', + userEntityRef: mockUserRef, + ownershipEntityRefs: [mockUserRef], + }, + }); + }); }); diff --git a/packages/backend-common/src/compat/legacy/legacy.ts b/packages/backend-common/src/compat/legacy/legacy.ts index 57220a8430..bf051f3040 100644 --- a/packages/backend-common/src/compat/legacy/legacy.ts +++ b/packages/backend-common/src/compat/legacy/legacy.ts @@ -18,15 +18,20 @@ import { AuthService, coreServices, createBackendPlugin, + LoggerService, + RootConfigService, ServiceRef, + UserInfoService, } from '@backstage/backend-plugin-api'; import { RequestHandler } from 'express'; import { cacheToPluginCacheManager } from '../cache'; import { loggerToWinstonLogger } from '../logging'; -import { TokenManager } from '../../deprecated'; +import { ServerTokenManager, TokenManager } from '../../deprecated'; +import { Request } from 'express'; /** * @public + * @deprecated Fully use the new backend system instead. */ export type LegacyCreateRouter = (deps: TEnv) => Promise; @@ -44,8 +49,13 @@ type TransformedEnv< // new plugin tokens, which we'll also be signaling by supporting the JWKS endpoint through // the http router. // This makes sure that we accept the new plugin tokens as valid tokens, but otherwise fall -// back to whatever the token manager is doing. -function wrapTokenManager(tokenManager: TokenManager, auth: AuthService) { +// back to the legacy token manager. +function createTokenManagerShim( + auth: AuthService, + config: RootConfigService, + logger: LoggerService, +): TokenManager { + const tokenManager = ServerTokenManager.fromConfig(config, { logger }); return { async getToken() { return tokenManager.getToken(); @@ -62,13 +72,71 @@ function wrapTokenManager(tokenManager: TokenManager, auth: AuthService) { } await tokenManager.authenticate(token); }, - } satisfies TokenManager; + }; +} + +/** + * Originally IdentityApi from `@backstage/plugin-auth-node`, re-declared here for backwards compatibility + * @public + * @deprecated Only relevant for legacy plugins, which are deprecated. + */ +export interface LegacyIdentityService { + getIdentity(options: { request: Request }): Promise< + | { + expiresInSeconds?: number; + + token: string; + identity: { + type: 'user'; + userEntityRef: string; + ownershipEntityRefs: string[]; + }; + } + | undefined + >; +} + +// This doesn't use DefaultIdentityClient because we will be removing it and break support for ownershipEntityRefs +function createIdentityServiceShim( + auth: AuthService, + userInfo: UserInfoService, +): LegacyIdentityService { + return { + async getIdentity(options) { + const authHeader = options.request.headers.authorization; + if (typeof authHeader !== 'string') { + return undefined; + } + + const token = authHeader.match(/^Bearer[ ]+(\S+)$/i)?.[1]; + if (!token) { + return undefined; + } + + const credentials = await auth.authenticate(token); + if (!auth.isPrincipal(credentials, 'user')) { + return undefined; + } + + const info = await userInfo.getUserInfo(credentials); + + return { + token, + identity: { + type: 'user', + userEntityRef: info.userEntityRef, + ownershipEntityRefs: info.ownershipEntityRefs, + }, + }; + }, + }; } /** * 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 @@ -84,19 +152,34 @@ export function makeLegacyPlugin< return ( name: string, createRouterImport: Promise<{ - default: LegacyCreateRouter>; + default: LegacyCreateRouter< + TransformedEnv & { + tokenManager: TokenManager; + identity: LegacyIdentityService; + } + >; }>, ) => { - const compatPlugin = createBackendPlugin({ + return createBackendPlugin({ pluginId: name, register(env) { env.registerInit({ deps: { ...envMapping, - _router: coreServices.httpRouter, - _auth: coreServices.auth, + $$router: coreServices.httpRouter, + $$auth: coreServices.auth, + $$userInfo: coreServices.userInfo, + $$config: coreServices.rootConfig, + $$logger: coreServices.logger, }, - async init({ _router, _auth, ...envDeps }) { + async init({ + $$auth, + $$config, + $$logger, + $$router, + $$userInfo, + ...envDeps + }) { const { default: createRouter } = await createRouterImport; const pluginEnv = Object.fromEntries( Object.entries(envDeps).map(([key, dep]) => { @@ -104,22 +187,35 @@ export function makeLegacyPlugin< if (transform) { return [key, transform(dep)]; } - if (key === 'tokenManager') { - return [key, wrapTokenManager(dep as TokenManager, _auth)]; - } return [key, dep]; }), ); - const router = await createRouter( - pluginEnv as TransformedEnv, + + const auth = $$auth as typeof coreServices.auth.T; + const config = $$config as typeof coreServices.rootConfig.T; + const logger = $$logger as typeof coreServices.logger.T; + const router = $$router as typeof coreServices.httpRouter.T; + const userInfo = $$userInfo as typeof coreServices.userInfo.T; + + // Token manager and identity services are no longer supported in the new backend system, so we provide shims for them. + pluginEnv.tokenManager = createTokenManagerShim( + auth, + config, + logger, ); - _router.use(router); + pluginEnv.identity = createIdentityServiceShim(auth, userInfo); + + const pluginRouter = await createRouter( + pluginEnv as TransformedEnv & { + tokenManager: TokenManager; + identity: LegacyIdentityService; + }, + ); + router.use(pluginRouter); }, }); }, }); - - return compatPlugin(); }; } @@ -128,6 +224,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 @@ -148,9 +245,7 @@ export const legacyPlugin = makeLegacyPlugin( logger: coreServices.logger, permissions: coreServices.permissions, scheduler: coreServices.scheduler, - tokenManager: coreServices.tokenManager, reader: coreServices.urlReader, - identity: coreServices.identity, }, { logger: log => loggerToWinstonLogger(log), 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-common/src/deprecated/config.ts b/packages/backend-common/src/deprecated/config.ts deleted file mode 100644 index 2d06ca2072..0000000000 --- a/packages/backend-common/src/deprecated/config.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { - createConfigSecretEnumerator, - loadBackendConfig as newLoadBackendConfig, -} from '../../../backend-app-api/src/config'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { AppConfig, Config } from '@backstage/config'; -import { LoadConfigOptionsRemote } from '@backstage/config-loader'; -import { setRootLoggerRedactionList } from './logging/createRootLogger'; - -/** - * Load configuration for a Backend. - * - * This function should only be called once, during the initialization of the backend. - * - * @public - * @deprecated Use {@link @backstage/backend-app-api#loadBackendConfig} instead. - */ -export async function loadBackendConfig(options: { - logger: LoggerService; - // process.argv or any other overrides - remote?: LoadConfigOptionsRemote; - additionalConfigs?: AppConfig[]; - argv: string[]; - watch?: boolean; -}): Promise { - const secretEnumerator = await createConfigSecretEnumerator({ - logger: options.logger, - }); - const { config } = await newLoadBackendConfig(options); - - setRootLoggerRedactionList(secretEnumerator(config)); - config.subscribe?.(() => - setRootLoggerRedactionList(secretEnumerator(config)), - ); - - return config; -} diff --git a/packages/backend-app-api/src/config/ObservableConfigProxy.test.ts b/packages/backend-common/src/deprecated/config/ObservableConfigProxy.test.ts similarity index 100% rename from packages/backend-app-api/src/config/ObservableConfigProxy.test.ts rename to packages/backend-common/src/deprecated/config/ObservableConfigProxy.test.ts diff --git a/packages/backend-app-api/src/config/ObservableConfigProxy.ts b/packages/backend-common/src/deprecated/config/ObservableConfigProxy.ts similarity index 100% rename from packages/backend-app-api/src/config/ObservableConfigProxy.ts rename to packages/backend-common/src/deprecated/config/ObservableConfigProxy.ts diff --git a/packages/backend-app-api/src/config/config.test.ts b/packages/backend-common/src/deprecated/config/config.test.ts similarity index 100% rename from packages/backend-app-api/src/config/config.test.ts rename to packages/backend-common/src/deprecated/config/config.test.ts index 8828c557e0..865b1b4d60 100644 --- a/packages/backend-app-api/src/config/config.test.ts +++ b/packages/backend-common/src/deprecated/config/config.test.ts @@ -15,8 +15,8 @@ */ import { loadConfigSchema } from '@backstage/config-loader'; -import { createConfigSecretEnumerator } from './config'; import { mockServices } from '@backstage/backend-test-utils'; +import { createConfigSecretEnumerator } from './config'; describe('createConfigSecretEnumerator', () => { it('should enumerate secrets', async () => { diff --git a/packages/backend-app-api/src/config/config.ts b/packages/backend-common/src/deprecated/config/config.ts similarity index 76% rename from packages/backend-app-api/src/config/config.ts rename to packages/backend-common/src/deprecated/config/config.ts index 957997849e..d50476a9f2 100644 --- a/packages/backend-app-api/src/config/config.ts +++ b/packages/backend-common/src/deprecated/config/config.ts @@ -14,8 +14,11 @@ * limitations under the License. */ +import { LoggerService } from '@backstage/backend-plugin-api'; +import { AppConfig, Config } from '@backstage/config'; +import { setRootLoggerRedactionList } from '../logging/createRootLogger'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { createConfigSecretEnumerator as _createConfigSecretEnumerator } from '../../../backend-defaults/src/entrypoints/rootConfig/createConfigSecretEnumerator'; +import { createConfigSecretEnumerator as _createConfigSecretEnumerator } from '../../../../backend-defaults/src/entrypoints/rootConfig/createConfigSecretEnumerator'; import { resolve as resolvePath } from 'path'; import parseArgs from 'minimist'; @@ -26,13 +29,12 @@ import { LoadConfigOptionsRemote, } from '@backstage/config-loader'; import { ConfigReader } from '@backstage/config'; -import type { Config, AppConfig } from '@backstage/config'; import { ObservableConfigProxy } from './ObservableConfigProxy'; -import { isValidUrl } from '../lib/urls'; +import { isValidUrl } from './urls'; /** * @public - * @deprecated Please import from `@backstage/backend-defaults/rootConfig` instead. + * @deprecated Please migrate to the new backend system and use `coreServices.rootConfig` instead, or the {@link @backstage/config-loader#ConfigSources} facilities if required. */ export const createConfigSecretEnumerator = _createConfigSecretEnumerator; @@ -45,6 +47,27 @@ export const createConfigSecretEnumerator = _createConfigSecretEnumerator; * @deprecated Please migrate to the new backend system and use `coreServices.rootConfig` instead, or the {@link @backstage/config-loader#ConfigSources} facilities if required. */ export async function loadBackendConfig(options: { + logger: LoggerService; + // process.argv or any other overrides + remote?: LoadConfigOptionsRemote; + additionalConfigs?: AppConfig[]; + argv: string[]; + watch?: boolean; +}): Promise { + const secretEnumerator = await createConfigSecretEnumerator({ + logger: options.logger, + }); + const { config } = await newLoadBackendConfig(options); + + setRootLoggerRedactionList(secretEnumerator(config)); + config.subscribe?.(() => + setRootLoggerRedactionList(secretEnumerator(config)), + ); + + return config; +} + +async function newLoadBackendConfig(options: { remote?: LoadConfigOptionsRemote; argv: string[]; additionalConfigs?: AppConfig[]; diff --git a/packages/backend-app-api/src/config/index.ts b/packages/backend-common/src/deprecated/config/index.ts similarity index 94% rename from packages/backend-app-api/src/config/index.ts rename to packages/backend-common/src/deprecated/config/index.ts index 0ed8d2bc93..c87193876c 100644 --- a/packages/backend-app-api/src/config/index.ts +++ b/packages/backend-common/src/deprecated/config/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2024 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/packages/backend-app-api/src/lib/urls.test.ts b/packages/backend-common/src/deprecated/config/urls.test.ts similarity index 100% rename from packages/backend-app-api/src/lib/urls.test.ts rename to packages/backend-common/src/deprecated/config/urls.test.ts diff --git a/packages/backend-app-api/src/lib/urls.ts b/packages/backend-common/src/deprecated/config/urls.ts similarity index 100% rename from packages/backend-app-api/src/lib/urls.ts rename to packages/backend-common/src/deprecated/config/urls.ts diff --git a/packages/backend-common/src/deprecated/index.ts b/packages/backend-common/src/deprecated/index.ts index e3e4101d25..5dfcd322ed 100644 --- a/packages/backend-common/src/deprecated/index.ts +++ b/packages/backend-common/src/deprecated/index.ts @@ -36,45 +36,6 @@ import { type LegacyRootDatabaseService as _LegacyRootDatabaseService, } from '../../../backend-defaults/src/entrypoints/database/DatabaseManager'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { AzureUrlReader as _AzureUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/AzureUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { BitbucketCloudUrlReader as _BitbucketCloudUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/BitbucketCloudUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { BitbucketUrlReader as _BitbucketUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/BitbucketUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { BitbucketServerUrlReader as _BitbucketServerUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/BitbucketServerUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { GerritUrlReader as _GerritUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { GithubUrlReader as _GithubUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { GitlabUrlReader as _GitlabUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { GiteaUrlReader as _GiteaUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/GiteaUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { HarnessUrlReader as _HarnessUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/HarnessUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { AwsS3UrlReader as _AwsS3UrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/AwsS3UrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { FetchUrlReader as _FetchUrlReader } from '../../../backend-defaults/src/entrypoints/urlReader/lib/FetchUrlReader'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { UrlReaders as _UrlReaders } from '../../../backend-defaults/src/entrypoints/urlReader/lib/UrlReaders'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { ReadUrlResponseFactory as _ReadUrlResponseFactory } from '../../../backend-defaults/src/entrypoints/urlReader/lib/ReadUrlResponseFactory'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import type { UrlReadersOptions as _UrlReadersOptions } from '../../../backend-defaults/src/entrypoints/urlReader/lib/UrlReaders'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import type { FromReadableArrayOptions as _FromReadableArrayOptions } from '../../../backend-defaults/src/entrypoints/urlReader/lib/types'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import type { - ReaderFactory as _ReaderFactory, - ReadTreeResponseFactory as _ReadTreeResponseFactory, - ReadTreeResponseFactoryOptions as _ReadTreeResponseFactoryOptions, - ReadUrlResponseFactoryFromStreamOptions as _ReadUrlResponseFactoryFromStreamOptions, - UrlReaderPredicateTuple as _UrlReaderPredicateTuple, -} from '../../../backend-defaults/src/entrypoints/urlReader/lib/types'; - import { DiscoveryService, CacheService, @@ -85,16 +46,6 @@ import { resolvePackagePath as _resolvePackagePath, resolveSafeChildPath as _resolveSafeChildPath, isChildPath as _isChildPath, - ReadTreeOptions as _ReadTreeOptions, - ReadTreeResponse as _ReadTreeResponse, - ReadTreeResponseFile as _ReadTreeResponseFile, - ReadTreeResponseDirOptions as _ReadTreeResponseDirOptions, - ReadUrlOptions as _ReadUrlOptions, - ReadUrlResponse as _ReadUrlResponse, - SearchOptions as _SearchOptions, - SearchResponse as _SearchResponse, - SearchResponseFile as _SearchResponseFile, - UrlReaderService as _UrlReaderService, LifecycleService, PluginMetadataService, } from '@backstage/backend-plugin-api'; @@ -290,184 +241,3 @@ export const resolveSafeChildPath = _resolveSafeChildPath; * Please use the `isChildPath` function from the `@backstage/cli-common` package instead. */ export const isChildPath = _isChildPath; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class AzureUrlReader extends _AzureUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class BitbucketCloudUrlReader extends _BitbucketCloudUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class BitbucketUrlReader extends _BitbucketUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class BitbucketServerUrlReader extends _BitbucketServerUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class GerritUrlReader extends _GerritUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class GithubUrlReader extends _GithubUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class GitlabUrlReader extends _GitlabUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class GiteaUrlReader extends _GiteaUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class HarnessUrlReader extends _HarnessUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class AwsS3UrlReader extends _AwsS3UrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class FetchUrlReader extends _FetchUrlReader {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class UrlReaders extends _UrlReaders {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export class ReadUrlResponseFactory extends _ReadUrlResponseFactory {} - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type UrlReadersOptions = _UrlReadersOptions; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type FromReadableArrayOptions = _FromReadableArrayOptions; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type ReaderFactory = _ReaderFactory; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type ReadTreeResponseFactory = _ReadTreeResponseFactory; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type ReadTreeResponseFactoryOptions = _ReadTreeResponseFactoryOptions; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type ReadUrlResponseFactoryFromStreamOptions = - _ReadUrlResponseFactoryFromStreamOptions; - -/** - * @public - * @deprecated Import from `@backstage/backend-defaults/urlReader` instead - */ -export type UrlReaderPredicateTuple = _UrlReaderPredicateTuple; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeOptions` from `@backstage/backend-plugin-api` instead - */ -export type ReadTreeOptions = _ReadTreeOptions; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponse` from `@backstage/backend-plugin-api` instead - */ -export type ReadTreeResponse = _ReadTreeResponse; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponseFile` from `@backstage/backend-plugin-api` instead - */ -export type ReadTreeResponseFile = _ReadTreeResponseFile; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponseDirOptions` from `@backstage/backend-plugin-api` instead - */ -export type ReadTreeResponseDirOptions = _ReadTreeResponseDirOptions; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadUrlOptions` from `@backstage/backend-plugin-api` instead - */ -export type ReadUrlOptions = _ReadUrlOptions; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadUrlResponse` from `@backstage/backend-plugin-api` instead - */ -export type ReadUrlResponse = _ReadUrlResponse; - -/** - * @public - * @deprecated Use `UrlReaderServiceSearchOptions` from `@backstage/backend-plugin-api` instead - */ -export type SearchOptions = _SearchOptions; - -/** - * @public - * @deprecated Use `UrlReaderServiceSearchResponse` from `@backstage/backend-plugin-api` instead - */ -export type SearchResponse = _SearchResponse; - -/** - * @public - * @deprecated Use `UrlReaderServiceSearchResponseFile` from `@backstage/backend-plugin-api` instead - */ -export type SearchResponseFile = _SearchResponseFile; - -/** - * @public - * @deprecated Use `UrlReaderService` from `@backstage/backend-plugin-api` instead - */ -export type UrlReader = _UrlReaderService; diff --git a/packages/backend-common/src/deprecated/logging/createRootLogger.ts b/packages/backend-common/src/deprecated/logging/createRootLogger.ts index b177024601..04b3799381 100644 --- a/packages/backend-common/src/deprecated/logging/createRootLogger.ts +++ b/packages/backend-common/src/deprecated/logging/createRootLogger.ts @@ -15,7 +15,7 @@ */ // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { WinstonLogger } from '../../../../backend-app-api/src/logging/WinstonLogger'; +import { WinstonLogger } from '../../../../backend-defaults/src/entrypoints/rootLogger/WinstonLogger'; import { merge } from 'lodash'; import * as winston from 'winston'; import { format, LoggerOptions } from 'winston'; diff --git a/packages/backend-common/src/deprecated/tokens/types.ts b/packages/backend-common/src/deprecated/tokens/types.ts index 09dc0bb211..41a2000868 100644 --- a/packages/backend-common/src/deprecated/tokens/types.ts +++ b/packages/backend-common/src/deprecated/tokens/types.ts @@ -14,10 +14,25 @@ * limitations under the License. */ -import { TokenManagerService } from '@backstage/backend-plugin-api'; - /** * @public * @deprecated Please {@link https://backstage.io/docs/tutorials/auth-service-migration | migrate} to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead. */ -export type TokenManager = TokenManagerService; +export interface TokenManager { + /** + * Fetches a valid token. + * + * @remarks + * + * Tokens are valid for roughly one hour; the actual deadline is set in the + * payload `exp` claim. Never hold on to tokens for reuse; always ask for a + * new one for each outgoing request. This ensures that you always get a + * valid, fresh one. + */ + getToken(): Promise<{ token: string }>; + + /** + * Validates a given token. + */ + authenticate(token: string): Promise; +} diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index ad5621b5a9..e08bef3048 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -17,6 +17,9 @@ /** * Common functionality library for Backstage backends * + * @remarks + * This package is deprecated and will be removed in a near future, so please follow the deprecated instructions for the exports you still use. + * * @packageDocumentation */ diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index 7e9788756f..64d9b77fba 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,179 @@ # @backstage/backend-defaults +## 0.4.2 + +### Patch Changes + +- 0d16b52: Add access restrictions to the JWKS external access method config schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 3b429fb: Added deprecation warning to urge users to perform the auth service migration or implement their own token manager service. + See https://backstage.io/docs/tutorials/auth-service-migration for more information. +- 7681b17: update the `morgan` middleware to use a custom format to prevent PII from being logged +- 4e79d19: The `createHealthRouter` utility that allows you to create a health check router is now exported via `@backstage/backend-defaults/rootHttpRouter`. +- ba9abf4: The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. +- 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`. +- 7c5f3b0: Update the `UrlReader` service to depends on multiple instances of `UrlReaderFactoryProvider` service. +- 81f930a: use formatted query to prevent chance of SQL-injection +- 1d5f298: Avoid excessive numbers of error listeners on cache clients +- Updated dependencies + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/integration@1.14.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-events-node@0.3.9 + +## 0.4.2-next.3 + +### Patch Changes + +- 81f930a: use formatted query to prevent chance of SQL-injection +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-app-api@0.8.1-next.3 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## 0.4.2-next.2 + +### Patch Changes + +- 0d16b52: Add access restrictions to the JWKS external access method config schema +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- ba9abf4: The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. +- 7c5f3b0: Update the `UrlReader` service to depends on multiple instances of `UrlReaderFactoryProvider` service. +- 1d5f298: Avoid excessive numbers of error listeners on cache clients +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-app-api@0.8.1-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-app-api@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/backend-dev-utils@0.1.4 + - @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.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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..2f201e4573 100644 --- a/packages/backend-defaults/api-report-auth.md +++ b/packages/backend-defaults/api-report-auth.md @@ -7,7 +7,11 @@ import { AuthService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const authServiceFactory: () => ServiceFactory; +export const authServiceFactory: ServiceFactory< + AuthService, + 'plugin', + 'singleton' +>; // (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..58106430ab 100644 --- a/packages/backend-defaults/api-report-cache.md +++ b/packages/backend-defaults/api-report-cache.md @@ -25,7 +25,11 @@ export type CacheManagerOptions = { }; // @public -export const cacheServiceFactory: () => ServiceFactory; +export const cacheServiceFactory: ServiceFactory< + CacheService, + 'plugin', + 'singleton' +>; // @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..10de8df1ff 100644 --- a/packages/backend-defaults/api-report-database.md +++ b/packages/backend-defaults/api-report-database.md @@ -32,9 +32,10 @@ export type DatabaseManagerOptions = { }; // @public -export const databaseServiceFactory: () => ServiceFactory< +export const databaseServiceFactory: ServiceFactory< DatabaseService, - 'plugin' + 'plugin', + 'singleton' >; // @public @deprecated diff --git a/packages/backend-defaults/api-report-discovery.md b/packages/backend-defaults/api-report-discovery.md index b434dab6fc..de60256f02 100644 --- a/packages/backend-defaults/api-report-discovery.md +++ b/packages/backend-defaults/api-report-discovery.md @@ -8,9 +8,10 @@ import { DiscoveryService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const discoveryServiceFactory: () => ServiceFactory< +export const discoveryServiceFactory: ServiceFactory< DiscoveryService, - 'plugin' + 'plugin', + 'singleton' >; // @public diff --git a/packages/backend-defaults/api-report-httpAuth.md b/packages/backend-defaults/api-report-httpAuth.md index 6ffe3d6539..6c08a6f4d0 100644 --- a/packages/backend-defaults/api-report-httpAuth.md +++ b/packages/backend-defaults/api-report-httpAuth.md @@ -7,9 +7,10 @@ import { HttpAuthService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const httpAuthServiceFactory: () => ServiceFactory< +export const httpAuthServiceFactory: ServiceFactory< HttpAuthService, - 'plugin' + 'plugin', + 'singleton' >; // (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..dc612377ae 100644 --- a/packages/backend-defaults/api-report-httpRouter.md +++ b/packages/backend-defaults/api-report-httpRouter.md @@ -14,15 +14,12 @@ 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: ServiceFactory< + HttpRouterService, + 'plugin', + 'singleton' +>; // @public export interface LifecycleMiddlewareOptions { diff --git a/packages/backend-defaults/api-report-lifecycle.md b/packages/backend-defaults/api-report-lifecycle.md index 4233584664..0d909faa90 100644 --- a/packages/backend-defaults/api-report-lifecycle.md +++ b/packages/backend-defaults/api-report-lifecycle.md @@ -7,9 +7,10 @@ import { LifecycleService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const lifecycleServiceFactory: () => ServiceFactory< +export const lifecycleServiceFactory: ServiceFactory< LifecycleService, - 'plugin' + 'plugin', + 'singleton' >; // (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..c3d2ab3359 100644 --- a/packages/backend-defaults/api-report-logger.md +++ b/packages/backend-defaults/api-report-logger.md @@ -7,9 +7,10 @@ import { LoggerService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const loggerServiceFactory: () => ServiceFactory< +export const loggerServiceFactory: ServiceFactory< LoggerService, - 'plugin' + 'plugin', + 'singleton' >; // (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..003bee9d25 100644 --- a/packages/backend-defaults/api-report-permissions.md +++ b/packages/backend-defaults/api-report-permissions.md @@ -7,9 +7,10 @@ import { PermissionsService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const permissionsServiceFactory: () => ServiceFactory< +export const permissionsServiceFactory: ServiceFactory< PermissionsService, - 'plugin' + 'plugin', + 'singleton' >; // (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..2935ad1128 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..b26de7cfda 100644 --- a/packages/backend-defaults/api-report-rootHealth.md +++ b/packages/backend-defaults/api-report-rootHealth.md @@ -7,9 +7,10 @@ import { RootHealthService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public (undocumented) -export const rootHealthServiceFactory: () => ServiceFactory< +export const rootHealthServiceFactory: ServiceFactory< RootHealthService, - 'root' + 'root', + 'singleton' >; // (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..3686f25a12 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..7a7c4fd7c0 100644 --- a/packages/backend-defaults/api-report-rootLifecycle.md +++ b/packages/backend-defaults/api-report-rootLifecycle.md @@ -7,9 +7,10 @@ import { RootLifecycleService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; // @public -export const rootLifecycleServiceFactory: () => ServiceFactory< +export const rootLifecycleServiceFactory: ServiceFactory< RootLifecycleService, - 'root' + 'root', + 'singleton' >; // (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..b353039c03 100644 --- a/packages/backend-defaults/api-report-rootLogger.md +++ b/packages/backend-defaults/api-report-rootLogger.md @@ -11,9 +11,10 @@ import { ServiceFactory } from '@backstage/backend-plugin-api'; import { transport } from 'winston'; // @public -export const rootLoggerServiceFactory: () => ServiceFactory< +export const rootLoggerServiceFactory: ServiceFactory< RootLoggerService, - 'root' + 'root', + 'singleton' >; // @public diff --git a/packages/backend-defaults/api-report-scheduler.md b/packages/backend-defaults/api-report-scheduler.md index 58991ddca3..10c0c0a943 100644 --- a/packages/backend-defaults/api-report-scheduler.md +++ b/packages/backend-defaults/api-report-scheduler.md @@ -18,9 +18,10 @@ export class DefaultSchedulerService { } // @public -export const schedulerServiceFactory: () => ServiceFactory< +export const schedulerServiceFactory: ServiceFactory< SchedulerService, - 'plugin' + 'plugin', + 'singleton' >; // (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..73fbcd60ac 100644 --- a/packages/backend-defaults/api-report-urlReader.md +++ b/packages/backend-defaults/api-report-urlReader.md @@ -22,6 +22,7 @@ import { HarnessIntegration } from '@backstage/integration'; import { LoggerService } from '@backstage/backend-plugin-api'; import { Readable } from 'stream'; import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceRef } 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'; @@ -413,6 +414,13 @@ export type ReadUrlResponseFactoryFromStreamOptions = { lastModifiedAt?: Date; }; +// @public +export const urlReaderFactoriesServiceRef: ServiceRef< + ReaderFactory, + 'plugin', + 'multiton' +>; + // @public export type UrlReaderPredicateTuple = { predicate: (url: URL) => boolean; @@ -426,9 +434,10 @@ export class UrlReaders { } // @public -export const urlReaderServiceFactory: () => ServiceFactory< +export const urlReaderServiceFactory: ServiceFactory< UrlReaderService, - 'plugin' + 'plugin', + 'singleton' >; // @public diff --git a/packages/backend-defaults/api-report-userInfo.md b/packages/backend-defaults/api-report-userInfo.md index d11fb69718..9ab4db1391 100644 --- a/packages/backend-defaults/api-report-userInfo.md +++ b/packages/backend-defaults/api-report-userInfo.md @@ -7,9 +7,10 @@ import { ServiceFactory } from '@backstage/backend-plugin-api'; import { UserInfoService } from '@backstage/backend-plugin-api'; // @public -export const userInfoServiceFactory: () => ServiceFactory< +export const userInfoServiceFactory: ServiceFactory< UserInfoService, - 'plugin' + 'plugin', + 'singleton' >; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts index ab1e443fc2..1582cf1a39 100644 --- a/packages/backend-defaults/config.d.ts +++ b/packages/backend-defaults/config.d.ts @@ -74,7 +74,7 @@ export interface Config { * the Backstage ecosystem to get authorized for access to APIs that do * not permit unauthorized access. */ - externalAccess: Array< + externalAccess?: Array< | { /** * This is the legacy service-to-service access method, where a set @@ -287,6 +287,46 @@ export interface Config { */ subjectPrefix?: string; }; + /** + * Restricts what types of access that are permitted for this access + * method. If no access restrictions are given, it'll have unlimited + * access. This access restriction applies for the framework level; + * individual plugins may have their own access control mechanisms + * on top of this. + */ + accessRestrictions?: Array<{ + /** + * Permit access to make requests to this plugin. + * + * Can be further refined by setting additional fields below. + */ + plugin: string; + /** + * If given, this method is limited to only performing actions + * with these named permissions in this plugin. + * + * Note that this only applies where permissions checks are + * enabled in the first place. Endpoints that are not protected by + * the permissions system at all, are not affected by this + * setting. + */ + permission?: string | Array; + /** + * If given, this method is limited to only performing actions + * whose permissions have these attributes. + * + * Note that this only applies where permissions checks are + * enabled in the first place. Endpoints that are not protected by + * the permissions system at all, are not affected by this + * setting. + */ + permissionAttribute?: { + /** + * One of more of 'create', 'read', 'update', or 'delete'. + */ + action?: string | Array; + }; + }>; } >; }; diff --git a/packages/backend-defaults/migrations/scheduler/20240712211735_nullable_next_run.js b/packages/backend-defaults/migrations/scheduler/20240712211735_nullable_next_run.js new file mode 100644 index 0000000000..efbef1c76f --- /dev/null +++ b/packages/backend-defaults/migrations/scheduler/20240712211735_nullable_next_run.js @@ -0,0 +1,41 @@ +/* + * 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. + */ + +// @ts-check + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = async function up(knex) { + await knex.schema.alterTable('backstage_backend_tasks__tasks', table => { + table.setNullable('next_run_start_at'); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = async function down(knex) { + await knex + .delete() + .from('backstage_backend_tasks__tasks') + .where({ next_run_start_at: null }); + await knex.schema.alterTable('backstage_backend_tasks__tasks', table => { + table.dropNullable('next_run_start_at'); + }); +}; diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 824f1893bd..3280a3cf9d 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", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" @@ -165,12 +165,13 @@ "minimist": "^1.2.5", "morgan": "^1.10.0", "mysql2": "^3.0.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "node-forge": "^1.3.1", "p-limit": "^3.1.0", "path-to-regexp": "^6.2.1", "pg": "^8.11.3", "pg-connection-string": "^2.3.0", + "pg-format": "^1.0.4", "raw-body": "^2.4.1", "selfsigned": "^2.0.0", "stoppable": "^1.1.0", @@ -191,6 +192,7 @@ "@types/http-errors": "^2.0.0", "@types/morgan": "^1.9.0", "@types/node-forge": "^1.3.0", + "@types/pg-format": "^1.0.5", "@types/stoppable": "^1.1.0", "aws-sdk-client-mock": "^4.0.0", "http-errors": "^2.0.0", diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index e3195748a6..4b0f497980 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -14,12 +14,7 @@ * limitations under the License. */ -import { - Backend, - createSpecializedBackend, - identityServiceFactory, - tokenManagerServiceFactory, -} from '@backstage/backend-app-api'; +import { Backend, createSpecializedBackend } from '@backstage/backend-app-api'; import { authServiceFactory } from '@backstage/backend-defaults/auth'; import { cacheServiceFactory } from '@backstage/backend-defaults/cache'; import { databaseServiceFactory } from '@backstage/backend-defaults/database'; @@ -40,26 +35,24 @@ 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, + lifecycleServiceFactory, + loggerServiceFactory, + permissionsServiceFactory, + rootHealthServiceFactory, + rootHttpRouterServiceFactory, + rootLifecycleServiceFactory, + rootLoggerServiceFactory, + schedulerServiceFactory, + userInfoServiceFactory, + urlReaderServiceFactory, + eventsServiceFactory, ]; /** diff --git a/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts b/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts index 159da79435..05d62a54a2 100644 --- a/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts +++ b/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { TokenManager } from '@backstage/backend-common'; import { AuthService, BackstageCredentials, @@ -23,7 +22,7 @@ import { BackstageServicePrincipal, BackstageUserPrincipal, } from '@backstage/backend-plugin-api'; -import { AuthenticationError, ForwardedError } from '@backstage/errors'; +import { AuthenticationError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; import { decodeJwt } from 'jose'; import { ExternalTokenHandler } from './external/ExternalTokenHandler'; @@ -43,7 +42,6 @@ export class DefaultAuthService implements AuthService { private readonly userTokenHandler: UserTokenHandler, private readonly pluginTokenHandler: PluginTokenHandler, private readonly externalTokenHandler: ExternalTokenHandler, - private readonly tokenManager: TokenManager, private readonly pluginId: string, private readonly disableDefaultAuthPolicy: boolean, private readonly pluginKeySource: PluginKeySource, @@ -151,52 +149,28 @@ export class DefaultAuthService implements AuthService { return { token: '' }; } - const targetSupportsNewAuth = - await this.pluginTokenHandler.isTargetPluginSupported(targetPluginId); - // check whether a plugin support the new auth system // by checking the public keys endpoint existance. switch (type) { // TODO: Check whether the principal is ourselves case 'service': - if (targetSupportsNewAuth) { - return this.pluginTokenHandler.issueToken({ - pluginId: this.pluginId, - targetPluginId, - }); - } - // If the target plugin does not support the new auth service, fall back to using old token format - return this.tokenManager.getToken().catch(error => { - throw new ForwardedError( - `Unable to generate legacy token for communication with the '${targetPluginId}' plugin. ` + - `You will typically encounter this error when attempting to call a plugin that does not exist, or is deployed with an old version of Backstage`, - error, - ); + return this.pluginTokenHandler.issueToken({ + pluginId: this.pluginId, + targetPluginId, }); case 'user': { const { token } = internalForward; if (!token) { throw new Error('User credentials is unexpectedly missing token'); } - // If the target plugin supports the new auth service we issue a service - // on-behalf-of token rather than forwarding the user token - if (targetSupportsNewAuth) { - const onBehalfOf = await this.userTokenHandler.createLimitedUserToken( - token, - ); - return this.pluginTokenHandler.issueToken({ - pluginId: this.pluginId, - targetPluginId, - onBehalfOf, - }); - } - - if (this.userTokenHandler.isLimitedUserToken(token)) { - throw new AuthenticationError( - `Unable to call '${targetPluginId}' plugin on behalf of user, because the target plugin does not support on-behalf-of tokens or the plugin doesn't exist`, - ); - } - return { token }; + const onBehalfOf = await this.userTokenHandler.createLimitedUserToken( + token, + ); + return this.pluginTokenHandler.issueToken({ + pluginId: this.pluginId, + targetPluginId, + onBehalfOf, + }); } default: throw new AuthenticationError( diff --git a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts index 22357d0f49..cb672ed7f1 100644 --- a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.test.ts @@ -17,27 +17,20 @@ import { ServiceFactoryTester, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; -import { tokenManagerServiceFactory } from '@backstage/backend-app-api'; import { authServiceFactory } from './authServiceFactory'; import { base64url, decodeJwt } from 'jose'; import { discoveryServiceFactory } from '../discovery'; -import { - BackstageServicePrincipal, - BackstageUserPrincipal, -} from '@backstage/backend-plugin-api'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { InternalBackstageCredentials } from './types'; import { toInternalBackstageCredentials } from './helpers'; const server = setupServer(); // TODO: Ship discovery mock service in the service factory tester const mockDeps = [ - discoveryServiceFactory(), - tokenManagerServiceFactory, + discoveryServiceFactory, mockServices.rootConfig.factory({ data: { backend: { @@ -68,47 +61,45 @@ const mockDeps = [ ]; describe('authServiceFactory', () => { - setupRequestMockHandlers(server); + registerMswTestHooks(server); afterEach(() => { jest.useRealTimers(); }); - it('should authenticate issued tokens with legacy auth', async () => { + it('should not support tokens issued with legacy auth', async () => { server.use( rest.get( 'http://localhost:7007/api/catalog/.backstage/auth/v1/jwks.json', (_req, res, ctx) => res(ctx.status(404)), ), + rest.get( + 'http://localhost:7007/api/search/.backstage/auth/v1/jwks.json', + (_req, res, ctx) => res(ctx.status(404)), + ), ); const tester = ServiceFactoryTester.from(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(), targetPluginId: 'catalog', }); - await expect(searchAuth.authenticate(searchToken)).resolves.toEqual( - expect.objectContaining({ - principal: { - type: 'service', - subject: 'external:backstage-plugin', - }, - }), + await expect( + searchAuth.authenticate(searchToken), + ).rejects.toMatchInlineSnapshot( + `[AuthenticationError: Received a plugin token where the source 'search' plugin unexpectedly does not have a JWKS endpoint. The target plugin needs to be migrated to be installed in an app using the new backend system.]`, ); - await expect(catalogAuth.authenticate(searchToken)).resolves.toEqual( - expect.objectContaining({ - principal: { - type: 'service', - subject: 'external:backstage-plugin', - }, - }), + await expect( + catalogAuth.authenticate(searchToken), + ).rejects.toMatchInlineSnapshot( + `[AuthenticationError: Received a plugin token where the source 'search' plugin unexpectedly does not have a JWKS endpoint. The target plugin needs to be migrated to be installed in an app using the new backend system.]`, ); }); @@ -117,8 +108,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( @@ -151,7 +142,7 @@ describe('authServiceFactory', () => { ); }); - it('should forward user token if target plugin does not support new auth service', async () => { + it('should issue a service token for the new system even if the target plugin does not support it', async () => { server.use( rest.get( 'http://localhost:7007/api/permission/.backstage/auth/v1/jwks.json', @@ -163,56 +154,17 @@ describe('authServiceFactory', () => { dependencies: mockDeps, }); - const catalogAuth = await tester.get('catalog'); - - await expect( - catalogAuth.getPluginRequestToken({ - onBehalfOf: { - $$type: '@backstage/BackstageCredentials', - version: 'v1', - authMethod: 'token', - token: 'alice-token', - principal: { - type: 'user', - userEntityRef: 'user:default/alice', - }, - } as InternalBackstageCredentials, - targetPluginId: 'permission', - }), - ).resolves.toEqual({ token: 'alice-token' }); - }); - - it('should issue a new service token with token manager if target plugin does not support new auth service', async () => { - server.use( - rest.get( - 'http://localhost:7007/api/permission/.backstage/auth/v1/jwks.json', - (_req, res, ctx) => res(ctx.status(404)), - ), - ); - - const tester = ServiceFactoryTester.from(authServiceFactory, { - dependencies: mockDeps, - }); - - const catalogAuth = await tester.get('catalog'); + const catalogAuth = await tester.getSubject('catalog'); const { token } = await catalogAuth.getPluginRequestToken({ - onBehalfOf: { - $$type: '@backstage/BackstageCredentials', - version: 'v1', - authMethod: 'token', - token: 'some-upstream-service-token', - principal: { - type: 'service', - subject: 'external:upstream-service', - }, - } as InternalBackstageCredentials, + onBehalfOf: await catalogAuth.getOwnServiceCredentials(), targetPluginId: 'permission', }); expect(decodeJwt(token)).toEqual( expect.objectContaining({ - sub: 'backstage-server', + sub: 'catalog', + aud: 'permission', }), ); }); @@ -261,7 +213,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 +270,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: { @@ -415,15 +367,6 @@ describe('authServiceFactory', () => { targetPluginId: 'permission', }); expect(decodeJwt(oboToken2).obo).toBe(limitedToken); - - await expect( - catalogAuth.getPluginRequestToken({ - onBehalfOf: oboCredentials, - targetPluginId: 'kubernetes', - }), - ).rejects.toThrow( - "Unable to call 'kubernetes' plugin on behalf of user, because the target plugin does not support on-behalf-of tokens or the plugin doesn't exist", - ); }); it('should eagerly reject access to external access tokens based on plugin id', async () => { @@ -431,7 +374,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 +393,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/authServiceFactory.ts b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts index e275e2be00..34071ca541 100644 --- a/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/auth/authServiceFactory.ts @@ -41,13 +41,8 @@ export const authServiceFactory = createServiceFactory({ discovery: coreServices.discovery, plugin: coreServices.pluginMetadata, database: coreServices.database, - // Re-using the token manager makes sure that we use the same generated keys for - // development as plugins that have not yet been migrated. It's important that this - // keeps working as long as there are plugins that have not been migrated to the - // new auth services in the new backend system. - tokenManager: coreServices.tokenManager, }, - async factory({ config, discovery, plugin, tokenManager, logger, database }) { + async factory({ config, discovery, plugin, logger, database }) { const disableDefaultAuthPolicy = config.getOptionalBoolean( 'backend.auth.dangerouslyDisableDefaultAuthPolicy', @@ -84,7 +79,6 @@ export const authServiceFactory = createServiceFactory({ userTokens, pluginTokens, externalTokens, - tokenManager, plugin.getId(), disableDefaultAuthPolicy, keySource, 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/plugin/PluginTokenHandler.ts b/packages/backend-defaults/src/entrypoints/auth/plugin/PluginTokenHandler.ts index a401469fdc..ff594ecbd4 100644 --- a/packages/backend-defaults/src/entrypoints/auth/plugin/PluginTokenHandler.ts +++ b/packages/backend-defaults/src/entrypoints/auth/plugin/PluginTokenHandler.ts @@ -146,7 +146,9 @@ export class PluginTokenHandler { return { token }; } - async isTargetPluginSupported(targetPluginId: string): Promise { + private async isTargetPluginSupported( + targetPluginId: string, + ): Promise { if (this.supportedTargetPlugins.has(targetPluginId)) { return true; } @@ -199,7 +201,8 @@ export class PluginTokenHandler { // Double check that the target plugin has a valid JWKS endpoint, otherwise avoid creating a remote key set if (!(await this.isTargetPluginSupported(pluginId))) { throw new AuthenticationError( - `Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint`, + `Received a plugin token where the source '${pluginId}' plugin unexpectedly does not have a JWKS endpoint. ` + + 'The target plugin needs to be migrated to be installed in an app using the new backend system.', ); } 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/cache/CacheManager.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts index 89fabfe977..873bcf9648 100644 --- a/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts +++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts @@ -111,23 +111,10 @@ export class CacheManager { return { getClient: (defaultOptions = {}) => { const clientFactory = (options: CacheServiceOptions) => { - const concreteClient = this.getClientWithTtl( + return this.getClientWithTtl( pluginId, options.defaultTtl ?? this.defaultTtl, ); - - // Always provide an error handler to avoid stopping the process. - concreteClient.on('error', (err: Error) => { - // In all cases, just log the error. - this.logger?.error('Failed to create cache client', err); - - // Invoke any custom error handler if provided. - if (typeof this.errorHandler === 'function') { - this.errorHandler(err); - } - }); - - return concreteClient; }; return new DefaultCacheClient( @@ -149,11 +136,17 @@ export class CacheManager { return (pluginId, defaultTtl) => { if (!store) { store = new KeyvRedis(this.connection); + // Always provide an error handler to avoid stopping the process + store.on('error', (err: Error) => { + this.logger?.error('Failed to create redis cache client', err); + this.errorHandler?.(err); + }); } return new Keyv({ namespace: pluginId, ttl: defaultTtl, store, + emitErrors: false, useRedisSets: this.useRedisSets, }); }; @@ -165,10 +158,16 @@ export class CacheManager { return (pluginId, defaultTtl) => { if (!store) { store = new KeyvMemcache(this.connection); + // Always provide an error handler to avoid stopping the process + store.on('error', (err: Error) => { + this.logger?.error('Failed to create memcache cache client', err); + this.errorHandler?.(err); + }); } return new Keyv({ namespace: pluginId, ttl: defaultTtl, + emitErrors: false, store, }); }; @@ -180,6 +179,7 @@ export class CacheManager { new Keyv({ namespace: pluginId, ttl: defaultTtl, + emitErrors: false, store, }); } diff --git a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts index 444bbc0d18..46b958789d 100644 --- a/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts +++ b/packages/backend-defaults/src/entrypoints/database/connectors/postgres.ts @@ -29,6 +29,7 @@ import { Connector } from '../types'; import defaultNameOverride from './defaultNameOverride'; import defaultSchemaOverride from './defaultSchemaOverride'; import { mergeDatabaseConfig } from './mergeDatabaseConfig'; +import format from 'pg-format'; // Limits the number of concurrent DDL operations to 1 const ddlLimiter = limiterFactory(1); @@ -52,7 +53,8 @@ export function createPgDatabaseClient( database.client.pool.on( 'createSuccess', async (_event: number, pgClient: Client) => { - await pgClient.query(`SET ROLE ${role}`); + const query = format('SET ROLE %I', role); + await pgClient.query(query); }, ); } diff --git a/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/httpRouter/httpRouterServiceFactory.test.ts index 58c074f985..3c721c9ed9 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', @@ -136,7 +110,7 @@ describe('httpRouterFactory', () => { }); const defaultServices = [ - httpRouterServiceFactory(), + httpRouterServiceFactory, mockServices.httpAuth.factory({ defaultCredentials: mockCredentials.none(), }), 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/permissions/permissionsServiceFactory.ts b/packages/backend-defaults/src/entrypoints/permissions/permissionsServiceFactory.ts index 873d6a4fa0..000d71965b 100644 --- a/packages/backend-defaults/src/entrypoints/permissions/permissionsServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/permissions/permissionsServiceFactory.ts @@ -35,13 +35,11 @@ export const permissionsServiceFactory = createServiceFactory({ auth: coreServices.auth, config: coreServices.rootConfig, discovery: coreServices.discovery, - tokenManager: coreServices.tokenManager, }, - async factory({ auth, config, discovery, tokenManager }) { + async factory({ auth, config, discovery }) { return ServerPermissionClient.fromConfig(config, { auth, discovery, - tokenManager, }); }, }); diff --git a/packages/backend-defaults/src/entrypoints/rootConfig/rootConfigServiceFactory.ts b/packages/backend-defaults/src/entrypoints/rootConfig/rootConfigServiceFactory.ts index 5752a9c634..ab31ec4850 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/http/MiddlewareFactory.ts b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts index 77136fd7e5..f38c10820a 100644 --- a/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts +++ b/packages/backend-defaults/src/entrypoints/rootHttpRouter/http/MiddlewareFactory.ts @@ -140,8 +140,9 @@ export class MiddlewareFactory { const logger = this.#logger.child({ type: 'incomingRequest', }); - - return morgan('combined', { + const customMorganFormat = + '[:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'; + return morgan(customMorganFormat, { stream: { write(message: string) { logger.info(message.trimEnd()); 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..a18ddbe096 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/lib/PluginTaskSchedulerImpl.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts index c5c4c8708a..3e0782d4a3 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts @@ -347,6 +347,7 @@ describe('PluginTaskManagerImpl', () => { expect(parseDuration({ milliseconds: 5000 })).toEqual('PT5S'); expect(parseDuration(Duration.fromMillis(5000))).toEqual('PT5S'); expect(parseDuration({ cron: '1 * * * *' })).toEqual('1 * * * *'); + expect(parseDuration({ trigger: 'manual' })).toEqual('manual'); }); }); }); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts index 40aa9d521c..f8d06baf56 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts @@ -155,6 +155,9 @@ export function parseDuration( if (typeof frequency === 'object' && 'cron' in frequency) { return frequency.cron; } + if (typeof frequency === 'object' && 'trigger' in frequency) { + return frequency.trigger; + } const parsed = Duration.isDuration(frequency) ? frequency diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts index 1cafe00670..5db82f3ece 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts @@ -503,4 +503,32 @@ describe('TaskWorker', () => { await knex.destroy(); }, ); + + it.each(databases.eachSupportedId())( + 'next_run_start_at is not set for manually-triggered tasks, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn( + async () => new Promise(resolve => setTimeout(resolve, 50)), + ); + + const initialSettings: TaskSettingsV2 = { + version: 2, + cadence: 'manual', + timeoutAfterDuration: 'PT1M', + }; + + const worker = new TaskWorker('task99', fn, knex, logger); + await worker.persistTask(initialSettings); + await worker.tryClaimTask('ticket', initialSettings); + await worker.tryReleaseTask('ticket', initialSettings); + + const row = (await knex(DB_TASKS_TABLE))[0]; + expect(row.next_run_start_at).toBeNull(); + + await knex.destroy(); + }, + ); }); diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts index a1c4ec44fd..c5570a8e30 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts @@ -53,8 +53,8 @@ export class TaskWorker { ); let workCheckFrequency = this.workCheckFrequency; - const isCron = !settings?.cadence.startsWith('P'); - if (!isCron) { + const isDuration = settings?.cadence.startsWith('P'); + if (isDuration) { const cadence = Duration.fromISO(settings.cadence); if (cadence < workCheckFrequency) { workCheckFrequency = cadence; @@ -175,7 +175,9 @@ export class TaskWorker { // read it back again. taskSettingsV2Schema.parse(settings); - const isCron = !settings?.cadence.startsWith('P'); + const isManual = settings?.cadence === 'manual'; + const isDuration = settings?.cadence.startsWith('P'); + const isCron = !isManual && !isDuration; let startAt: Knex.Raw | undefined; let nextStartAt: Knex.Raw | undefined; @@ -194,6 +196,9 @@ export class TaskWorker { nextStartAt = this.nextRunAtRaw(time); startAt ||= nextStartAt; + } else if (isManual) { + nextStartAt = this.knex.raw('null'); + startAt ||= nextStartAt; } else { startAt ||= this.knex.fn.now(); nextStartAt = nowPlus(Duration.fromISO(settings.cadence), this.knex); @@ -317,7 +322,9 @@ export class TaskWorker { ticket: string, settings: TaskSettingsV2, ): Promise { - const isCron = !settings?.cadence.startsWith('P'); + const isManual = settings?.cadence === 'manual'; + const isDuration = settings?.cadence.startsWith('P'); + const isCron = !isManual && !isDuration; let nextRun: Knex.Raw; if (isCron) { @@ -325,6 +332,8 @@ export class TaskWorker { this.logger.debug(`task: ${this.taskId} will next occur around ${time}`); nextRun = this.nextRunAtRaw(time); + } else if (isManual) { + nextRun = this.knex.raw('null'); } else { const dt = Duration.fromISO(settings.cadence).as('seconds'); this.logger.debug( diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts index 61e6c1d9a8..173c989543 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts @@ -40,6 +40,10 @@ function isValidCronFormat(c: string | undefined): boolean { } } +function isValidTrigger(t: string): boolean { + return t === 'manual'; +} + export const taskSettingsV1Schema = z.object({ version: z.literal(1), initialDelayDuration: z @@ -68,6 +72,11 @@ export const taskSettingsV2Schema = z.object({ cadence: z .string() .refine(isValidCronFormat, { message: 'Invalid cron' }) + .or( + z.string().refine(isValidTrigger, { + message: "Invalid trigger, expecting 'manual'", + }), + ) .or( z.string().refine(isValidOptionalDurationString, { message: 'Invalid duration, expecting ISO Period', diff --git a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts index 7b889b3a0e..2d84f6ce87 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts @@ -20,9 +20,9 @@ import { schedulerServiceFactory } from './schedulerServiceFactory'; describe('schedulerFactory', () => { it('creates sidecar database features', async () => { - const tester = ServiceFactoryTester.from(schedulerServiceFactory()); + 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/index.ts b/packages/backend-defaults/src/entrypoints/urlReader/index.ts index cf927ea031..05271966c9 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/index.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/index.ts @@ -15,4 +15,7 @@ */ export * from './lib'; -export { urlReaderServiceFactory } from './urlReaderServiceFactory'; +export { + urlReaderServiceFactory, + urlReaderFactoriesServiceRef, +} from './urlReaderServiceFactory'; 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/urlReader/urlReaderServiceFactory.ts b/packages/backend-defaults/src/entrypoints/urlReader/urlReaderServiceFactory.ts index 810882edf8..2639e99d83 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/urlReaderServiceFactory.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/urlReaderServiceFactory.ts @@ -14,12 +14,36 @@ * limitations under the License. */ +import { ReaderFactory } from './lib'; import { UrlReaders } from './lib/UrlReaders'; import { coreServices, createServiceFactory, + createServiceRef, } from '@backstage/backend-plugin-api'; +/** + * @public + * A non-singleton reference to URL Reader factory services. + * + * @example + * Creating a service factory implementation for a Custom URL Reader. + * ```ts + * createServiceFactory({ + * service: urlReaderFactoriesServiceRef, + * deps: {}, + * async factory() { + * return CustomUrlReader.factory; + * }, + * }); + * ``` + */ +export const urlReaderFactoriesServiceRef = createServiceRef({ + id: 'core.urlReader.factories', + scope: 'plugin', + multiton: true, +}); + /** * Reading content from external systems. * @@ -34,11 +58,13 @@ export const urlReaderServiceFactory = createServiceFactory({ deps: { config: coreServices.rootConfig, logger: coreServices.logger, + factories: urlReaderFactoriesServiceRef, }, - async factory({ config, logger }) { + async factory({ config, logger, factories }) { return UrlReaders.default({ config, logger, + factories, }); }, }); 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-defaults/src/migrations.test.ts b/packages/backend-defaults/src/migrations.test.ts new file mode 100644 index 0000000000..579c32e626 --- /dev/null +++ b/packages/backend-defaults/src/migrations.test.ts @@ -0,0 +1,123 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Knex } from 'knex'; +import { TestDatabases } from '@backstage/backend-test-utils'; +import fs from 'fs'; + +const migrationsDir = `${__dirname}/../migrations/scheduler`; +const migrationsFiles = fs.readdirSync(migrationsDir).sort(); + +async function migrateUpOnce(knex: Knex): Promise { + await knex.migrate.up({ directory: migrationsDir }); +} + +async function migrateDownOnce(knex: Knex): Promise { + await knex.migrate.down({ directory: migrationsDir }); +} + +async function migrateUntilBefore(knex: Knex, target: string): Promise { + const index = migrationsFiles.indexOf(target); + if (index === -1) { + throw new Error(`Migration ${target} not found`); + } + for (let i = 0; i < index; i++) { + await migrateUpOnce(knex); + } +} + +jest.setTimeout(60_000); + +describe('migrations', () => { + const databases = TestDatabases.create(); + + it.each(databases.eachSupportedId())( + '20210928160613_init.js, %p', + async databaseId => { + const knex = await databases.init(databaseId); + + await migrateUntilBefore(knex, '20210928160613_init.js'); + await migrateUpOnce(knex); + + await knex('backstage_backend_tasks__tasks').insert({ + id: 'test', + settings_json: '{}', + next_run_start_at: knex.fn.now(), + }); + + await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([ + { + id: 'test', + settings_json: '{}', + next_run_start_at: expect.anything(), + current_run_ticket: null, + current_run_started_at: null, + current_run_expires_at: null, + }, + ]); + + await migrateDownOnce(knex); + + // This looks odd - you might expect a .toThrow at the end but that + // actually is flaky for some reason specifically on sqlite when + // performing multiple runs in sequence + await expect(knex('backstage_backend_tasks__tasks')).rejects.toEqual( + expect.anything(), + ); + + await knex.destroy(); + }, + ); + + it.each(databases.eachSupportedId())( + '20240712211735_nullable_next_run.js, %p', + async databaseId => { + const knex = await databases.init(databaseId); + + await migrateUntilBefore(knex, '20240712211735_nullable_next_run.js'); + await migrateUpOnce(knex); + + await knex('backstage_backend_tasks__tasks').insert({ + id: 'test', + settings_json: '{}', + next_run_start_at: knex.raw('null'), + }); + + await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([ + { + id: 'test', + settings_json: '{}', + next_run_start_at: null, + current_run_ticket: null, + current_run_started_at: null, + current_run_expires_at: null, + }, + ]); + + await migrateDownOnce(knex); + + await expect( + knex('backstage_backend_tasks__tasks').insert({ + id: 'test', + settings_json: '{}', + next_run_start_at: knex.raw('null'), + }), + ).rejects.toEqual(expect.anything()); + + await knex.destroy(); + }, + ); +}); diff --git a/packages/backend-dev-utils/CHANGELOG.md b/packages/backend-dev-utils/CHANGELOG.md index d60b686e79..81686fb493 100644 --- a/packages/backend-dev-utils/CHANGELOG.md +++ b/packages/backend-dev-utils/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/backend-dev-utils +## 0.1.5 + +### Patch Changes + +- 3a35172: Fix `EventEmitter` memory leak in the development utilities + ## 0.1.4 ### Patch Changes diff --git a/packages/backend-dev-utils/package.json b/packages/backend-dev-utils/package.json index b24c9102b2..4d22c5010f 100644 --- a/packages/backend-dev-utils/package.json +++ b/packages/backend-dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-dev-utils", - "version": "0.1.4", + "version": "0.1.5", "backstage": { "role": "node-library" }, diff --git a/packages/backend-dev-utils/src/ipcClient.ts b/packages/backend-dev-utils/src/ipcClient.ts index d9ec7821de..6e1d3d181e 100644 --- a/packages/backend-dev-utils/src/ipcClient.ts +++ b/packages/backend-dev-utils/src/ipcClient.ts @@ -35,6 +35,8 @@ type Response = error: Error; }; +type ResponseHandler = (response: Response) => void; + const requestType = '@backstage/cli/channel/request'; const responseType = '@backstage/cli/channel/response'; @@ -48,21 +50,40 @@ const IPC_TIMEOUT_MS = 5000; export class BackstageIpcClient { #messageId = 0; #sendMessage: SendMessage; + #handlers: Map = new Map(); /** * Creates a new client if we're in a child process with IPC and BACKSTAGE_CLI_CHANNEL is set. */ static create(): BackstageIpcClient | undefined { const sendMessage = process.send?.bind(process); - return sendMessage && process.env.BACKSTAGE_CLI_CHANNEL - ? new BackstageIpcClient(sendMessage) - : undefined; + const client = + sendMessage && process.env.BACKSTAGE_CLI_CHANNEL + ? new BackstageIpcClient(sendMessage) + : undefined; + + if (client) { + process.on('message', (message, _sendHandle) => + client.handleMessage(message, _sendHandle), + ); + } + + return client; } constructor(sendMessage: SendMessage) { this.#sendMessage = sendMessage; } + private handleMessage(message: unknown, _sendHandle: unknown) { + const isResponse = (msg: unknown): msg is Response => + (msg as Response)?.type === responseType; + + if (isResponse(message)) { + this.#handlers.get(message.id)?.(message); + } + } + /** * Send a request to the parent process and wait for a response. */ @@ -82,17 +103,10 @@ export class BackstageIpcClient { let timeout: NodeJS.Timeout | undefined = undefined; - const messageHandler = (response: Response) => { - if (response?.type !== responseType) { - return; - } - if (response.id !== id) { - return; - } - + const responseHandler: ResponseHandler = (response: Response) => { clearTimeout(timeout); timeout = undefined; - process.removeListener('message', messageHandler); + this.#handlers.delete(id); if ('error' in response) { const error = new Error(response.error.message); @@ -107,12 +121,11 @@ export class BackstageIpcClient { timeout = setTimeout(() => { reject(new Error(`IPC request '${method}' with ID ${id} timed out`)); - process.removeListener('message', messageHandler); + this.#handlers.delete(id); }, IPC_TIMEOUT_MS); timeout.unref(); - process.addListener('message', messageHandler as () => void); - + this.#handlers.set(id, responseHandler); this.#sendMessage(request, (e: Error) => { if (e) { reject(e); diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index 4ec9489674..5828cebe32 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,194 @@ # @backstage/backend-dynamic-feature-service +## 0.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- b63d378: Update internal imports +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @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 + - @backstage/plugin-events-backend@0.3.10 + - @backstage/plugin-events-node@0.3.9 + +## 0.2.16-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-app-api@0.8.1-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.23-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-events-backend@0.3.10-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-search-common@1.2.14-next.1 + +## 0.2.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-app-api@0.8.1-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-app-node@0.1.23-next.2 + - @backstage/plugin-events-backend@0.3.10-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/cli-node@0.2.7 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-app-api@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-app-node@0.1.23-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/backend-tasks@0.5.28-next.1 + - @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.1 + - @backstage/plugin-events-backend@0.3.10-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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..148825dce1 100644 --- a/packages/backend-dynamic-feature-service/api-report.md +++ b/packages/backend-dynamic-feature-service/api-report.md @@ -4,7 +4,6 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; import { BackstagePackageJson } from '@backstage/cli-node'; import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; import { Config } from '@backstage/config'; @@ -25,15 +24,15 @@ import { PermissionPolicy } from '@backstage/plugin-permission-node'; import { PluginCacheManager } from '@backstage/backend-common'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { RootLoggerService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { ServiceFactory } 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'; import { TokenManager } from '@backstage/backend-common'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public (undocumented) export interface BackendDynamicPlugin extends BaseDynamicPlugin { @@ -114,18 +113,20 @@ export interface DynamicPluginsFactoryOptions { } // @public (undocumented) -export const dynamicPluginsFeatureDiscoveryServiceFactory: () => ServiceFactory< +export const dynamicPluginsFeatureDiscoveryServiceFactory: ServiceFactory< FeatureDiscoveryService, - 'root' + 'root', + 'singleton' >; // @public (undocumented) -export const dynamicPluginsFrontendSchemas: BackendFeatureCompat; +export const dynamicPluginsFrontendSchemas: BackendFeature; // @public (undocumented) -export const dynamicPluginsRootLoggerServiceFactory: () => ServiceFactory< +export const dynamicPluginsRootLoggerServiceFactory: ServiceFactory< RootLoggerService, - 'root' + 'root', + 'singleton' >; // @public (undocumented) @@ -142,19 +143,34 @@ export interface DynamicPluginsSchemasService { } // @public (undocumented) -export const dynamicPluginsSchemasServiceFactory: ( - options?: DynamicPluginsSchemasOptions | undefined, -) => ServiceFactory; +export const dynamicPluginsSchemasServiceFactory: ServiceFactory< + DynamicPluginsSchemasService, + 'root', + 'singleton' +>; // @public (undocumented) -export const dynamicPluginsServiceFactory: ( - options?: DynamicPluginsFactoryOptions | undefined, -) => ServiceFactory; +export const dynamicPluginsSchemasServiceFactoryWithOptions: ( + options?: DynamicPluginsSchemasOptions, +) => ServiceFactory; + +// @public (undocumented) +export const dynamicPluginsServiceFactory: ServiceFactory< + DynamicPluginProvider, + 'root', + 'singleton' +>; + +// @public (undocumented) +export const dynamicPluginsServiceFactoryWithOptions: ( + options?: DynamicPluginsFactoryOptions, +) => ServiceFactory; // @public (undocumented) export const dynamicPluginsServiceRef: ServiceRef< DynamicPluginProvider, - 'root' + 'root', + 'singleton' >; // @public (undocumented) @@ -199,7 +215,7 @@ export interface LegacyBackendPluginInstaller { // (undocumented) search?( indexBuilder: IndexBuilder, - schedule: TaskRunner, + schedule: SchedulerServiceTaskRunner, env: LegacyPluginEnvironment, ): void; } @@ -210,11 +226,11 @@ export type LegacyPluginEnvironment = { cache: PluginCacheManager; database: PluginDatabaseManager; config: Config; - reader: UrlReader; + reader: UrlReaderService; discovery: PluginEndpointDiscovery; tokenManager: TokenManager; permissions: PermissionEvaluator; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; identity: IdentityApi; eventBroker: EventBroker; events: EventsService; diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 60ff4d2d4e..ac06fcd8d0 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,24 +1,29 @@ { "name": "@backstage/backend-dynamic-feature-service", + "version": "0.3.0", "description": "Backstage dynamic feature service", - "version": "0.2.14-next.0", - "main": "src/index.ts", - "types": "src/index.ts", + "backstage": { + "role": "node-library" + }, "publishConfig": { "access": "public" }, + "keywords": [ + "backstage" + ], + "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", "directory": "packages/backend-dynamic-feature-service" }, - "backstage": { - "role": "node-library" - }, + "license": "Apache-2.0", "exports": { ".": "./src/index.ts", "./package.json": "./package.json" }, + "main": "src/index.ts", + "types": "src/index.ts", "typesVersions": { "*": { "package.json": [ @@ -26,25 +31,24 @@ ] } }, - "homepage": "https://backstage.io", - "keywords": [ - "backstage" + "files": [ + "dist", + "config.d.ts" ], - "license": "Apache-2.0", "scripts": { "build": "backstage-cli package build", + "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "start": "backstage-cli package start", + "test": "backstage-cli package test" }, "dependencies": { "@backstage/backend-app-api": "workspace:^", "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/cli-common": "workspace:^", "@backstage/cli-node": "workspace:^", "@backstage/config": "workspace:^", @@ -73,9 +77,5 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "wait-for-expect": "^3.0.2" - }, - "files": [ - "dist", - "config.d.ts" - ] + } } diff --git a/packages/backend-dynamic-feature-service/src/manager/index.ts b/packages/backend-dynamic-feature-service/src/manager/index.ts index dd6aaacfe9..542bab68ac 100644 --- a/packages/backend-dynamic-feature-service/src/manager/index.ts +++ b/packages/backend-dynamic-feature-service/src/manager/index.ts @@ -34,6 +34,7 @@ export { DynamicPluginManager, dynamicPluginsFeatureDiscoveryServiceFactory, dynamicPluginsServiceFactory, + dynamicPluginsServiceFactoryWithOptions, dynamicPluginsServiceRef, } from './plugin-manager'; diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts index 8531624509..5c01a3dfe5 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts @@ -16,7 +16,7 @@ import { DynamicPluginManager, - dynamicPluginsServiceFactory, + dynamicPluginsServiceFactoryWithOptions, } from './plugin-manager'; import { BackendFeature, @@ -37,15 +37,13 @@ import { import { ScannedPluginManifest, ScannedPluginPackage } from '../scanner/types'; import { randomUUID } from 'crypto'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { - createSpecializedBackend, - rootLifecycleServiceFactory, -} from '@backstage/backend-app-api'; +import { createSpecializedBackend } from '@backstage/backend-app-api'; import { ConfigSources } from '@backstage/config-loader'; import { Logs, MockedLogger, LogContent } from '../__testUtils__/testUtils'; import { PluginScanner } from '../scanner/plugin-scanner'; import { findPaths } from '@backstage/cli-common'; import { createMockDirectory } from '@backstage/backend-test-utils'; +import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle'; describe('backend-dynamic-feature-service', () => { const mockDir = createMockDirectory(); @@ -248,6 +246,59 @@ describe('backend-dynamic-feature-service', () => { >([]); }, }, + { + name: 'should successfully load a backend plugin wrapped in a BackendFeatureCompatWrapper function', + packageManifest: { + name: 'backend-dynamic-plugin-test', + version: '0.0.0', + backstage: { + role: 'backend-plugin', + }, + main: 'dist/index.cjs.js', + }, + indexFile: { + relativePath: ['dist', 'index.cjs.js'], + content: ` + function backendFeatureCompatWrapper() { + return backendFeatureCompatWrapper; + } + Object.assign(backendFeatureCompatWrapper, { + $$type: "@backstage/BackendFeature", + version: "v1", + }); + const alpha = backendFeatureCompatWrapper; + exports.default = alpha; + `, + }, + expectedLogs(location) { + return { + infos: [ + { + message: `loaded dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`, + }, + ], + }; + }, + checkLoadedPlugins(plugins) { + expect(plugins).toMatchObject([ + { + name: 'backend-dynamic-plugin-test', + version: '0.0.0', + role: 'backend-plugin', + platform: 'node', + installer: { + kind: 'new', + }, + }, + ]); + const installer: NewBackendPluginInstaller = ( + plugins[0] as BackendDynamicPlugin + ).installer as NewBackendPluginInstaller; + expect((installer.install() as BackendFeature).$$type).toEqual( + '@backstage/BackendFeature', + ); + }, + }, { name: 'should fail when no index file', packageManifest: { @@ -639,7 +690,7 @@ describe('backend-dynamic-feature-service', () => { const backend = createSpecializedBackend({ defaultServiceFactories: [ - rootLifecycleServiceFactory(), + rootLifecycleServiceFactory, createServiceFactory({ service: coreServices.rootConfig, deps: {}, @@ -672,7 +723,7 @@ describe('backend-dynamic-feature-service', () => { return rootLogger; }, }), - dynamicPluginsServiceFactory({ + dynamicPluginsServiceFactoryWithOptions({ moduleLoader: _ => mockedModuleLoader, }), ], diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts index 95b0c6592c..40746ff440 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts @@ -232,8 +232,10 @@ export interface DynamicPluginsFactoryOptions { /** * @public */ -export const dynamicPluginsServiceFactory = createServiceFactory( - (options?: DynamicPluginsFactoryOptions) => ({ +export const dynamicPluginsServiceFactoryWithOptions = ( + options?: DynamicPluginsFactoryOptions, +) => + createServiceFactory({ service: dynamicPluginsServiceRef, deps: { config: coreServices.rootConfig, @@ -247,8 +249,13 @@ export const dynamicPluginsServiceFactory = createServiceFactory( moduleLoader: options?.moduleLoader?.(logger), }); }, - }), -); + }); + +/** + * @public + */ +export const dynamicPluginsServiceFactory = + dynamicPluginsServiceFactoryWithOptions(); class DynamicPluginsEnabledFeatureDiscoveryService implements FeatureDiscoveryService @@ -301,7 +308,7 @@ export const dynamicPluginsFeatureDiscoveryServiceFactory = function isBackendFeature(value: unknown): value is BackendFeature { return ( !!value && - typeof value === 'object' && + (typeof value === 'object' || typeof value === 'function') && (value as BackendFeature).$$type === '@backstage/BackendFeature' ); } diff --git a/packages/backend-dynamic-feature-service/src/manager/types.ts b/packages/backend-dynamic-feature-service/src/manager/types.ts index 60b2eb831e..a4f2937fd5 100644 --- a/packages/backend-dynamic-feature-service/src/manager/types.ts +++ b/packages/backend-dynamic-feature-service/src/manager/types.ts @@ -21,10 +21,8 @@ import { PluginDatabaseManager, PluginEndpointDiscovery, TokenManager, - UrlReader, } from '@backstage/backend-common'; import { Router } from 'express'; -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { @@ -33,7 +31,12 @@ import { HttpPostIngressOptions, } from '@backstage/plugin-events-node'; -import { BackendFeature } from '@backstage/backend-plugin-api'; +import { + BackendFeature, + UrlReaderService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; import { PackagePlatform, PackageRole } from '@backstage/cli-node'; import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -58,11 +61,11 @@ export type LegacyPluginEnvironment = { cache: PluginCacheManager; database: PluginDatabaseManager; config: Config; - reader: UrlReader; + reader: UrlReaderService; discovery: PluginEndpointDiscovery; tokenManager: TokenManager; permissions: PermissionEvaluator; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; identity: IdentityApi; eventBroker: EventBroker; events: EventsService; @@ -161,7 +164,7 @@ export interface LegacyBackendPluginInstaller { scaffolder?(env: LegacyPluginEnvironment): TemplateAction[]; search?( indexBuilder: IndexBuilder, - schedule: TaskRunner, + schedule: SchedulerServiceTaskRunner, env: LegacyPluginEnvironment, ): void; events?( diff --git a/packages/backend-dynamic-feature-service/src/schemas/index.ts b/packages/backend-dynamic-feature-service/src/schemas/index.ts index 94c167679b..14c6734b6b 100644 --- a/packages/backend-dynamic-feature-service/src/schemas/index.ts +++ b/packages/backend-dynamic-feature-service/src/schemas/index.ts @@ -16,6 +16,7 @@ export { dynamicPluginsSchemasServiceFactory, + dynamicPluginsSchemasServiceFactoryWithOptions, type DynamicPluginsSchemasService, type DynamicPluginsSchemasOptions, } from './schemas'; diff --git a/packages/backend-dynamic-feature-service/src/schemas/rootLoggerServiceFactory.ts b/packages/backend-dynamic-feature-service/src/schemas/rootLoggerServiceFactory.ts index 6681a65a75..00d5c85e6e 100644 --- a/packages/backend-dynamic-feature-service/src/schemas/rootLoggerServiceFactory.ts +++ b/packages/backend-dynamic-feature-service/src/schemas/rootLoggerServiceFactory.ts @@ -18,9 +18,9 @@ import { createServiceFactory, coreServices, } from '@backstage/backend-plugin-api'; -import { WinstonLogger } from '@backstage/backend-app-api'; +import { WinstonLogger } from '@backstage/backend-defaults/rootLogger'; import { transports, format } from 'winston'; -import { createConfigSecretEnumerator } from '@backstage/backend-app-api'; +import { createConfigSecretEnumerator } from '@backstage/backend-common'; import { loadConfigSchema } from '@backstage/config-loader'; import { getPackages } from '@manypkg/get-packages'; import { dynamicPluginsSchemasServiceRef } from './schemas'; diff --git a/packages/backend-dynamic-feature-service/src/schemas/schemas.ts b/packages/backend-dynamic-feature-service/src/schemas/schemas.ts index 0c03e93ada..1cfb42a093 100644 --- a/packages/backend-dynamic-feature-service/src/schemas/schemas.ts +++ b/packages/backend-dynamic-feature-service/src/schemas/schemas.ts @@ -71,8 +71,10 @@ export interface DynamicPluginsSchemasOptions { /** * @public */ -export const dynamicPluginsSchemasServiceFactory = createServiceFactory( - (options?: DynamicPluginsSchemasOptions) => ({ +export const dynamicPluginsSchemasServiceFactoryWithOptions = ( + options?: DynamicPluginsSchemasOptions, +) => + createServiceFactory({ service: dynamicPluginsSchemasServiceRef, deps: { config: coreServices.rootConfig, @@ -137,8 +139,13 @@ export const dynamicPluginsSchemasServiceFactory = createServiceFactory( }, }; }, - }), -); + }); + +/** + * @public + */ +export const dynamicPluginsSchemasServiceFactory = + dynamicPluginsSchemasServiceFactoryWithOptions(); /** @internal */ async function gatherDynamicPluginsSchemas( diff --git a/packages/backend-legacy/CHANGELOG.md b/packages/backend-legacy/CHANGELOG.md index 9535ba1211..3b6a1af6f4 100644 --- a/packages/backend-legacy/CHANGELOG.md +++ b/packages/backend-legacy/CHANGELOG.md @@ -1,5 +1,305 @@ # example-backend-legacy +## 0.2.101 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.40 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-scaffolder-backend@1.24.0 + - @backstage/plugin-techdocs-backend@1.10.10 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.0 + - @backstage/plugin-search-backend-module-explore@0.2.0 + - @backstage/plugin-kubernetes-backend@0.18.4 + - @backstage/plugin-permission-backend@0.5.47 + - @backstage/plugin-devtools-backend@0.3.9 + - @backstage/plugin-signals-backend@0.1.9 + - @backstage/plugin-proxy-backend@0.5.4 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-app-backend@0.3.72 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5 + - @backstage/plugin-search-backend-module-catalog@0.2.0 + - @backstage/plugin-search-backend@1.5.15 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/plugin-search-backend-module-pg@0.5.33 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10 + - @backstage/plugin-events-backend@0.3.10 + - @backstage/plugin-events-node@0.3.9 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.4 + - @backstage/plugin-signals-node@0.1.9 + +## 0.2.101-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.3 + - @backstage/plugin-techdocs-backend@1.10.10-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-scaffolder-backend@1.23.1-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-app-backend@0.3.72-next.3 + - @backstage/plugin-auth-backend@0.22.10-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-devtools-backend@0.3.9-next.3 + - @backstage/plugin-events-backend@0.3.10-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-kubernetes-backend@0.18.4-next.3 + - @backstage/plugin-permission-backend@0.5.47-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + - @backstage/plugin-proxy-backend@0.5.4-next.3 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.3 + - @backstage/plugin-search-backend@1.5.15-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.3 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.3 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.3 + - @backstage/plugin-search-backend-module-pg@0.5.33-next.3 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.3 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-signals-backend@0.1.9-next.3 + - @backstage/plugin-signals-node@0.1.9-next.3 + +## 0.2.101-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.23.1-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.2 + - @backstage/plugin-kubernetes-backend@0.18.4-next.2 + - @backstage/plugin-permission-backend@0.5.47-next.2 + - @backstage/plugin-devtools-backend@0.3.9-next.2 + - @backstage/plugin-techdocs-backend@1.10.10-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/plugin-signals-backend@0.1.9-next.2 + - @backstage/plugin-proxy-backend@0.5.4-next.2 + - @backstage/plugin-auth-backend@0.22.10-next.2 + - @backstage/plugin-app-backend@0.3.72-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-backend@1.5.15-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.2 + - @backstage/plugin-events-backend@0.3.10-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.2 + - @backstage/plugin-search-backend-module-pg@0.5.33-next.2 + - @backstage/plugin-signals-node@0.1.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.2.101-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@1.10.10-next.1 + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.1 + - @backstage/plugin-scaffolder-backend@1.23.1-next.1 + - @backstage/plugin-auth-backend@0.22.10-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.1 + - example-app@0.2.100-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-app-backend@0.3.72-next.1 + - @backstage/plugin-devtools-backend@0.3.9-next.1 + - @backstage/plugin-proxy-backend@0.5.4-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-kubernetes-backend@0.18.4-next.1 + - @backstage/plugin-permission-backend@0.5.47-next.1 + - @backstage/plugin-search-backend@1.5.15-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-auth-node@0.4.18-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.1 + - @backstage/plugin-events-backend@0.3.10-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.24-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.40-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.5.4-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.33-next.1 + - @backstage/plugin-signals-backend@0.1.9-next.1 + - @backstage/plugin-signals-node@0.1.9-next.1 + +## 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 6b9bb3523a..0bdfa350e9 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", "backstage": { "role": "backend" }, @@ -29,7 +29,8 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", - "@backstage/backend-tasks": "workspace:^", + "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", diff --git a/packages/backend-legacy/src/index.ts b/packages/backend-legacy/src/index.ts index 33520cba03..9a581e7d26 100644 --- a/packages/backend-legacy/src/index.ts +++ b/packages/backend-legacy/src/index.ts @@ -32,10 +32,8 @@ import { loadBackendConfig, notFoundHandler, ServerTokenManager, - UrlReaders, useHotMemoize, } from '@backstage/backend-common'; -import { TaskScheduler } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import healthcheck from './plugins/healthcheck'; import { metricsHandler, metricsInit } from './metrics'; @@ -57,6 +55,8 @@ import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; import { DefaultEventBroker } from '@backstage/plugin-events-backend'; import { DefaultEventsService } from '@backstage/plugin-events-node'; import { DefaultSignalsService } from '@backstage/plugin-signals-node'; +import { UrlReaders } from '@backstage/backend-defaults/urlReader'; +import { DefaultSchedulerService } from '@backstage/backend-defaults/scheduler'; function makeCreateEnv(config: Config) { const root = getRootLogger(); @@ -69,7 +69,6 @@ function makeCreateEnv(config: Config) { }); const databaseManager = DatabaseManager.fromConfig(config, { logger: root }); const cacheManager = CacheManager.fromConfig(config); - const taskScheduler = TaskScheduler.fromConfig(config, { databaseManager }); const identity = DefaultIdentityClient.create({ discovery, }); @@ -89,7 +88,10 @@ function makeCreateEnv(config: Config) { const logger = root.child({ type: 'plugin', plugin }); const database = databaseManager.forPlugin(plugin); const cache = cacheManager.forPlugin(plugin); - const scheduler = taskScheduler.forPlugin(plugin); + const scheduler = DefaultSchedulerService.create({ + logger, + database, + }); return { logger, diff --git a/packages/backend-legacy/src/types.ts b/packages/backend-legacy/src/types.ts index a75b1620c4..3f5d163925 100644 --- a/packages/backend-legacy/src/types.ts +++ b/packages/backend-legacy/src/types.ts @@ -21,24 +21,26 @@ import { PluginDatabaseManager, PluginEndpointDiscovery, TokenManager, - UrlReader, } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { EventBroker, EventsService } from '@backstage/plugin-events-node'; import { SignalsService } from '@backstage/plugin-signals-node'; +import { + UrlReaderService, + SchedulerService, +} from '@backstage/backend-plugin-api'; export type PluginEnvironment = { logger: Logger; cache: PluginCacheManager; database: PluginDatabaseManager; config: Config; - reader: UrlReader; + reader: UrlReaderService; discovery: PluginEndpointDiscovery; tokenManager: TokenManager; permissions: PermissionEvaluator; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; identity: IdentityApi; /** * @deprecated use `events` instead diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index 0040eddc57..0d58c6ad9c 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/backend-openapi-utils +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/errors@1.2.4 + +## 0.1.16-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/errors@1.2.4 + +## 0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/errors@1.2.4 + +## 0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/errors@1.2.4 + +## 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..a64ef674a9 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", "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..cb830feb33 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,361 @@ # @backstage/backend-plugin-api +## 0.8.0 + +### Minor Changes + +- 389f5a4: **BREAKING** Deleted the following deprecated `UrlReader` exports + + - ReadUrlOptions: Use `UrlReaderServiceReadUrlOptions` instead; + - ReadUrlResponse: Use `UrlReaderServiceReadUrlResponse` instead; + - ReadTreeOptions: Use `UrlReaderServiceReadTreeOptions` instead; + - ReadTreeResponse: Use `UrlReaderServiceReadTreeResponse` instead; + - ReadTreeResponseFile: Use `UrlReaderServiceReadTreeResponseFile` instead; + - ReadTreeResponseDirOptions: Use `UrlReaderServiceReadTreeResponseDirOptions` instead; + - SearchOptions: Use `UrlReaderServiceSearchOptions` instead; + - SearchResponse: Use `UrlReaderServiceSearchResponse` instead; + - SearchResponseFile: Use `UrlReaderServiceSearchResponseFile` instead. + +- 7c5f3b0: The `createServiceRef` function now accepts a new boolean `multiple` option. The `multiple` option defaults to `false` and when set to `true`, it enables that multiple implementation are installed for the created service ref. + + We're looking for ways to make it possible to augment services without the need to replace the entire service. + + Typical example of that being the ability to install support for additional targets for the `UrlReader` service without replacing the service itself. This achieves that by allowing us to define services that can have multiple simultaneous implementation, allowing the `UrlReader` implementation to depend on such a service to collect all possible implementation of support for external targets: + + ```diff + // @backstage/backend-defaults + + + export const urlReaderFactoriesServiceRef = createServiceRef({ + + id: 'core.urlReader.factories', + + scope: 'plugin', + + multiton: true, + + }); + + ... + + export const urlReaderServiceFactory = createServiceFactory({ + service: coreServices.urlReader, + deps: { + config: coreServices.rootConfig, + logger: coreServices.logger, + + factories: urlReaderFactoriesServiceRef, + }, + - async factory({ config, logger }) { + + async factory({ config, logger, factories }) { + return UrlReaders.default({ + config, + logger, + + factories, + }); + }, + }); + ``` + + With that, you can then add more custom `UrlReader` factories by installing more implementations of the `urlReaderFactoriesServiceRef` in your backend instance. Something like: + + ```ts + // packages/backend/index.ts + import { createServiceFactory } from '@backstage/backend-plugin-api'; + import { urlReaderFactoriesServiceRef } from '@backstage/backend-defaults'; + ... + + backend.add(createServiceFactory({ + service: urlReaderFactoriesServiceRef, + deps: {}, + async factory() { + return CustomUrlReader.factory; + }, + })); + + ... + + ``` + +- c99c620: **BREAKING** Removed the following deprecated types: + + - `ServiceRefConfig` use `ServiceRefOptions` + - `RootServiceFactoryConfig` use `RootServiceFactoryOptions` + - `PluginServiceFactoryConfig` use `PluginServiceFactoryOptions` + +### Patch Changes + +- 6061061: Added `createBackendFeatureLoader`, which can be used to create an installable backend feature that can in turn load in additional backend features in a dynamic way. +- ba9abf4: The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. +- 8b13183: Added `createBackendFeatureLoader`, which can be used to programmatically select and install backend features. + + A feature loader can return an list of features to be installed, for example in the form on an `Array` or other for of iterable, which allows for the loader to be defined as a generator function. Both synchronous and asynchronous loaders are supported. + + Additionally, a loader can depend on services in its implementation, with the restriction that it can only depend on root-scoped services, and it may not override services that have already been instantiated. + + ```ts + const searchLoader = createBackendFeatureLoader({ + deps: { + config: coreServices.rootConfig, + }, + *loader({ config }) { + // Example of a custom config flag to enable search + if (config.getOptionalString('customFeatureToggle.search')) { + yield import('@backstage/plugin-search-backend/alpha'); + yield import('@backstage/plugin-search-backend-module-catalog/alpha'); + yield import('@backstage/plugin-search-backend-module-explore/alpha'); + yield import('@backstage/plugin-search-backend-module-techdocs/alpha'); + } + }, + }); + ``` + +- ddde5fe: Fixed a type issue where plugin and modules depending on multiton services would not receive the correct type. +- f011d1b: fix typo in `getPluginRequestToken` comments +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.8.0-next.3 + +### Patch Changes + +- ddde5fe: Fixed a type issue where plugin and modules depending on multiton services would not receive the correct type. +- 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.5.0-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## 0.8.0-next.2 + +### Minor Changes + +- 7c5f3b0: The `createServiceRef` function now accepts a new boolean `multiple` option. The `multiple` option defaults to `false` and when set to `true`, it enables that multiple implementation are installed for the created service ref. + + We're looking for ways to make it possible to augment services without the need to replace the entire service. + + Typical example of that being the ability to install support for additional targets for the `UrlReader` service without replacing the service itself. This achieves that by allowing us to define services that can have multiple simultaneous implementation, allowing the `UrlReader` implementation to depend on such a service to collect all possible implementation of support for external targets: + + ```diff + // @backstage/backend-defaults + + + export const urlReaderFactoriesServiceRef = createServiceRef({ + + id: 'core.urlReader.factories', + + scope: 'plugin', + + multiton: true, + + }); + + ... + + export const urlReaderServiceFactory = createServiceFactory({ + service: coreServices.urlReader, + deps: { + config: coreServices.rootConfig, + logger: coreServices.logger, + + factories: urlReaderFactoriesServiceRef, + }, + - async factory({ config, logger }) { + + async factory({ config, logger, factories }) { + return UrlReaders.default({ + config, + logger, + + factories, + }); + }, + }); + ``` + + With that, you can then add more custom `UrlReader` factories by installing more implementations of the `urlReaderFactoriesServiceRef` in your backend instance. Something like: + + ```ts + // packages/backend/index.ts + import { createServiceFactory } from '@backstage/backend-plugin-api'; + import { urlReaderFactoriesServiceRef } from '@backstage/backend-defaults'; + ... + + backend.add(createServiceFactory({ + service: urlReaderFactoriesServiceRef, + deps: {}, + async factory() { + return CustomUrlReader.factory; + }, + })); + + ... + + ``` + +### Patch Changes + +- 6061061: Added `createBackendFeatureLoader`, which can be used to create an installable backend feature that can in turn load in additional backend features in a dynamic way. +- ba9abf4: The `SchedulerService` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `SchedulerService.triggerTask`. +- 8b13183: Added `createBackendFeatureLoader`, which can be used to programmatically select and install backend features. + + A feature loader can return an list of features to be installed, for example in the form on an `Array` or other for of iterable, which allows for the loader to be defined as a generator function. Both synchronous and asynchronous loaders are supported. + + Additionally, a loader can depend on services in its implementation, with the restriction that it can only depend on root-scoped services, and it may not override services that have already been instantiated. + + ```ts + const searchLoader = createBackendFeatureLoader({ + deps: { + config: coreServices.rootConfig, + }, + *loader({ config }) { + // Example of a custom config flag to enable search + if (config.getOptionalString('customFeatureToggle.search')) { + yield import('@backstage/plugin-search-backend/alpha'); + yield import('@backstage/plugin-search-backend-module-catalog/alpha'); + yield import('@backstage/plugin-search-backend-module-explore/alpha'); + yield import('@backstage/plugin-search-backend-module-techdocs/alpha'); + } + }, + }); + ``` + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.7.1-next.1 + +### Patch Changes + +- f011d1b: fix typo in `getPluginRequestToken` comments +- Updated dependencies + - @backstage/plugin-permission-common@0.8.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-auth-node@0.4.18-next.1 + +## 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-alpha.md b/packages/backend-plugin-api/api-report-alpha.md index 81b378a671..0f4348336a 100644 --- a/packages/backend-plugin-api/api-report-alpha.md +++ b/packages/backend-plugin-api/api-report-alpha.md @@ -17,7 +17,8 @@ export interface FeatureDiscoveryService { // @alpha export const featureDiscoveryServiceRef: ServiceRef< FeatureDiscoveryService, - 'root' + 'root', + 'singleton' >; // (No @packageDocumentation comment for this package) diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index e9762f652e..1732998e44 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -9,9 +9,9 @@ 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'; import { isChildPath } from '@backstage/cli-common'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; @@ -63,15 +63,6 @@ export interface BackendFeature { $$type: '@backstage/BackendFeature'; } -// @public @deprecated (undocumented) -export interface BackendFeatureCompat extends BackendFeature { - // @deprecated (undocumented) - (): this; -} - -// @public @deprecated (undocumented) -export type BackendModuleConfig = CreateBackendModuleOptions; - // @public export interface BackendModuleRegistrationPoints { // (undocumented) @@ -81,20 +72,15 @@ export interface BackendModuleRegistrationPoints { ): void; // (undocumented) registerInit< - Deps extends { - [name in string]: unknown; + TDeps extends { + [name in string]: ServiceRef | ExtensionPoint; }, >(options: { - deps: { - [name in keyof Deps]: ServiceRef | ExtensionPoint; - }; - init(deps: Deps): Promise; + deps: TDeps; + init(deps: DepsToInstances): Promise; }): void; } -// @public @deprecated (undocumented) -export type BackendPluginConfig = CreateBackendPluginOptions; - // @public export interface BackendPluginRegistrationPoints { // (undocumented) @@ -104,14 +90,12 @@ export interface BackendPluginRegistrationPoints { ): void; // (undocumented) registerInit< - Deps extends { - [name in string]: unknown; + TDeps extends { + [name in string]: ServiceRef; }, >(options: { - deps: { - [name in keyof Deps]: ServiceRef; - }; - init(deps: Deps): Promise; + deps: TDeps; + init(deps: DepsToInstances): Promise; }): void; } @@ -188,34 +172,75 @@ export type CacheServiceSetOptions = { // @public export namespace coreServices { - const auth: ServiceRef; - const userInfo: ServiceRef; - const cache: ServiceRef; - const rootConfig: ServiceRef; - const database: ServiceRef; - const discovery: ServiceRef; - const rootHealth: ServiceRef; - const httpAuth: ServiceRef; - const httpRouter: ServiceRef; - const lifecycle: ServiceRef; - const logger: ServiceRef; - const permissions: ServiceRef; - const pluginMetadata: ServiceRef; - const rootHttpRouter: ServiceRef; - const rootLifecycle: ServiceRef; - const rootLogger: ServiceRef; - const scheduler: ServiceRef; - const // @deprecated - tokenManager: ServiceRef; - const urlReader: ServiceRef; - const // @deprecated - identity: ServiceRef; + const auth: ServiceRef; + const userInfo: ServiceRef; + const cache: ServiceRef; + const rootConfig: ServiceRef; + const database: ServiceRef; + const discovery: ServiceRef; + const rootHealth: ServiceRef; + const httpAuth: ServiceRef; + const httpRouter: ServiceRef; + const lifecycle: ServiceRef; + const logger: ServiceRef; + const permissions: ServiceRef; + const pluginMetadata: ServiceRef< + PluginMetadataService, + 'plugin', + 'singleton' + >; + const rootHttpRouter: ServiceRef; + const rootLifecycle: ServiceRef; + const rootLogger: ServiceRef; + const scheduler: ServiceRef; + const urlReader: ServiceRef; +} + +// @public +export function createBackendFeatureLoader< + TDeps extends { + [name in string]: unknown; + }, +>(options: CreateBackendFeatureLoaderOptions): BackendFeature; + +// @public +export interface CreateBackendFeatureLoaderOptions< + TDeps extends { + [name in string]: unknown; + }, +> { + // (undocumented) + deps?: { + [name in keyof TDeps]: ServiceRef; + }; + // (undocumented) + loader(deps: TDeps): + | Iterable< + | BackendFeature + | Promise<{ + default: BackendFeature; + }> + > + | Promise< + Iterable< + | BackendFeature + | Promise<{ + default: BackendFeature; + }> + > + > + | AsyncIterable< + | BackendFeature + | { + default: BackendFeature; + } + >; } // @public export function createBackendModule( options: CreateBackendModuleOptions, -): BackendFeatureCompat; +): BackendFeature; // @public export interface CreateBackendModuleOptions { @@ -228,7 +253,7 @@ export interface CreateBackendModuleOptions { // @public export function createBackendPlugin( options: CreateBackendPluginOptions, -): BackendFeatureCompat; +): BackendFeature; // @public export interface CreateBackendPluginOptions { @@ -250,64 +275,53 @@ export interface CreateExtensionPointOptions { // @public export function createServiceFactory< TService, + TInstances extends 'singleton' | 'multiton', TImpl extends TService, TDeps extends { [name in string]: ServiceRef; }, - TOpts extends object | undefined = undefined, >( - config: RootServiceFactoryConfig, -): () => ServiceFactory; - -// @public -export function createServiceFactory< - TService, - TImpl extends TService, - TDeps extends { - [name in string]: ServiceRef; - }, - TOpts extends object | undefined = undefined, ->( - config: (options?: TOpts) => RootServiceFactoryConfig, -): (options?: TOpts) => ServiceFactory; + options: RootServiceFactoryOptions, +): ServiceFactory; // @public export function createServiceFactory< TService, + TInstances extends 'singleton' | 'multiton', 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?: TOpts, - ) => PluginServiceFactoryConfig, -): (options?: TOpts) => ServiceFactory; + options: PluginServiceFactoryOptions< + TService, + TInstances, + TContext, + TImpl, + TDeps + >, +): ServiceFactory; // @public export function createServiceRef( - config: ServiceRefConfig, -): ServiceRef; + options: ServiceRefOptions, +): ServiceRef; // @public export function createServiceRef( - config: ServiceRefConfig, -): ServiceRef; + options: ServiceRefOptions, +): ServiceRef; + +// @public +export function createServiceRef( + options: ServiceRefOptions, +): ServiceRef; + +// @public +export function createServiceRef( + options: ServiceRefOptions, +): ServiceRef; // @public export interface DatabaseService { @@ -331,9 +345,6 @@ export type ExtensionPoint = { $$type: '@backstage/ExtensionPoint'; }; -// @public @deprecated (undocumented) -export type ExtensionPointConfig = CreateExtensionPointOptions; - // @public export interface HttpAuthService { credentials( @@ -367,9 +378,6 @@ export interface HttpRouterServiceAuthPolicy { path: string; } -// @public @deprecated -export interface IdentityService extends IdentityApi {} - export { isChildPath }; // @public @@ -421,22 +429,20 @@ 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 { @@ -444,8 +450,9 @@ export interface PluginMetadataService { } // @public (undocumented) -export interface PluginServiceFactoryConfig< +export interface PluginServiceFactoryOptions< TService, + TInstances extends 'singleton' | 'multiton', TContext, TImpl extends TService, TDeps extends { @@ -465,7 +472,7 @@ export interface PluginServiceFactoryConfig< ): TImpl | Promise; initialization?: 'always' | 'lazy'; // (undocumented) - service: ServiceRef; + service: ServiceRef; } // @public @@ -473,25 +480,6 @@ export function readSchedulerServiceTaskScheduleDefinitionFromConfig( config: Config, ): SchedulerServiceTaskScheduleDefinition; -// @public @deprecated (undocumented) -export type ReadTreeOptions = UrlReaderServiceReadTreeOptions; - -// @public @deprecated (undocumented) -export type ReadTreeResponse = UrlReaderServiceReadTreeResponse; - -// @public @deprecated (undocumented) -export type ReadTreeResponseDirOptions = - UrlReaderServiceReadTreeResponseDirOptions; - -// @public @deprecated (undocumented) -export type ReadTreeResponseFile = UrlReaderServiceReadTreeResponseFile; - -// @public @deprecated (undocumented) -export type ReadUrlOptions = UrlReaderServiceReadUrlOptions; - -// @public @deprecated (undocumented) -export type ReadUrlResponse = UrlReaderServiceReadUrlResponse; - // @public export function resolvePackagePath(name: string, ...paths: string[]): string; @@ -525,8 +513,9 @@ export interface RootLifecycleService extends LifecycleService {} export interface RootLoggerService extends LoggerService {} // @public (undocumented) -export interface RootServiceFactoryConfig< - TService, +export interface RootServiceFactoryOptions< + TService, // TODO(Rugvip): Can we forward the entire service ref type here instead of forwarding each type arg once the callback form is gone? + TInstances extends 'singleton' | 'multiton', TImpl extends TService, TDeps extends { [name in string]: ServiceRef; @@ -538,7 +527,7 @@ export interface RootServiceFactoryConfig< factory(deps: ServiceRefsToInstances): TImpl | Promise; initialization?: 'always' | 'lazy'; // (undocumented) - service: ServiceRef; + service: ServiceRef; } // @public @@ -587,7 +576,10 @@ export interface SchedulerServiceTaskScheduleDefinition { cron: string; } | Duration - | HumanDuration; + | HumanDuration + | { + trigger: 'manual'; + }; initialDelay?: Duration | HumanDuration; scope?: 'global' | 'local'; timeout: Duration | HumanDuration; @@ -600,64 +592,60 @@ export interface SchedulerServiceTaskScheduleDefinitionConfig { cron: string; } | string - | HumanDuration; + | HumanDuration + /** + * This task will only run when manually triggered with the `triggerTask` method; no automatic + * scheduling. This is useful for locking of global tasks that should not be run concurrently. + */ + | { + trigger: 'manual'; + }; initialDelay?: string | HumanDuration; scope?: 'global' | 'local'; timeout: string | HumanDuration; } -// @public @deprecated (undocumented) -export type SearchOptions = UrlReaderServiceSearchOptions; - -// @public @deprecated (undocumented) -export type SearchResponse = UrlReaderServiceSearchResponse; - -// @public @deprecated (undocumented) -export type SearchResponseFile = UrlReaderServiceSearchResponseFile; - // @public (undocumented) export interface ServiceFactory< TService = unknown, TScope extends 'plugin' | 'root' = 'plugin' | 'root', + TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton', > extends BackendFeature { // (undocumented) - service: ServiceRef; + service: ServiceRef; } -// @public -export type ServiceFactoryOrFunction = ServiceFactory | (() => ServiceFactory); - // @public export type ServiceRef< TService, TScope extends 'root' | 'plugin' = 'root' | 'plugin', + TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton', > = { id: string; scope: TScope; + multiton?: TInstances extends 'multiton' ? true : false; T: TService; $$type: '@backstage/ServiceRef'; }; // @public (undocumented) -export interface ServiceRefConfig { +export interface ServiceRefOptions< + TService, + TScope extends 'root' | 'plugin', + TInstances extends 'singleton' | 'multiton', +> { // (undocumented) - defaultFactory?: ( + defaultFactory?( service: ServiceRef, - ) => Promise; + ): Promise; // (undocumented) id: string; // (undocumented) + multiton?: TInstances extends 'multiton' ? true : false; + // (undocumented) scope?: TScope; } -// @public @deprecated -export interface TokenManagerService { - authenticate(token: string): Promise; - getToken(): Promise<{ - token: string; - }>; -} - // @public export interface UrlReaderService { readTree( diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index dbb59f7991..94793e1e49 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.8.0", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" diff --git a/packages/backend-plugin-api/src/index.ts b/packages/backend-plugin-api/src/index.ts index e6e57db1dc..c2e2a13a89 100644 --- a/packages/backend-plugin-api/src/index.ts +++ b/packages/backend-plugin-api/src/index.ts @@ -21,6 +21,6 @@ */ export * from './services'; -export type { BackendFeature, BackendFeatureCompat } from './types'; +export type { BackendFeature } from './types'; export * from './paths'; export * from './wiring'; diff --git a/packages/backend-plugin-api/src/services/definitions/AuthService.ts b/packages/backend-plugin-api/src/services/definitions/AuthService.ts index dce5e9be3b..1e0cf6e56b 100644 --- a/packages/backend-plugin-api/src/services/definitions/AuthService.ts +++ b/packages/backend-plugin-api/src/services/definitions/AuthService.ts @@ -208,7 +208,7 @@ export interface AuthService { * * @remarks * - * This method should be called before each request. Do not cold on to the + * This method should be called before each request. Do not hold on to the * issued token and reuse it for future calls. */ getPluginRequestToken(options: { diff --git a/packages/backend-plugin-api/src/services/definitions/IdentityService.ts b/packages/backend-plugin-api/src/services/definitions/IdentityService.ts deleted file mode 100644 index daa45b8200..0000000000 --- a/packages/backend-plugin-api/src/services/definitions/IdentityService.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { IdentityApi } from '@backstage/plugin-auth-node'; - -/** - * This is the legacy service for identity handling in Backstage. Please migrate to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead. - * - * See the {@link https://backstage.io/docs/backend-system/core-services/identity | service documentation} for more details. - * - * @public - * @deprecated Please {@link https://backstage.io/docs/tutorials/auth-service-migration | migrate} to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead. - */ -export interface IdentityService extends IdentityApi {} 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/definitions/SchedulerService.ts b/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts index a7ec79a336..a8b84b9084 100644 --- a/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts +++ b/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts @@ -96,7 +96,8 @@ export interface SchedulerServiceTaskScheduleDefinition { cron: string; } | Duration - | HumanDuration; + | HumanDuration + | { trigger: 'manual' }; /** * The maximum amount of time that a single task invocation can take, before @@ -193,7 +194,12 @@ export interface SchedulerServiceTaskScheduleDefinitionConfig { cron: string; } | string - | HumanDuration; + | HumanDuration + /** + * This task will only run when manually triggered with the `triggerTask` method; no automatic + * scheduling. This is useful for locking of global tasks that should not be run concurrently. + */ + | { trigger: 'manual' }; /** * The maximum amount of time that a single task invocation can take, before @@ -361,14 +367,20 @@ function readDuration(config: Config, key: string): HumanDuration { return readDurationFromConfig(config, { key }); } -function readCronOrDuration( +function readFrequency( config: Config, key: string, -): { cron: string } | HumanDuration { +): { cron: string } | HumanDuration | { trigger: 'manual' } { const value = config.get(key); if (typeof value === 'object' && (value as { cron?: string }).cron) { return value as { cron: string }; } + if ( + typeof value === 'object' && + (value as { trigger?: string }).trigger === 'manual' + ) { + return { trigger: 'manual' }; + } return readDuration(config, key); } @@ -383,7 +395,7 @@ function readCronOrDuration( export function readSchedulerServiceTaskScheduleDefinitionFromConfig( config: Config, ): SchedulerServiceTaskScheduleDefinition { - const frequency = readCronOrDuration(config, 'frequency'); + const frequency = readFrequency(config, 'frequency'); const timeout = readDuration(config, 'timeout'); const initialDelay = config.has('initialDelay') diff --git a/packages/backend-plugin-api/src/services/definitions/TokenManagerService.ts b/packages/backend-plugin-api/src/services/definitions/TokenManagerService.ts deleted file mode 100644 index 52d5d1ff9f..0000000000 --- a/packages/backend-plugin-api/src/services/definitions/TokenManagerService.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This is the legacy service for creating and validating tokens. Please migrate to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead. - * - * See the {@link https://backstage.io/docs/backend-system/core-services/token-manager | service documentation} for more details. - * - * @public - * @deprecated Please {@link https://backstage.io/docs/tutorials/auth-service-migration | migrate} to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead. - */ -export interface TokenManagerService { - /** - * Fetches a valid token. - * - * @remarks - * - * Tokens are valid for roughly one hour; the actual deadline is set in the - * payload `exp` claim. Never hold on to tokens for reuse; always ask for a - * new one for each outgoing request. This ensures that you always get a - * valid, fresh one. - */ - getToken(): Promise<{ token: string }>; - - /** - * Validates a given token. - */ - authenticate(token: string): Promise; -} diff --git a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts index 25b787107e..9b327cfbf0 100644 --- a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts +++ b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts @@ -368,50 +368,3 @@ export type UrlReaderServiceSearchResponseFile = { */ lastModifiedAt?: Date; }; - -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeOptions` instead - */ -export type ReadTreeOptions = UrlReaderServiceReadTreeOptions; -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponse` instead - */ -export type ReadTreeResponse = UrlReaderServiceReadTreeResponse; -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponseDirOptions` instead - */ -export type ReadTreeResponseDirOptions = - UrlReaderServiceReadTreeResponseDirOptions; -/** - * @public - * @deprecated Use `UrlReaderServiceReadTreeResponseFile` instead - */ -export type ReadTreeResponseFile = UrlReaderServiceReadTreeResponseFile; -/** - * @public - * @deprecated Use `UrlReaderServiceReadUrlResponse` instead - */ -export type ReadUrlResponse = UrlReaderServiceReadUrlResponse; -/** - * @public - * @deprecated Use `UrlReaderServiceReadUrlOptions` instead - */ -export type ReadUrlOptions = UrlReaderServiceReadUrlOptions; -/** - * @public - * @deprecated Use `UrlReaderServiceSearchOptions` instead - */ -export type SearchOptions = UrlReaderServiceSearchOptions; -/** - * @public - * @deprecated Use `UrlReaderServiceSearchResponse` instead - */ -export type SearchResponse = UrlReaderServiceSearchResponse; -/** - * @public - * @deprecated Use `UrlReaderServiceSearchResponseFile` instead - */ -export type SearchResponseFile = UrlReaderServiceSearchResponseFile; diff --git a/packages/backend-plugin-api/src/services/definitions/coreServices.ts b/packages/backend-plugin-api/src/services/definitions/coreServices.ts index 33d1da2903..f87bff023c 100644 --- a/packages/backend-plugin-api/src/services/definitions/coreServices.ts +++ b/packages/backend-plugin-api/src/services/definitions/coreServices.ts @@ -239,20 +239,6 @@ export namespace coreServices { import('./SchedulerService').SchedulerService >({ id: 'core.scheduler' }); - /** - * Deprecated service authentication service, use the `auth` service instead. - * - * See {@link TokenManagerService} - * and {@link https://backstage.io/docs/backend-system/core-services/token-manager | the service docs} - * for more information. - * - * @public - * @deprecated Please migrate to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead - */ - export const tokenManager = createServiceRef< - import('./TokenManagerService').TokenManagerService - >({ id: 'core.tokenManager' }); - /** * Reading content from external systems. * @@ -265,18 +251,4 @@ export namespace coreServices { export const urlReader = createServiceRef< import('./UrlReaderService').UrlReaderService >({ id: 'core.urlReader' }); - - /** - * Deprecated user authentication service, use the `auth` service instead. - * - * See {@link IdentityService} - * and {@link https://backstage.io/docs/backend-system/core-services/identity | the service docs} - * for more information. - * - * @public - * @deprecated Please migrate to the new `coreServices.auth`, `coreServices.httpAuth`, and `coreServices.userInfo` services as needed instead - */ - export const identity = createServiceRef< - import('./IdentityService').IdentityService - >({ id: 'core.identity' }); } diff --git a/packages/backend-plugin-api/src/services/definitions/index.ts b/packages/backend-plugin-api/src/services/definitions/index.ts index 5e30d444fd..1df00bdead 100644 --- a/packages/backend-plugin-api/src/services/definitions/index.ts +++ b/packages/backend-plugin-api/src/services/definitions/index.ts @@ -64,17 +64,7 @@ export type { SchedulerServiceTaskScheduleDefinition, SchedulerServiceTaskScheduleDefinitionConfig, } from './SchedulerService'; -export type { TokenManagerService } from './TokenManagerService'; export type { - ReadTreeOptions, - ReadTreeResponse, - ReadTreeResponseDirOptions, - ReadTreeResponseFile, - ReadUrlResponse, - ReadUrlOptions, - SearchOptions, - SearchResponse, - SearchResponseFile, UrlReaderServiceReadTreeOptions, UrlReaderServiceReadTreeResponse, UrlReaderServiceReadTreeResponseDirOptions, @@ -87,4 +77,3 @@ export type { UrlReaderService, } from './UrlReaderService'; export type { BackstageUserInfo, UserInfoService } from './UserInfoService'; -export type { IdentityService } from './IdentityService'; diff --git a/packages/backend-plugin-api/src/services/system/index.ts b/packages/backend-plugin-api/src/services/system/index.ts index 2b91758cbc..82afa904fe 100644 --- a/packages/backend-plugin-api/src/services/system/index.ts +++ b/packages/backend-plugin-api/src/services/system/index.ts @@ -16,10 +16,9 @@ export type { ServiceRef, - ServiceRefConfig, + ServiceRefOptions, ServiceFactory, - PluginServiceFactoryConfig, - RootServiceFactoryConfig, - ServiceFactoryOrFunction, + PluginServiceFactoryOptions, + RootServiceFactoryOptions, } from './types'; export { createServiceRef, createServiceFactory } from './types'; diff --git a/packages/backend-plugin-api/src/services/system/types.test.ts b/packages/backend-plugin-api/src/services/system/types.test.ts index adf097761b..a98b8c44ee 100644 --- a/packages/backend-plugin-api/src/services/system/types.test.ts +++ b/packages/backend-plugin-api/src/services/system/types.test.ts @@ -14,20 +14,20 @@ * limitations under the License. */ -import { createServiceFactory, createServiceRef } from './types'; +import { + InternalServiceFactory, + createServiceFactory, + createServiceRef, +} from './types'; const ref = createServiceRef({ id: 'x' }); const rootDep = createServiceRef({ id: 'y', scope: 'root' }); const pluginDep = createServiceRef({ id: 'z' }); - -interface TestOptions { - x: number; -} function unused(..._any: any[]) {} describe('createServiceFactory', () => { - it('should create a sync factory', () => { - const metaFactory = createServiceFactory({ + it('should create a plugin scoped factory', () => { + const factory = createServiceFactory({ service: ref, deps: {}, createRootContext() {}, @@ -35,48 +35,26 @@ describe('createServiceFactory', () => { return 'x'; }, }); - expect(metaFactory).toEqual(expect.any(Function)); - expect(metaFactory().service).toBe(ref); - - // @ts-expect-error - metaFactory('string'); - // @ts-expect-error - metaFactory({}); - // @ts-expect-error - metaFactory({ x: 1 }); - // @ts-expect-error - metaFactory(null); - // @ts-expect-error - metaFactory(undefined); - metaFactory(); + expect(factory.$$type).toBe('@backstage/BackendFeature'); + expect((factory as InternalServiceFactory).featureType).toBe('service'); + expect(factory.service).toBe(ref); }); - it('should create a sync root factory', () => { - const metaFactory = createServiceFactory({ + it('should create a root scoped factory', () => { + const factory = createServiceFactory({ service: rootDep, deps: {}, factory(_deps) { return 0; }, }); - expect(metaFactory).toEqual(expect.any(Function)); - expect(metaFactory().service).toBe(rootDep); - - // @ts-expect-error - metaFactory('string'); - // @ts-expect-error - metaFactory({}); - // @ts-expect-error - metaFactory({ x: 1 }); - // @ts-expect-error - metaFactory(null); - // @ts-expect-error - metaFactory(undefined); - metaFactory(); + expect(factory.$$type).toBe('@backstage/BackendFeature'); + expect((factory as InternalServiceFactory).featureType).toBe('service'); + expect(factory.service).toBe(rootDep); }); - it('should create a factory', () => { - const metaFactory = createServiceFactory({ + it('should create a plugin scoped factory with a root context', () => { + const factory = createServiceFactory({ service: ref, deps: {}, async createRootContext() {}, @@ -84,85 +62,13 @@ describe('createServiceFactory', () => { return 'x'; }, }); - expect(metaFactory).toEqual(expect.any(Function)); - expect(metaFactory().service).toBe(ref); - - // @ts-expect-error - metaFactory('string'); - // @ts-expect-error - metaFactory({}); - // @ts-expect-error - metaFactory({ x: 1 }); - // @ts-expect-error - metaFactory(null); - // @ts-expect-error - metaFactory(undefined); - metaFactory(); - }); - - it('should create a factory with options', () => { - const metaFactory = createServiceFactory((_opts?: { x: number }) => ({ - service: ref, - deps: {}, - async createRootContext() {}, - async factory() { - return 'x'; - }, - })); - expect(metaFactory).toEqual(expect.any(Function)); - - // @ts-expect-error - metaFactory('string'); - // @ts-expect-error - metaFactory({}); - metaFactory({ x: 1 }); - // @ts-expect-error - metaFactory({ x: 1, y: 2 }); - // @ts-expect-error - metaFactory(null); - metaFactory(undefined); - metaFactory(); - }); - - it('should not be allowed to require options', () => { - // @ts-expect-error - const metaFactory = createServiceFactory((_opts: { x: number }) => ({ - service: ref, - deps: {}, - async createRootContext() {}, - async factory() { - return 'x'; - }, - })); - expect(metaFactory).toEqual(expect.any(Function)); - }); - - it('should create a factory with options as interface', () => { - const metaFactory = createServiceFactory((_opts?: TestOptions) => ({ - service: ref, - deps: {}, - async createRootContext() {}, - async factory() { - return 'x'; - }, - })); - expect(metaFactory).toEqual(expect.any(Function)); - - // @ts-expect-error - metaFactory('string'); - // @ts-expect-error - metaFactory({}); - metaFactory({ x: 1 }); - // @ts-expect-error - metaFactory({ x: 1, y: 2 }); - // @ts-expect-error - metaFactory(null); - metaFactory(undefined); - metaFactory(); + expect(factory.$$type).toBe('@backstage/BackendFeature'); + expect((factory as InternalServiceFactory).featureType).toBe('service'); + expect(factory.service).toBe(ref); }); it('should create root scoped factory with dependencies', () => { - const metaFactory = createServiceFactory({ + const factory = createServiceFactory({ service: createServiceRef({ id: 'foo', scope: 'root' }), deps: { root: rootDep, @@ -175,48 +81,11 @@ describe('createServiceFactory', () => { return 0; }, }); - expect(metaFactory).toEqual(expect.any(Function)); - - // @ts-expect-error - metaFactory({}); - // @ts-expect-error - metaFactory(null); - // @ts-expect-error - metaFactory(undefined); - metaFactory(); + expect(factory.$$type).toBe('@backstage/BackendFeature'); }); - it('should create root scoped factory with dependencies and options', () => { - const metaFactory = createServiceFactory((_options?: TestOptions) => ({ - service: createServiceRef({ id: 'foo', scope: 'root' }), - deps: { - root: rootDep, - }, - async factory({ root }) { - const root1: number = root; - // @ts-expect-error - const root2: string = root; - unused(root1, root2); - return 0; - }, - })); - expect(metaFactory).toEqual(expect.any(Function)); - - // @ts-expect-error - metaFactory('string'); - // @ts-expect-error - metaFactory({}); - metaFactory({ x: 1 }); - // @ts-expect-error - metaFactory({ x: 1, y: 2 }); - // @ts-expect-error - metaFactory(null); - metaFactory(undefined); - metaFactory(); - }); - - it('should create factory with dependencies', () => { - const metaFactory = createServiceFactory({ + it('should create a plugin scoped factory with dependencies', () => { + const factory = createServiceFactory({ service: createServiceRef({ id: 'derp' }), deps: { root: rootDep, @@ -243,19 +112,11 @@ describe('createServiceFactory', () => { return 'x'; }, }); - expect(metaFactory).toEqual(expect.any(Function)); - - // @ts-expect-error - metaFactory({}); - // @ts-expect-error - metaFactory(null); - // @ts-expect-error - metaFactory(undefined); - metaFactory(); + expect(factory.$$type).toBe('@backstage/BackendFeature'); }); it('should create factory with dependencies with optional derpFactory', () => { - const metaFactory = createServiceFactory({ + const factory = createServiceFactory({ service: createServiceRef({ id: 'derp' }), deps: { root: rootDep, @@ -272,131 +133,24 @@ describe('createServiceFactory', () => { return 'x'; }, }); - - expect(metaFactory).toEqual(expect.any(Function)); - - // @ts-expect-error - metaFactory({}); - // @ts-expect-error - metaFactory(null); - // @ts-expect-error - metaFactory(undefined); - metaFactory(); + expect(factory.$$type).toBe('@backstage/BackendFeature'); }); - it('should create factory with options and dependencies', () => { - const metaFactory = createServiceFactory((_opts?: TestOptions) => ({ + it('should support old service refs without a multiton field', () => { + const oldPluginDep = pluginDep as Omit; // Old refs don't have a multiton field + const factory = createServiceFactory({ service: ref, deps: { - root: rootDep, - plugin: pluginDep, + plugin: oldPluginDep, }, - async createRootContext({ root }) { - const root1: number = root; + async factory({ plugin }) { + const plugin1: boolean = plugin; // @ts-expect-error - const root2: string = root; - unused(root1, root2); - return { root }; - }, - async factory({ plugin }, { root }) { - const root1: number = root; - // @ts-expect-error - const root2: string = root; - const plugin3: boolean = plugin; - // @ts-expect-error - const plugin4: number = plugin; - unused(root1, root2, plugin3, plugin4); + const plugin2: number = plugin; + unused(plugin1, plugin2); return 'x'; }, - })); - expect(metaFactory).toEqual(expect.any(Function)); - - // @ts-expect-error - metaFactory('string'); - // @ts-expect-error - metaFactory({}); - metaFactory({ x: 1 }); - // @ts-expect-error - metaFactory({ x: 1, y: 2 }); - // @ts-expect-error - metaFactory(null); - metaFactory(undefined); - metaFactory(); - }); - - it('should only allow objects as options', () => { - // @ts-expect-error - const metaFactory = createServiceFactory((_opts: string) => ({ - service: ref, - deps: {}, - async factory() { - return async () => 'x'; - }, - })); - expect(metaFactory).toEqual(expect.any(Function)); - // @ts-expect-error - createServiceFactory((_opts: number) => ({ - service: ref, - deps: {}, - async factory() { - return async () => 'x'; - }, - })); - // @ts-expect-error - createServiceFactory((_opts: symbol) => ({ - service: ref, - deps: {}, - async factory() { - return async () => 'x'; - }, - })); - // @ts-expect-error - createServiceFactory((_opts: bigint) => ({ - service: ref, - deps: {}, - async factory() { - return async () => 'x'; - }, - })); - // @ts-expect-error - createServiceFactory((_opts: 'string') => ({ - service: ref, - deps: {}, - async factory() { - return async () => 'x'; - }, - })); - // @ts-expect-error - createServiceFactory((_opts: Array) => ({ - service: ref, - deps: {}, - async factory() { - return async () => 'x'; - }, - })); - // @ts-expect-error - createServiceFactory((_opts: Map) => ({ - service: ref, - deps: {}, - async factory() { - return async () => 'x'; - }, - })); - // @ts-expect-error - createServiceFactory((_opts: Set) => ({ - service: ref, - deps: {}, - async factory() { - return async () => 'x'; - }, - })); - // @ts-expect-error - createServiceFactory((_opts: null) => ({ - service: ref, - deps: {}, - async factory() { - return async () => 'x'; - }, - })); + }); + expect(factory.$$type).toBe('@backstage/BackendFeature'); }); }); diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts index 13252ba6e4..9e054ed4a4 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -24,6 +24,7 @@ import { BackendFeature } from '../../types'; export type ServiceRef< TService, TScope extends 'root' | 'plugin' = 'root' | 'plugin', + TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton', > = { id: string; @@ -38,6 +39,8 @@ export type ServiceRef< */ scope: TScope; + multiton?: TInstances extends 'multiton' ? true : false; + /** * Utility for getting the type of the service, using `typeof serviceRef.T`. * Attempting to actually read this value will result in an exception. @@ -51,16 +54,19 @@ export type ServiceRef< export interface ServiceFactory< TService = unknown, TScope extends 'plugin' | 'root' = 'plugin' | 'root', + TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton', > extends BackendFeature { - service: ServiceRef; + service: ServiceRef; } /** @internal */ export interface InternalServiceFactory< TService = unknown, TScope extends 'plugin' | 'root' = 'plugin' | 'root', -> extends ServiceFactory { + TInstances extends 'singleton' | 'multiton' = 'singleton' | 'multiton', +> extends ServiceFactory { version: 'v1'; + featureType: 'service'; initialization?: 'always' | 'lazy'; deps: { [key in string]: ServiceRef }; createRootContext?(deps: { [key in string]: unknown }): Promise; @@ -70,20 +76,18 @@ export interface InternalServiceFactory< ): Promise; } -/** - * Represents either a {@link ServiceFactory} or a function that returns one. - * - * @public - */ -export type ServiceFactoryOrFunction = ServiceFactory | (() => ServiceFactory); - /** @public */ -export interface ServiceRefConfig { +export interface ServiceRefOptions< + TService, + TScope extends 'root' | 'plugin', + TInstances extends 'singleton' | 'multiton', +> { id: string; scope?: TScope; - defaultFactory?: ( + multiton?: TInstances extends 'multiton' ? true : false; + defaultFactory?( service: ServiceRef, - ) => Promise; + ): Promise; } /** @@ -92,8 +96,8 @@ export interface ServiceRefConfig { * @public */ export function createServiceRef( - config: ServiceRefConfig, -): ServiceRef; + options: ServiceRefOptions, +): ServiceRef; /** * Creates a new service definition. This overload is used to create root scoped services. @@ -101,27 +105,49 @@ export function createServiceRef( * @public */ export function createServiceRef( - config: ServiceRefConfig, -): ServiceRef; + options: ServiceRefOptions, +): ServiceRef; + +/** + * Creates a new service definition. This overload is used to create plugin scoped services. + * + * @public + */ export function createServiceRef( - config: ServiceRefConfig, -): ServiceRef { - const { id, scope = 'plugin', defaultFactory } = config; + options: ServiceRefOptions, +): ServiceRef; + +/** + * Creates a new service definition. This overload is used to create root scoped services. + * + * @public + */ +export function createServiceRef( + options: ServiceRefOptions, +): ServiceRef; +export function createServiceRef< + TService, + TInstances extends 'singleton' | 'multiton', +>( + options: ServiceRefOptions, +): ServiceRef { + const { id, scope = 'plugin', multiton = false, defaultFactory } = options; return { id, scope, + multiton, get T(): TService { throw new Error(`tried to read ServiceRef.T of ${this}`); }, toString() { - return `serviceRef{${config.id}}`; + return `serviceRef{${options.id}}`; }, $$type: '@backstage/ServiceRef', __defaultFactory: defaultFactory, - } as ServiceRef & { + } as ServiceRef & { __defaultFactory?: ( service: ServiceRef, - ) => Promise | (() => ServiceFactory)>; + ) => Promise>; }; } @@ -130,12 +156,17 @@ type ServiceRefsToInstances< T extends { [key in string]: ServiceRef }, TScope extends 'root' | 'plugin' = 'root' | 'plugin', > = { - [key in keyof T as T[key]['scope'] extends TScope ? key : never]: T[key]['T']; + [key in keyof T as T[key]['scope'] extends TScope + ? key + : never]: T[key]['multiton'] extends true | undefined + ? Array + : T[key]['T']; }; /** @public */ -export interface RootServiceFactoryConfig< - TService, +export interface RootServiceFactoryOptions< + TService, // TODO(Rugvip): Can we forward the entire service ref type here instead of forwarding each type arg once the callback form is gone? + TInstances extends 'singleton' | 'multiton', TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, > { @@ -150,14 +181,15 @@ export interface RootServiceFactoryConfig< * Service factories for root scoped services use `always` as the default, while plugin scoped services use `lazy`. */ initialization?: 'always' | 'lazy'; - service: ServiceRef; + service: ServiceRef; deps: TDeps; factory(deps: ServiceRefsToInstances): TImpl | Promise; } /** @public */ -export interface PluginServiceFactoryConfig< +export interface PluginServiceFactoryOptions< TService, + TInstances extends 'singleton' | 'multiton', TContext, TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, @@ -173,7 +205,7 @@ export interface PluginServiceFactoryConfig< * Service factories for root scoped services use `always` as the default, while plugin scoped services use `lazy`. */ initialization?: 'always' | 'lazy'; - service: ServiceRef; + service: ServiceRef; deps: TDeps; createRootContext?( deps: ServiceRefsToInstances, @@ -188,118 +220,88 @@ 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, + TInstances extends 'singleton' | 'multiton', TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, - TOpts extends object | undefined = undefined, >( - config: RootServiceFactoryConfig, -): () => ServiceFactory; -/** - * Creates a root scoped service factory with optional options. - * - * @public - * @param config - The service factory configuration. - */ -export function createServiceFactory< - TService, - TImpl extends TService, - TDeps extends { [name in string]: ServiceRef }, - TOpts extends object | undefined = undefined, ->( - config: (options?: TOpts) => RootServiceFactoryConfig, -): (options?: TOpts) => ServiceFactory; + options: RootServiceFactoryOptions, +): ServiceFactory; /** * 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, + TInstances extends 'singleton' | 'multiton', TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, TContext = undefined, - TOpts extends object | undefined = undefined, >( - config: PluginServiceFactoryConfig, -): () => ServiceFactory; -/** - * Creates a plugin scoped service factory with optional options. - * - * @public - * @param config - The service factory configuration. - */ -export function createServiceFactory< - TService, - TImpl extends TService, - TDeps extends { [name in string]: ServiceRef }, - TContext = undefined, - TOpts extends object | undefined = undefined, ->( - config: ( - options?: TOpts, - ) => PluginServiceFactoryConfig, -): (options?: TOpts) => ServiceFactory; + options: PluginServiceFactoryOptions< + TService, + TInstances, + TContext, + TImpl, + TDeps + >, +): ServiceFactory; export function createServiceFactory< TService, + TInstances extends 'singleton' | 'multiton', TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, TContext, - TOpts extends object | undefined = undefined, >( - config: - | RootServiceFactoryConfig - | PluginServiceFactoryConfig - | ((options: TOpts) => RootServiceFactoryConfig) - | (( - options: TOpts, - ) => PluginServiceFactoryConfig) - | (() => RootServiceFactoryConfig) - | (() => PluginServiceFactoryConfig), -): (options: TOpts) => ServiceFactory { - const configCallback = typeof config === 'function' ? config : () => config; - const factory = ( - options: TOpts, - ): InternalServiceFactory => { - const anyConf = configCallback(options); - if (anyConf.service.scope === 'root') { - const c = anyConf as RootServiceFactoryConfig; - return { - $$type: '@backstage/BackendFeature', - version: 'v1', - service: c.service, - initialization: c.initialization, - deps: c.deps, - factory: async (deps: TDeps) => c.factory(deps), - }; - } - const c = anyConf as PluginServiceFactoryConfig< + options: + | RootServiceFactoryOptions + | PluginServiceFactoryOptions, +): ServiceFactory { + if (options.service.scope === 'root') { + const c = options as RootServiceFactoryOptions< TService, - TContext, + TInstances, TImpl, TDeps >; return { $$type: '@backstage/BackendFeature', version: 'v1', + featureType: 'service', service: c.service, initialization: c.initialization, - ...('createRootContext' in c - ? { - createRootContext: async (deps: TDeps) => - c?.createRootContext?.(deps), - } - : {}), - deps: c.deps, - factory: async (deps: TDeps, ctx: TContext) => c.factory(deps, ctx), - }; - }; - - factory.$$type = '@backstage/BackendFeatureFactory'; - - return factory; + deps: options.deps, + factory: async (deps: ServiceRefsToInstances) => + c.factory(deps), + } as InternalServiceFactory; + } + const c = options as PluginServiceFactoryOptions< + TService, + TInstances, + TContext, + TImpl, + TDeps + >; + return { + $$type: '@backstage/BackendFeature', + version: 'v1', + featureType: 'service', + service: c.service, + initialization: c.initialization, + ...('createRootContext' in options + ? { + createRootContext: async ( + deps: ServiceRefsToInstances, + ) => c?.createRootContext?.(deps), + } + : {}), + deps: options.deps, + factory: async (deps: ServiceRefsToInstances, ctx: TContext) => + c.factory(deps, ctx), + } as InternalServiceFactory; } diff --git a/packages/backend-plugin-api/src/types.ts b/packages/backend-plugin-api/src/types.ts index 31684b6a38..59f1106fab 100644 --- a/packages/backend-plugin-api/src/types.ts +++ b/packages/backend-plugin-api/src/types.ts @@ -27,14 +27,3 @@ export interface BackendFeature { // NOTE: This type is opaque in order to simplify future API evolution. $$type: '@backstage/BackendFeature'; } - -/** - * @public - * @deprecated This type exists only as a helper for old code that relied on `createBackendFeature` and `createBackendPlugin` to return `() => BackendFeature` instead of `BackendFeature`. You should remove the `()` parentheses at the end of your usages. This type will be removed in a future release. - */ -export interface BackendFeatureCompat extends BackendFeature { - /** - * @deprecated You do not need to use this call signature; use the type directly instead by removing the `()` parentheses at the end. This call signature will be removed in a future release. - */ - (): this; -} diff --git a/packages/backend-plugin-api/src/wiring/createBackendFeatureLoader.test.ts b/packages/backend-plugin-api/src/wiring/createBackendFeatureLoader.test.ts new file mode 100644 index 0000000000..cf7ab18ace --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createBackendFeatureLoader.test.ts @@ -0,0 +1,195 @@ +/* + * 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 { coreServices, createServiceFactory } from '../services'; +import { InternalServiceFactory } from '../services/system/types'; +import { BackendFeature } from '../types'; +import { createBackendFeatureLoader } from './createBackendFeatureLoader'; +import { createBackendPlugin } from './createBackendPlugin'; +import { InternalBackendFeatureLoader } from './types'; + +describe('createBackendFeatureLoader', () => { + it('should create an empty feature loader', () => { + const result = createBackendFeatureLoader({ + deps: {}, + loader: () => [], + }) as InternalBackendFeatureLoader; + + expect(result.$$type).toEqual('@backstage/BackendFeature'); + expect(result.version).toEqual('v1'); + expect(result.featureType).toEqual('loader'); + expect(result.deps).toEqual({}); + expect(result.loader).toEqual(expect.any(Function)); + expect(result.description).toMatch(/^created at '.*'$/); + }); + + it('should create a feature loader that loads a few features', async () => { + const result = createBackendFeatureLoader({ + deps: { + config: coreServices.rootConfig, + }, + loader({ config: _unused }) { + return [ + createBackendPlugin({ + pluginId: 'x', + register() {}, + }), + createServiceFactory({ + service: coreServices.pluginMetadata, + deps: {}, + factory: () => ({ getId: () => 'fake-id' }), + }), + // Dynamic import format + Promise.resolve({ + default: createBackendPlugin({ + pluginId: 'y', + register() {}, + }), + }), + ]; + }, + }) as InternalBackendFeatureLoader; + + expect(result.$$type).toEqual('@backstage/BackendFeature'); + expect(result.version).toEqual('v1'); + expect(result.featureType).toEqual('loader'); + + const results = await result.loader({ config: {} }); + expect(results.length).toBe(3); + const [pluginX, serviceFactory, pluginY] = results; + expect(pluginX.$$type).toBe('@backstage/BackendFeature'); + expect(serviceFactory.$$type).toBe('@backstage/BackendFeature'); + expect(pluginY.$$type).toBe('@backstage/BackendFeature'); + expect((serviceFactory as InternalServiceFactory).service.id).toBe( + coreServices.pluginMetadata.id, + ); + }); + + it('should support multiple output formats', async () => { + const feature = createBackendPlugin({ pluginId: 'x', register() {} }); + const dynamicFeature = Promise.resolve({ default: feature }); + + async function extractResult(f: BackendFeature) { + const internal = f as InternalBackendFeatureLoader; + return internal.loader({}); + } + + await expect( + extractResult( + createBackendFeatureLoader({ + loader() { + return [feature]; + }, + }), + ), + ).resolves.toEqual([feature]); + + await expect( + extractResult( + createBackendFeatureLoader({ + async loader() { + return [feature]; + }, + }), + ), + ).resolves.toEqual([feature]); + + await expect( + extractResult( + createBackendFeatureLoader({ + *loader() { + yield feature; + }, + }), + ), + ).resolves.toEqual([feature]); + + await expect( + extractResult( + createBackendFeatureLoader({ + async *loader() { + yield feature; + }, + }), + ), + ).resolves.toEqual([feature]); + + await expect( + extractResult( + createBackendFeatureLoader({ + loader() { + return [dynamicFeature]; + }, + }), + ), + ).resolves.toEqual([feature]); + + await expect( + extractResult( + createBackendFeatureLoader({ + async loader() { + return [dynamicFeature]; + }, + }), + ), + ).resolves.toEqual([feature]); + + await expect( + extractResult( + createBackendFeatureLoader({ + *loader() { + yield dynamicFeature; + }, + }), + ), + ).resolves.toEqual([feature]); + + await expect( + extractResult( + createBackendFeatureLoader({ + async *loader() { + yield dynamicFeature; + }, + }), + ), + ).resolves.toEqual([feature]); + }); + + it('should only allow dependencies on root scoped services', () => { + createBackendFeatureLoader({ + deps: { + rootLogger: coreServices.rootLogger, + }, + loader: () => [], + }); + createBackendFeatureLoader({ + deps: { + // @ts-expect-error + logger: coreServices.logger, + }, + loader: () => [], + }); + createBackendFeatureLoader({ + deps: { + rootLogger: coreServices.rootLogger, + // @ts-expect-error + logger: coreServices.logger, + }, + loader: () => [], + }); + expect('test').toBe('test'); + }); +}); diff --git a/packages/backend-plugin-api/src/wiring/createBackendFeatureLoader.ts b/packages/backend-plugin-api/src/wiring/createBackendFeatureLoader.ts new file mode 100644 index 0000000000..cfa3e96c65 --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createBackendFeatureLoader.ts @@ -0,0 +1,68 @@ +/* + * 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 } from '../services'; +import { BackendFeature } from '../types'; +import { describeParentCallSite } from './describeParentCallSite'; +import { InternalBackendFeatureLoader } from './types'; + +/** + * @public + * Options for creating a new backend feature loader. + */ +export interface CreateBackendFeatureLoaderOptions< + TDeps extends { [name in string]: unknown }, +> { + deps?: { + [name in keyof TDeps]: ServiceRef; + }; + loader( + deps: TDeps, + ): + | Iterable> + | Promise>> + | AsyncIterable; +} + +/** + * @public + * Creates a new backend feature loader. + */ +export function createBackendFeatureLoader< + TDeps extends { [name in string]: unknown }, +>(options: CreateBackendFeatureLoaderOptions): BackendFeature { + return { + $$type: '@backstage/BackendFeature', + version: 'v1', + featureType: 'loader', + description: `created at '${describeParentCallSite()}'`, + deps: options.deps, + async loader(deps: TDeps) { + const it = await options.loader(deps); + const result = new Array(); + for await (const item of it) { + if ('$$type' in item && item.$$type === '@backstage/BackendFeature') { + result.push(item); + } else if ('default' in item) { + result.push(item.default); + } else { + throw new Error(`Invalid item "${item}"`); + } + } + return result; + }, + } as InternalBackendFeatureLoader; +} diff --git a/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts b/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts new file mode 100644 index 0000000000..4d927c86f6 --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts @@ -0,0 +1,80 @@ +/* + * 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 { createServiceRef } from '../services'; +import { createBackendModule } from './createBackendModule'; +import { createExtensionPoint } from './createExtensionPoint'; +import { InternalBackendRegistrations } from './types'; + +describe('createBackendModule', () => { + it('should create a BackendModule', () => { + const result = createBackendModule({ + pluginId: 'x', + moduleId: 'y', + register(r) { + r.registerInit({ deps: {}, async init() {} }); + }, + }); + + const module = result as unknown as InternalBackendRegistrations; + expect(module.$$type).toEqual('@backstage/BackendFeature'); + expect(module.version).toEqual('v1'); + expect(module.getRegistrations).toEqual(expect.any(Function)); + expect(module.getRegistrations()).toEqual([ + { + type: 'module', + pluginId: 'x', + moduleId: 'y', + extensionPoints: [], + init: { + deps: expect.any(Object), + func: expect.any(Function), + }, + }, + ]); + }); + + it('should be able to depend on all types of dependencies', () => { + const extensionPoint = createExtensionPoint({ id: 'point' }); + const singleServiceRef = createServiceRef({ id: 'single' }); + const multiServiceRef = createServiceRef({ + id: 'multi', + multiton: true, + }); + + const plugin = createBackendModule({ + pluginId: 'x', + moduleId: 'y', + register(r) { + r.registerInit({ + deps: { + point: extensionPoint, + single: singleServiceRef, + multi: multiServiceRef, + }, + async init({ point, single, multi }) { + const a: string = point; + const b: string = single; + const c: string[] = multi; + expect([a, b, c]).toBe('unused'); + }, + }); + }, + }); + + expect(plugin.$$type).toEqual('@backstage/BackendFeature'); + }); +}); diff --git a/packages/backend-plugin-api/src/wiring/createBackendModule.ts b/packages/backend-plugin-api/src/wiring/createBackendModule.ts new file mode 100644 index 0000000000..e2357a65d0 --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createBackendModule.ts @@ -0,0 +1,103 @@ +/* + * 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 { BackendFeature } from '../types'; +import { + BackendModuleRegistrationPoints, + InternalBackendModuleRegistration, + InternalBackendPluginRegistration, + InternalBackendRegistrations, +} from './types'; + +/** + * The configuration options passed to {@link createBackendModule}. + * + * @public + * @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules} + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ +export interface CreateBackendModuleOptions { + /** + * Should exactly match the `id` of the plugin that the module extends. + * + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ + pluginId: string; + + /** + * The ID of this module, used to identify the module and ensure that it is not installed twice. + */ + moduleId: string; + register(reg: BackendModuleRegistrationPoints): void; +} + +/** + * Creates a new backend module for a given plugin. + * + * @public + * @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules} + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ +export function createBackendModule( + options: CreateBackendModuleOptions, +): BackendFeature { + function getRegistrations() { + const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] = + []; + let init: InternalBackendModuleRegistration['init'] | undefined = undefined; + + options.register({ + registerExtensionPoint(ext, impl) { + if (init) { + throw new Error('registerExtensionPoint called after registerInit'); + } + extensionPoints.push([ext, impl]); + }, + registerInit(regInit) { + if (init) { + throw new Error('registerInit must only be called once'); + } + init = { + deps: regInit.deps, + func: regInit.init, + }; + }, + }); + + if (!init) { + throw new Error( + `registerInit was not called by register in ${options.moduleId} module for ${options.pluginId}`, + ); + } + + return [ + { + type: 'module', + pluginId: options.pluginId, + moduleId: options.moduleId, + extensionPoints, + init, + }, + ]; + } + + return { + $$type: '@backstage/BackendFeature' as const, + featureType: 'registrations', + version: 'v1', + getRegistrations, + } as InternalBackendRegistrations; +} diff --git a/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts b/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts new file mode 100644 index 0000000000..5acd8e46d7 --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts @@ -0,0 +1,93 @@ +/* + * 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 { createServiceRef } from '../services'; +import { createBackendPlugin } from './createBackendPlugin'; +import { createExtensionPoint } from './createExtensionPoint'; +import { InternalBackendRegistrations } from './types'; + +describe('createBackendPlugin', () => { + it('should create a BackendPlugin', () => { + const result = createBackendPlugin({ + pluginId: 'x', + register(r) { + r.registerInit({ deps: {}, async init() {} }); + }, + }); + + const plugin = result as unknown as InternalBackendRegistrations; + expect(plugin.$$type).toEqual('@backstage/BackendFeature'); + expect(plugin.version).toEqual('v1'); + expect(plugin.getRegistrations).toEqual(expect.any(Function)); + expect(plugin.getRegistrations()).toEqual([ + { + type: 'plugin', + pluginId: 'x', + extensionPoints: [], + init: { + deps: expect.any(Object), + func: expect.any(Function), + }, + }, + ]); + }); + + it('should be able to depend on all compatible dependencies', () => { + const singleServiceRef = createServiceRef({ id: 'single' }); + const multiServiceRef = createServiceRef({ + id: 'multi', + multiton: true, + }); + + const plugin = createBackendPlugin({ + pluginId: 'x', + register(r) { + r.registerInit({ + deps: { + single: singleServiceRef, + multi: multiServiceRef, + }, + async init({ single, multi }) { + const a: string = single; + const b: string[] = multi; + expect([a, b]).toBe('unused'); + }, + }); + }, + }); + + expect(plugin.$$type).toEqual('@backstage/BackendFeature'); + }); + + it('should not be able to depend on extension points', () => { + const extensionPoint = createExtensionPoint({ id: 'point' }); + + const plugin = createBackendPlugin({ + pluginId: 'x', + register(r) { + r.registerInit({ + deps: { + // @ts-expect-error + point: extensionPoint, + }, + async init() {}, + }); + }, + }); + + expect(plugin.$$type).toEqual('@backstage/BackendFeature'); + }); +}); diff --git a/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts b/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts new file mode 100644 index 0000000000..185500c495 --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts @@ -0,0 +1,96 @@ +/* + * 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 { BackendFeature } from '../types'; +import { + BackendPluginRegistrationPoints, + InternalBackendPluginRegistration, + InternalBackendRegistrations, +} from './types'; + +/** + * The configuration options passed to {@link createBackendPlugin}. + * + * @public + * @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins} + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ +export interface CreateBackendPluginOptions { + /** + * The ID of this plugin. + * + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ + pluginId: string; + register(reg: BackendPluginRegistrationPoints): void; +} + +/** + * Creates a new backend plugin. + * + * @public + * @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins} + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ +export function createBackendPlugin( + options: CreateBackendPluginOptions, +): BackendFeature { + function getRegistrations() { + const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] = + []; + let init: InternalBackendPluginRegistration['init'] | undefined = undefined; + + options.register({ + registerExtensionPoint(ext, impl) { + if (init) { + throw new Error('registerExtensionPoint called after registerInit'); + } + extensionPoints.push([ext, impl]); + }, + registerInit(regInit) { + if (init) { + throw new Error('registerInit must only be called once'); + } + init = { + deps: regInit.deps, + func: regInit.init, + }; + }, + }); + + if (!init) { + throw new Error( + `registerInit was not called by register in ${options.pluginId}`, + ); + } + + return [ + { + type: 'plugin', + pluginId: options.pluginId, + extensionPoints, + init, + }, + ]; + } + + return { + $$type: '@backstage/BackendFeature' as const, + version: 'v1', + featureType: 'registrations', + getRegistrations, + } as InternalBackendRegistrations; +} diff --git a/packages/backend-app-api/src/services/implementations/index.ts b/packages/backend-plugin-api/src/wiring/createExtensionPoint.test.ts similarity index 59% rename from packages/backend-app-api/src/services/implementations/index.ts rename to packages/backend-plugin-api/src/wiring/createExtensionPoint.test.ts index 3a2fe819ed..825063e845 100644 --- a/packages/backend-app-api/src/services/implementations/index.ts +++ b/packages/backend-plugin-api/src/wiring/createExtensionPoint.test.ts @@ -14,15 +14,14 @@ * limitations under the License. */ -export * from './cache'; -export * from './config'; -export * from './database'; -export * from './discovery'; -export * from './identity'; -export * from './lifecycle'; -export * from './permissions'; -export * from './rootLifecycle'; -export * from './tokenManager'; -export * from './urlReader'; +import { createExtensionPoint } from './createExtensionPoint'; -export * from './deprecated'; +describe('createExtensionPoint', () => { + it('should create an ExtensionPoint', () => { + const extensionPoint = createExtensionPoint({ id: 'x' }); + expect(extensionPoint).toBeDefined(); + expect(extensionPoint.id).toBe('x'); + expect(() => extensionPoint.T).not.toThrow(); + expect(String(extensionPoint)).toBe('extensionPoint{x}'); + }); +}); diff --git a/packages/backend-plugin-api/src/wiring/createExtensionPoint.ts b/packages/backend-plugin-api/src/wiring/createExtensionPoint.ts new file mode 100644 index 0000000000..9dbae249ea --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createExtensionPoint.ts @@ -0,0 +1,58 @@ +/* + * 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 { ExtensionPoint } from './types'; + +/** + * The configuration options passed to {@link createExtensionPoint}. + * + * @public + * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points} + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ +export interface CreateExtensionPointOptions { + /** + * The ID of this extension point. + * + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ + id: string; +} + +/** + * Creates a new backend extension point. + * + * @public + * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points} + */ +export function createExtensionPoint( + options: CreateExtensionPointOptions, +): ExtensionPoint { + return { + id: options.id, + get T(): T { + if (process.env.NODE_ENV === 'test') { + // Avoid throwing errors so tests asserting extensions' properties cannot be easily broken + return null as T; + } + throw new Error(`tried to read ExtensionPoint.T of ${this}`); + }, + toString() { + return `extensionPoint{${options.id}}`; + }, + $$type: '@backstage/ExtensionPoint', + }; +} diff --git a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts b/packages/backend-plugin-api/src/wiring/describeParentCallSite.ts similarity index 67% rename from packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts rename to packages/backend-plugin-api/src/wiring/describeParentCallSite.ts index 988b907b4c..35603e33b0 100644 --- a/packages/backend-app-api/src/services/implementations/auth/authServiceFactory.ts +++ b/packages/backend-plugin-api/src/wiring/describeParentCallSite.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * Copyright 2023 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,7 @@ * limitations under the License. */ +// Single re-export to avoid doing this import in multiple places, but still +// avoid duplicate declarations because this one is a bit tricky. // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { authServiceFactory as _authServiceFactory } from '../../../../../backend-defaults/src/entrypoints/auth'; - -/** - * @public - * @deprecated Please import from `@backstage/backend-defaults/auth` instead. - */ -export const authServiceFactory = _authServiceFactory; +export { describeParentCallSite } from '../../../frontend-plugin-api/src/routing/describeParentCallSite'; diff --git a/packages/backend-plugin-api/src/wiring/factories.test.ts b/packages/backend-plugin-api/src/wiring/factories.test.ts deleted file mode 100644 index 6d5c1f39cd..0000000000 --- a/packages/backend-plugin-api/src/wiring/factories.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - createBackendModule, - createBackendPlugin, - createExtensionPoint, -} from './factories'; -import { InternalBackendFeature } from './types'; - -describe('createExtensionPoint', () => { - it('should create an ExtensionPoint', () => { - const extensionPoint = createExtensionPoint({ id: 'x' }); - expect(extensionPoint).toBeDefined(); - expect(extensionPoint.id).toBe('x'); - expect(() => extensionPoint.T).not.toThrow(); - expect(String(extensionPoint)).toBe('extensionPoint{x}'); - }); -}); - -describe('createBackendPlugin', () => { - it('should create a BackendPlugin', () => { - const result = createBackendPlugin({ - pluginId: 'x', - register(r) { - r.registerInit({ deps: {}, async init() {} }); - }, - }); - - // legacy form - const legacy = result() as unknown as InternalBackendFeature; - expect(legacy.$$type).toEqual('@backstage/BackendFeature'); - expect(legacy.version).toEqual('v1'); - expect(legacy.getRegistrations).toEqual(expect.any(Function)); - - // new form - const plugin = result as unknown as InternalBackendFeature; - expect(plugin.$$type).toEqual('@backstage/BackendFeature'); - expect(plugin.version).toEqual('v1'); - expect(plugin.getRegistrations).toEqual(expect.any(Function)); - expect(plugin.getRegistrations()).toEqual([ - { - type: 'plugin', - pluginId: 'x', - extensionPoints: [], - init: { - deps: expect.any(Object), - func: expect.any(Function), - }, - }, - ]); - - // @ts-expect-error - expect(plugin({ a: 'a' })).toBeDefined(); - }); -}); - -describe('createBackendModule', () => { - it('should create a BackendModule', () => { - const result = createBackendModule({ - pluginId: 'x', - moduleId: 'y', - register(r) { - r.registerInit({ deps: {}, async init() {} }); - }, - }); - - // legacy form - const legacy = result() as unknown as InternalBackendFeature; - expect(legacy.$$type).toEqual('@backstage/BackendFeature'); - expect(legacy.version).toEqual('v1'); - expect(legacy.getRegistrations).toEqual(expect.any(Function)); - - // new form - const module = result as unknown as InternalBackendFeature; - expect(module.$$type).toEqual('@backstage/BackendFeature'); - expect(module.version).toEqual('v1'); - expect(module.getRegistrations).toEqual(expect.any(Function)); - expect(module.getRegistrations()).toEqual([ - { - type: 'module', - pluginId: 'x', - moduleId: 'y', - extensionPoints: [], - init: { - deps: expect.any(Object), - func: expect.any(Function), - }, - }, - ]); - - // @ts-expect-error - expect(module({ a: 'a' })).toBeDefined(); - }); -}); diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts deleted file mode 100644 index 81e2247152..0000000000 --- a/packages/backend-plugin-api/src/wiring/factories.ts +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { BackendFeatureCompat } from '../types'; -import { - BackendModuleRegistrationPoints, - BackendPluginRegistrationPoints, - ExtensionPoint, - InternalBackendModuleRegistration, - InternalBackendPluginRegistration, -} from './types'; - -/** - * The configuration options passed to {@link createExtensionPoint}. - * - * @public - * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points} - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ -export interface CreateExtensionPointOptions { - /** - * The ID of this extension point. - * - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ - id: string; -} - -/** - * Creates a new backend extension point. - * - * @public - * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points} - */ -export function createExtensionPoint( - options: CreateExtensionPointOptions, -): ExtensionPoint { - return { - id: options.id, - get T(): T { - if (process.env.NODE_ENV === 'test') { - // Avoid throwing errors so tests asserting extensions' properties cannot be easily broken - return null as T; - } - throw new Error(`tried to read ExtensionPoint.T of ${this}`); - }, - toString() { - return `extensionPoint{${options.id}}`; - }, - $$type: '@backstage/ExtensionPoint', - }; -} - -/** - * The configuration options passed to {@link createBackendPlugin}. - * - * @public - * @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins} - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ -export interface CreateBackendPluginOptions { - /** - * The ID of this plugin. - * - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ - pluginId: string; - register(reg: BackendPluginRegistrationPoints): void; -} - -/** - * Creates a new backend plugin. - * - * @public - * @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins} - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ -export function createBackendPlugin( - options: CreateBackendPluginOptions, -): BackendFeatureCompat { - function getRegistrations() { - const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] = - []; - let init: InternalBackendPluginRegistration['init'] | undefined = undefined; - - options.register({ - registerExtensionPoint(ext, impl) { - if (init) { - throw new Error('registerExtensionPoint called after registerInit'); - } - extensionPoints.push([ext, impl]); - }, - registerInit(regInit) { - if (init) { - throw new Error('registerInit must only be called once'); - } - init = { - deps: regInit.deps, - func: regInit.init, - }; - }, - }); - - if (!init) { - throw new Error( - `registerInit was not called by register in ${options.pluginId}`, - ); - } - - return [ - { - type: 'plugin', - pluginId: options.pluginId, - extensionPoints, - init, - }, - ]; - } - - function backendFeatureCompatWrapper() { - return backendFeatureCompatWrapper; - } - - Object.assign(backendFeatureCompatWrapper, { - $$type: '@backstage/BackendFeature' as const, - version: 'v1', - getRegistrations, - }); - - return backendFeatureCompatWrapper as BackendFeatureCompat; -} - -/** - * The configuration options passed to {@link createBackendModule}. - * - * @public - * @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules} - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ -export interface CreateBackendModuleOptions { - /** - * Should exactly match the `id` of the plugin that the module extends. - * - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ - pluginId: string; - - /** - * The ID of this module, used to identify the module and ensure that it is not installed twice. - */ - moduleId: string; - register(reg: BackendModuleRegistrationPoints): void; -} - -/** - * Creates a new backend module for a given plugin. - * - * @public - * @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules} - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ -export function createBackendModule( - options: CreateBackendModuleOptions, -): BackendFeatureCompat { - function getRegistrations() { - const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] = - []; - let init: InternalBackendModuleRegistration['init'] | undefined = undefined; - - options.register({ - registerExtensionPoint(ext, impl) { - if (init) { - throw new Error('registerExtensionPoint called after registerInit'); - } - extensionPoints.push([ext, impl]); - }, - registerInit(regInit) { - if (init) { - throw new Error('registerInit must only be called once'); - } - init = { - deps: regInit.deps, - func: regInit.init, - }; - }, - }); - - if (!init) { - throw new Error( - `registerInit was not called by register in ${options.moduleId} module for ${options.pluginId}`, - ); - } - - return [ - { - type: 'module', - pluginId: options.pluginId, - moduleId: options.moduleId, - extensionPoints, - init, - }, - ]; - } - - function backendFeatureCompatWrapper() { - return backendFeatureCompatWrapper; - } - - Object.assign(backendFeatureCompatWrapper, { - $$type: '@backstage/BackendFeature' as const, - version: 'v1', - getRegistrations, - }); - - return backendFeatureCompatWrapper as BackendFeatureCompat; -} diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts index 5197cec050..73b099978e 100644 --- a/packages/backend-plugin-api/src/wiring/index.ts +++ b/packages/backend-plugin-api/src/wiring/index.ts @@ -14,17 +14,17 @@ * limitations under the License. */ -import type { - CreateBackendPluginOptions, - CreateBackendModuleOptions, - CreateExtensionPointOptions, -} from './factories'; +import { type CreateBackendModuleOptions } from './createBackendModule'; +import { type CreateBackendPluginOptions } from './createBackendPlugin'; +import { type CreateExtensionPointOptions } from './createExtensionPoint'; +export { createBackendModule } from './createBackendModule'; +export { createBackendPlugin } from './createBackendPlugin'; +export { createExtensionPoint } from './createExtensionPoint'; export { - createBackendModule, - createBackendPlugin, - createExtensionPoint, -} from './factories'; + createBackendFeatureLoader, + type CreateBackendFeatureLoaderOptions, +} from './createBackendFeatureLoader'; export type { BackendModuleRegistrationPoints, @@ -37,21 +37,3 @@ export type { CreateBackendModuleOptions, CreateExtensionPointOptions, }; - -/** - * @public - * @deprecated Use {@link CreateBackendPluginOptions} instead. - */ -export type BackendPluginConfig = CreateBackendPluginOptions; - -/** - * @public - * @deprecated Use {@link CreateBackendModuleOptions} instead. - */ -export type BackendModuleConfig = CreateBackendModuleOptions; - -/** - * @public - * @deprecated Use {@link CreateExtensionPointOptions} instead. - */ -export type ExtensionPointConfig = CreateExtensionPointOptions; diff --git a/packages/backend-plugin-api/src/wiring/types.ts b/packages/backend-plugin-api/src/wiring/types.ts index 55d43ccb7c..1e8c3910a3 100644 --- a/packages/backend-plugin-api/src/wiring/types.ts +++ b/packages/backend-plugin-api/src/wiring/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ServiceRef } from '../services/system/types'; +import { InternalServiceFactory, ServiceRef } from '../services/system/types'; import { BackendFeature } from '../types'; /** @@ -36,6 +36,17 @@ export type ExtensionPoint = { $$type: '@backstage/ExtensionPoint'; }; +/** @ignore */ +type DepsToInstances< + TDeps extends { + [key in string]: ServiceRef | ExtensionPoint; + }, +> = { + [key in keyof TDeps]: TDeps[key] extends ServiceRef + ? Array + : TDeps[key]['T']; +}; + /** * The callbacks passed to the `register` method of a backend plugin. * @@ -46,11 +57,13 @@ export interface BackendPluginRegistrationPoints { ref: ExtensionPoint, impl: TExtensionPoint, ): void; - registerInit(options: { - deps: { - [name in keyof Deps]: ServiceRef; - }; - init(deps: Deps): Promise; + registerInit< + TDeps extends { + [name in string]: ServiceRef; + }, + >(options: { + deps: TDeps; + init(deps: DepsToInstances): Promise; }): void; } @@ -64,17 +77,20 @@ export interface BackendModuleRegistrationPoints { ref: ExtensionPoint, impl: TExtensionPoint, ): void; - registerInit(options: { - deps: { - [name in keyof Deps]: ServiceRef | ExtensionPoint; - }; - init(deps: Deps): Promise; + registerInit< + TDeps extends { + [name in string]: ServiceRef | ExtensionPoint; + }, + >(options: { + deps: TDeps; + init(deps: DepsToInstances): Promise; }): void; } /** @internal */ -export interface InternalBackendFeature extends BackendFeature { +export interface InternalBackendRegistrations extends BackendFeature { version: 'v1'; + featureType: 'registrations'; getRegistrations(): Array< InternalBackendPluginRegistration | InternalBackendModuleRegistration >; @@ -102,3 +118,20 @@ export interface InternalBackendModuleRegistration { func(deps: Record): Promise; }; } + +/** + * @public + */ +export interface InternalBackendFeatureLoader extends BackendFeature { + version: 'v1'; + featureType: 'loader'; + description: string; + deps: Record>; + loader(deps: Record): Promise; +} + +/** @internal */ +export type InternalBackendFeature = + | InternalBackendRegistrations + | InternalBackendFeatureLoader + | InternalServiceFactory; diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 008ae3cb11..fbdbff1a8a 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,100 @@ # @backstage/backend-tasks +## 0.6.0 + +### Minor Changes + +- fc24d9e: This package is deprecated and will be removed in a near future, follow the instructions below to stop using it: + + - `TaskScheduler`: Please migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead, or use `DefaultSchedulerService` from `@backstage/backend-defaults; + - `TaskRunner`: Please import `SchedulerServiceTaskRunner` from `@backstage/backend-plugin-api` instead; + - `TaskFunction`: Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead; + - `TaskDescriptor`: Please import `SchedulerServiceTaskDescriptor` from `@backstage/backend-plugin-api` instead; + - `TaskInvocationDefinition`: Please import `SchedulerServiceTaskInvocationDefinition` from `@backstage/backend-plugin-api` instead; + - `TaskScheduleDefinition`: Please import `SchedulerServiceTaskFunction` from `@backstage/backend-plugin-api` instead; + - `TaskScheduleDefinitionConfig`: Please import `SchedulerServiceTaskScheduleDefinitionConfig` from `@backstage/backend-plugin-api` instead; + - `PluginTaskScheduler`: Please use `SchedulerService` from `@backstage/backend-plugin-api` instead (most likely via `coreServices.scheduler`); + - `readTaskScheduleDefinitionFromConfig`: Please import `readSchedulerServiceTaskScheduleDefinitionFromConfig` from `@backstage/backend-plugin-api` instead; + - `HumanDuration`: Import `TypesHumanDuration` from `@backstage/types` instead. + +### Patch Changes + +- ba9abf4: The `PluginTaskScheduler` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `PluginTaskScheduler.triggerTask`. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.28-next.2 + +### Patch Changes + +- ba9abf4: The `PluginTaskScheduler` now allows tasks with `frequency: { trigger: 'manual' }`. This means that the task will not be scheduled, but rather run only when manually triggered with `PluginTaskScheduler.triggerTask`. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.5.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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/README.md b/packages/backend-tasks/README.md index 57cd63b0e0..bdb151dea6 100644 --- a/packages/backend-tasks/README.md +++ b/packages/backend-tasks/README.md @@ -1,9 +1,16 @@ # @backstage/backend-tasks +> [!CAUTION] +> This package is deprecated and will be removed in a near future. + Common distributed task management for Backstage backends. ## Usage +> [!CAUTION] +> Please note that the documentation below is only valid for versions equal to or below `0.5.28-next.3`. +> As this package will be deleted soon, we recommend that you migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead, or use `DefaultSchedulerService` from `@backstage/backend-defaults`. Here are the [backend](https://backstage.io/docs/backend-system/building-backends/migrating) and [plugin](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating) migration guides. + Add the library to your backend package: ```bash @@ -27,12 +34,3 @@ await scheduler.scheduleTask({ }, }); ``` - -## Local Development - -When working with the `@backstage/backend-tasks` library you may run into your task not running immediately at startup as expected if you are using a persistent database. This is by design - the library respects the previous state and does not run the task sooner than the specified frequency. If you want to get around this, there is a table called `backstage_backend_tasks__tasks` in the applicable plugin's database which will contain a record with the next run date and time. You can delete this record to get things back to what you expect. - -## Documentation - -- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) -- [Backstage Documentation](https://backstage.io/docs) diff --git a/packages/backend-tasks/api-report.md b/packages/backend-tasks/api-report.md index 0d5ced044a..c0f9da3a01 100644 --- a/packages/backend-tasks/api-report.md +++ b/packages/backend-tasks/api-report.md @@ -62,7 +62,14 @@ export interface TaskScheduleDefinition { cron: string; } | Duration - | HumanDuration_2; + | HumanDuration_2 + /** + * This task will only run when manually triggered with the `triggerTask` method; no automatic + * scheduling. This is useful for locking of global tasks that should not be run concurrently. + */ + | { + trigger: 'manual'; + }; initialDelay?: Duration | HumanDuration_2; scope?: 'global' | 'local'; timeout: Duration | HumanDuration_2; diff --git a/packages/backend-tasks/migrations/20240712211735_nullable_next_run.js b/packages/backend-tasks/migrations/20240712211735_nullable_next_run.js new file mode 100644 index 0000000000..efbef1c76f --- /dev/null +++ b/packages/backend-tasks/migrations/20240712211735_nullable_next_run.js @@ -0,0 +1,41 @@ +/* + * 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. + */ + +// @ts-check + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = async function up(knex) { + await knex.schema.alterTable('backstage_backend_tasks__tasks', table => { + table.setNullable('next_run_start_at'); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = async function down(knex) { + await knex + .delete() + .from('backstage_backend_tasks__tasks') + .where({ next_run_start_at: null }); + await knex.schema.alterTable('backstage_backend_tasks__tasks', table => { + table.dropNullable('next_run_start_at'); + }); +}; diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 2033be826c..f0209c1fa9 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,35 +1,39 @@ { "name": "@backstage/backend-tasks", + "version": "0.6.0", "description": "Common distributed task management library for Backstage backends", - "version": "0.5.26-next.0", - "main": "src/index.ts", - "types": "src/index.ts", + "backstage": { + "role": "node-library" + }, "publishConfig": { "access": "public", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, - "backstage": { - "role": "node-library" - }, + "keywords": [ + "backstage" + ], "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", "directory": "packages/backend-tasks" }, - "keywords": [ - "backstage" - ], "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist", + "migrations/**/*.{js,d.ts}" + ], "scripts": { "build": "backstage-cli package build", + "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "start": "backstage-cli package start", + "test": "backstage-cli package test" }, "dependencies": { "@backstage/backend-common": "workspace:^", @@ -50,9 +54,5 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "wait-for-expect": "^3.0.2" - }, - "files": [ - "dist", - "migrations/**/*.{js,d.ts}" - ] + } } diff --git a/packages/backend-tasks/src/index.ts b/packages/backend-tasks/src/index.ts index 0aae81b143..83c7606700 100644 --- a/packages/backend-tasks/src/index.ts +++ b/packages/backend-tasks/src/index.ts @@ -17,6 +17,10 @@ /** * Common distributed task management library for Backstage backends * + * @remarks + * This package is deprecated and will be removed in a near future. + * Please migrate to the new backend system, and depend on `coreServices.scheduler` from `@backstage/backend-plugin-api` instead, or use `DefaultSchedulerService` from `@backstage/backend-defaults`. + * * @packageDocumentation */ diff --git a/packages/backend-tasks/src/migrations.test.ts b/packages/backend-tasks/src/migrations.test.ts index 82208da975..7583948f71 100644 --- a/packages/backend-tasks/src/migrations.test.ts +++ b/packages/backend-tasks/src/migrations.test.ts @@ -81,4 +81,43 @@ describe('migrations', () => { await knex.destroy(); }, ); + + it.each(databases.eachSupportedId())( + '20240712211735_nullable_next_run.js, %p', + async databaseId => { + const knex = await databases.init(databaseId); + + await migrateUntilBefore(knex, '20240712211735_nullable_next_run.js'); + await migrateUpOnce(knex); + + await knex('backstage_backend_tasks__tasks').insert({ + id: 'test', + settings_json: '{}', + next_run_start_at: knex.raw('null'), + }); + + await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([ + { + id: 'test', + settings_json: '{}', + next_run_start_at: null, + current_run_ticket: null, + current_run_started_at: null, + current_run_expires_at: null, + }, + ]); + + await migrateDownOnce(knex); + + await expect( + knex('backstage_backend_tasks__tasks').insert({ + id: 'test', + settings_json: '{}', + next_run_start_at: knex.raw('null'), + }), + ).rejects.toEqual(expect.anything()); + + await knex.destroy(); + }, + ); }); diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts index 77965632e9..eca899098f 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts @@ -345,6 +345,7 @@ describe('PluginTaskManagerImpl', () => { expect(parseDuration({ milliseconds: 5000 })).toEqual('PT5S'); expect(parseDuration(Duration.fromMillis(5000))).toEqual('PT5S'); expect(parseDuration({ cron: '1 * * * *' })).toEqual('1 * * * *'); + expect(parseDuration({ trigger: 'manual' })).toEqual('manual'); }); }); }); diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts index b1647f4edf..0ccc77a6c1 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts @@ -152,6 +152,9 @@ export function parseDuration( if ('cron' in frequency) { return frequency.cron; } + if ('trigger' in frequency) { + return frequency.trigger; + } const parsed = Duration.isDuration(frequency) ? frequency diff --git a/packages/backend-tasks/src/tasks/TaskWorker.test.ts b/packages/backend-tasks/src/tasks/TaskWorker.test.ts index 10ebfde5b4..829d7754f8 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.test.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.test.ts @@ -503,4 +503,32 @@ describe('TaskWorker', () => { await knex.destroy(); }, ); + + it.each(databases.eachSupportedId())( + 'next_run_start_at is not set for manually-triggered tasks, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn( + async () => new Promise(resolve => setTimeout(resolve, 50)), + ); + + const initialSettings: TaskSettingsV2 = { + version: 2, + cadence: 'manual', + timeoutAfterDuration: 'PT1M', + }; + + const worker = new TaskWorker('task99', fn, knex, logger); + await worker.persistTask(initialSettings); + await worker.tryClaimTask('ticket', initialSettings); + await worker.tryReleaseTask('ticket', initialSettings); + + const row = (await knex(DB_TASKS_TABLE))[0]; + expect(row.next_run_start_at).toBeNull(); + + await knex.destroy(); + }, + ); }); diff --git a/packages/backend-tasks/src/tasks/TaskWorker.ts b/packages/backend-tasks/src/tasks/TaskWorker.ts index 57cbf2203e..a50b988522 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.ts @@ -52,8 +52,8 @@ export class TaskWorker { ); let workCheckFrequency = this.workCheckFrequency; - const isCron = !settings?.cadence.startsWith('P'); - if (!isCron) { + const isDuration = settings?.cadence.startsWith('P'); + if (isDuration) { const cadence = Duration.fromISO(settings.cadence); if (cadence < workCheckFrequency) { workCheckFrequency = cadence; @@ -174,7 +174,9 @@ export class TaskWorker { // read it back again. taskSettingsV2Schema.parse(settings); - const isCron = !settings?.cadence.startsWith('P'); + const isManual = settings?.cadence === 'manual'; + const isDuration = settings?.cadence.startsWith('P'); + const isCron = !isManual && !isDuration; let startAt: Knex.Raw | undefined; let nextStartAt: Knex.Raw | undefined; @@ -193,6 +195,9 @@ export class TaskWorker { nextStartAt = this.nextRunAtRaw(time); startAt ||= nextStartAt; + } else if (isManual) { + nextStartAt = this.knex.raw('null'); + startAt ||= nextStartAt; } else { startAt ||= this.knex.fn.now(); nextStartAt = nowPlus(Duration.fromISO(settings.cadence), this.knex); @@ -316,7 +321,9 @@ export class TaskWorker { ticket: string, settings: TaskSettingsV2, ): Promise { - const isCron = !settings?.cadence.startsWith('P'); + const isManual = settings?.cadence === 'manual'; + const isDuration = settings?.cadence.startsWith('P'); + const isCron = !isManual && !isDuration; let nextRun: Knex.Raw; if (isCron) { @@ -324,6 +331,8 @@ export class TaskWorker { this.logger.debug(`task: ${this.taskId} will next occur around ${time}`); nextRun = this.nextRunAtRaw(time); + } else if (isManual) { + nextRun = this.knex.raw('null'); } else { const dt = Duration.fromISO(settings.cadence).as('seconds'); this.logger.debug( diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts index e94d06d5c3..85099d82db 100644 --- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts +++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts @@ -32,14 +32,20 @@ function readDuration(config: Config, key: string): HumanDuration { return readDurationFromConfig(config, { key }); } -function readCronOrDuration( +function readFrequency( config: Config, key: string, -): { cron: string } | Duration | HumanDuration { +): { cron: string } | Duration | HumanDuration | { trigger: 'manual' } { const value = config.get(key); if (typeof value === 'object' && (value as { cron?: string }).cron) { return value as { cron: string }; } + if ( + typeof value === 'object' && + (value as { trigger?: string }).trigger === 'manual' + ) { + return { trigger: 'manual' }; + } return readDuration(config, key); } @@ -56,7 +62,7 @@ function readCronOrDuration( export function readTaskScheduleDefinitionFromConfig( config: Config, ): TaskScheduleDefinition { - const frequency = readCronOrDuration(config, 'frequency'); + const frequency = readFrequency(config, 'frequency'); const timeout = readDuration(config, 'timeout'); const initialDelay = config.has('initialDelay') diff --git a/packages/backend-tasks/src/tasks/types.ts b/packages/backend-tasks/src/tasks/types.ts index e6a31873be..a0bdbbaa64 100644 --- a/packages/backend-tasks/src/tasks/types.ts +++ b/packages/backend-tasks/src/tasks/types.ts @@ -100,7 +100,12 @@ export interface TaskScheduleDefinition { cron: string; } | Duration - | HumanDuration; + | HumanDuration + /** + * This task will only run when manually triggered with the `triggerTask` method; no automatic + * scheduling. This is useful for locking of global tasks that should not be run concurrently. + */ + | { trigger: 'manual' }; /** * The maximum amount of time that a single task invocation can take, before @@ -373,6 +378,10 @@ function isValidCronFormat(c: string | undefined): boolean { } } +function isValidTrigger(t: string): boolean { + return t === 'manual'; +} + export const taskSettingsV1Schema = z.object({ version: z.literal(1), initialDelayDuration: z @@ -401,6 +410,11 @@ export const taskSettingsV2Schema = z.object({ cadence: z .string() .refine(isValidCronFormat, { message: 'Invalid cron' }) + .or( + z.string().refine(isValidTrigger, { + message: "Invalid trigger, expecting 'manual'", + }), + ) .or( z.string().refine(isValidOptionalDurationString, { message: 'Invalid duration, expecting ISO Period', diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 435d1f8abc..2b11e31fb9 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,130 @@ # @backstage/backend-test-utils +## 0.5.0 + +### Minor Changes + +- 861f162: **BREAKING**: Removed these deprecated helpers: + + - `setupRequestMockHandlers` is removed; use `registerMswTestHooks` instead. + - `MockDirectoryOptions` is removed; use `CreateMockDirectoryOptions` instead. + + Stopped exporting the deprecated and internal `isDockerDisabledForTests` helper. + + Removed `get` method from `ServiceFactoryTester` which is replaced by `getSubject` + +### Patch Changes + +- 8b13183: Internal updates to support latest version of `BackendFeauture`s from `@backstage/backend-plugin-api`. +- b63d378: Update internal imports +- 7c5f3b0: Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation. +- 4e79d19: The default services for `startTestBackend` and `ServiceFactoryTester` now includes the Root Health Service. +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-app-api@0.9.0 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + +## 0.4.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-defaults@0.4.2-next.3 + - @backstage/backend-app-api@0.8.1-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.4.5-next.2 + +### Patch Changes + +- 8b13183: Internal updates to support latest version of `BackendFeauture`s from `@backstage/backend-plugin-api`. +- 7c5f3b0: Update the `ServiceFactoryTester` to be able to test services that enables multi implementation installation. +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.2 + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-app-api@0.8.1-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-app-api@0.8.1-next.1 + - @backstage/backend-defaults@0.4.2-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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..109ddbece3 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -19,12 +19,10 @@ import { CacheService } from '@backstage/backend-plugin-api'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import { EventsService } from '@backstage/plugin-events-node'; -import { ExtendedHttpServer } from '@backstage/backend-app-api'; +import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter'; 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'; import Keyv from 'keyv'; import { Knex } from 'knex'; @@ -40,7 +38,6 @@ import { RootLoggerService } from '@backstage/backend-plugin-api'; import { SchedulerService } from '@backstage/backend-plugin-api'; import { ServiceFactory } from '@backstage/backend-plugin-api'; import { ServiceRef } from '@backstage/backend-plugin-api'; -import { TokenManagerService } from '@backstage/backend-plugin-api'; import { UrlReaderService } from '@backstage/backend-plugin-api'; import { UserInfoService } from '@backstage/backend-plugin-api'; @@ -55,9 +52,6 @@ export interface CreateMockDirectoryOptions { mockOsTmpDir?: boolean; } -// @public (undocumented) -export function isDockerDisabledForTests(): boolean; - // @public (undocumented) export namespace mockCredentials { export function limitedUser( @@ -143,9 +137,6 @@ export interface MockDirectoryContentOptions { shouldReadAsText?: boolean | ((path: string, buffer: Buffer) => boolean); } -// @public @deprecated (undocumented) -export type MockDirectoryOptions = CreateMockDirectoryOptions; - // @public (undocumented) export namespace mockServices { // (undocumented) @@ -156,7 +147,7 @@ export namespace mockServices { // (undocumented) export namespace auth { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -165,7 +156,7 @@ export namespace mockServices { // (undocumented) export namespace cache { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -174,7 +165,7 @@ export namespace mockServices { // (undocumented) export namespace database { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -185,7 +176,7 @@ export namespace mockServices { // (undocumented) export namespace discovery { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -194,7 +185,7 @@ export namespace mockServices { // (undocumented) export namespace events { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -206,13 +197,9 @@ export namespace mockServices { }): HttpAuthService; // (undocumented) export namespace httpAuth { - const factory: ( - options?: - | { - defaultCredentials?: BackstageCredentials | undefined; - } - | undefined, - ) => ServiceFactory; + const factory: (options?: { + defaultCredentials?: BackstageCredentials; + }) => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -221,29 +208,16 @@ export namespace mockServices { // (undocumented) export namespace httpRouter { const // (undocumented) - factory: ( - options?: HttpRouterFactoryOptions | undefined, - ) => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, ) => ServiceMock; } // (undocumented) - export function identity(): IdentityService; - // (undocumented) - export namespace identity { - const // (undocumented) - factory: () => ServiceFactory; - const // (undocumented) - mock: ( - partialImpl?: Partial | undefined, - ) => ServiceMock; - } - // (undocumented) export namespace lifecycle { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -252,7 +226,7 @@ export namespace mockServices { // (undocumented) export namespace logger { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -261,7 +235,7 @@ export namespace mockServices { // (undocumented) export namespace permissions { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -278,12 +252,12 @@ export namespace mockServices { const // (undocumented) factory: ( options?: Options | undefined, - ) => ServiceFactory; + ) => ServiceFactory; } // (undocumented) export namespace rootHealth { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -292,9 +266,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 +278,7 @@ export namespace mockServices { // (undocumented) export namespace rootLifecycle { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -320,7 +295,7 @@ export namespace mockServices { const // (undocumented) factory: ( options?: Options | undefined, - ) => ServiceFactory; + ) => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -329,27 +304,16 @@ export namespace mockServices { // (undocumented) export namespace scheduler { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, ) => ServiceMock; } // (undocumented) - export function tokenManager(): TokenManagerService; - // (undocumented) - export namespace tokenManager { - const // (undocumented) - factory: () => ServiceFactory; - const // (undocumented) - mock: ( - partialImpl?: Partial | undefined, - ) => ServiceMock; - } - // (undocumented) export namespace urlReader { const // (undocumented) - factory: () => ServiceFactory; + factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -360,7 +324,7 @@ export namespace mockServices { ): UserInfoService; // (undocumented) export namespace userInfo { - const factory: () => ServiceFactory; + const factory: () => ServiceFactory; const // (undocumented) mock: ( partialImpl?: Partial | undefined, @@ -369,25 +333,42 @@ export namespace mockServices { } // @public -export class ServiceFactoryTester { - static from( - subject: - | ServiceFactory - | (() => ServiceFactory), +export function registerMswTestHooks(worker: { + listen: (t: any) => void; + close: () => void; + resetHandlers: () => void; +}): void; + +// @public +export class ServiceFactoryTester< + TService, + TScope extends 'root' | 'plugin', + TInstances extends 'singleton' | 'multiton' = 'singleton', +> { + static from< + TService, + TScope extends 'root' | 'plugin', + TInstances extends 'singleton' | 'multiton' = 'singleton', + >( + subject: ServiceFactory, options?: ServiceFactoryTesterOptions, - ): ServiceFactoryTester; - get( - ...args: 'root' extends TScope ? [] : [pluginId?: string] - ): Promise; - getService( - service: ServiceRef, + ): ServiceFactoryTester; + getService< + TGetService, + TGetScope extends 'root' | 'plugin', + TGetInstances extends 'singleton' | 'multiton' = 'singleton', + >( + service: ServiceRef, ...args: 'root' extends TGetScope ? [] : [pluginId?: string] - ): Promise; + ): Promise; + getSubject( + ...args: 'root' extends TScope ? [] : [pluginId?: string] + ): Promise; } // @public export interface ServiceFactoryTesterOptions { - dependencies?: Array ServiceFactory)>; + dependencies?: Array; } // @public (undocumented) @@ -401,13 +382,6 @@ export type ServiceMock = { : TService[Key]; }; -// @public -export function setupRequestMockHandlers(worker: { - listen: (t: any) => void; - close: () => void; - resetHandlers: () => void; -}): void; - // @public (undocumented) export function startTestBackend( options: TestBackendOptions, @@ -432,9 +406,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 540b4f9ee4..682ec3067e 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.5.0", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend-test-utils/src/index.ts b/packages/backend-test-utils/src/index.ts index a9feacbacd..d1d80bf387 100644 --- a/packages/backend-test-utils/src/index.ts +++ b/packages/backend-test-utils/src/index.ts @@ -20,10 +20,8 @@ * @packageDocumentation */ -export * from './deprecated'; export * from './cache'; 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/MockIdentityService.ts b/packages/backend-test-utils/src/next/services/MockIdentityService.ts deleted file mode 100644 index ff424f79c2..0000000000 --- a/packages/backend-test-utils/src/next/services/MockIdentityService.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { IdentityService } from '@backstage/backend-plugin-api'; -import { - IdentityApiGetIdentityRequest, - BackstageIdentityResponse, -} from '@backstage/plugin-auth-node'; - -export class MockIdentityService implements IdentityService { - getIdentity( - _options: IdentityApiGetIdentityRequest, - ): Promise { - return Promise.resolve({ - token: 'mock-token', - identity: { - type: 'user', - userEntityRef: 'user:default/mock-user', - ownershipEntityRefs: [], - }, - }); - } -} diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts index d56179d958..d3ec8fb18d 100644 --- a/packages/backend-test-utils/src/next/services/mockServices.ts +++ b/packages/backend-test-utils/src/next/services/mockServices.ts @@ -35,12 +35,10 @@ import { BackstageUserInfo, DiscoveryService, HttpAuthService, - IdentityService, LoggerService, RootConfigService, ServiceFactory, ServiceRef, - TokenManagerService, UserInfoService, coreServices, createServiceFactory, @@ -53,7 +51,6 @@ import { import { JsonObject } from '@backstage/types'; import { MockAuthService } from './MockAuthService'; import { MockHttpAuthService } from './MockHttpAuthService'; -import { MockIdentityService } from './MockIdentityService'; import { MockRootLoggerService } from './MockRootLoggerService'; import { MockUserInfoService } from './MockUserInfoService'; import { mockCredentials } from './mockCredentials'; @@ -70,7 +67,7 @@ function createLoggerMock() { } /** @internal */ -function simpleFactory< +function simpleFactoryWithOptions< TService, TScope extends 'root' | 'plugin', TOptions extends [options?: object] = [], @@ -78,13 +75,19 @@ function simpleFactory< 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; + 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 */ @@ -119,7 +122,7 @@ function simpleMock( service: ref, deps: {}, factory: () => mock, - })(), + }), }) as ServiceMock; }; } @@ -134,7 +137,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 +151,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(), @@ -155,39 +164,6 @@ export namespace mockServices { })); } - export function tokenManager(): TokenManagerService { - return { - async getToken(): Promise<{ token: string }> { - return { token: 'mock-token' }; - }, - async authenticate(token: string): Promise { - if (token !== 'mock-token') { - throw new Error('Invalid token'); - } - }, - }; - } - export namespace tokenManager { - export const factory = simpleFactory( - coreServices.tokenManager, - tokenManager, - ); - export const mock = simpleMock(coreServices.tokenManager, () => ({ - authenticate: jest.fn(), - getToken: jest.fn(), - })); - } - - export function identity(): IdentityService { - return new MockIdentityService(); - } - export namespace identity { - export const factory = simpleFactory(coreServices.identity, identity); - export const mock = simpleMock(coreServices.identity, () => ({ - getIdentity: jest.fn(), - })); - } - export function auth(options?: { pluginId?: string; disableDefaultAuthPolicy?: boolean; @@ -198,24 +174,25 @@ export namespace mockServices { }); } export namespace auth { - export const factory = createServiceFactory({ - service: coreServices.auth, - deps: { - plugin: coreServices.pluginMetadata, - config: coreServices.rootConfig, - }, - factory({ plugin, config }) { - const disableDefaultAuthPolicy = Boolean( - config.getOptionalBoolean( - 'backend.auth.dangerouslyDisableDefaultAuthPolicy', - ), - ); - return new MockAuthService({ - pluginId: plugin.getId(), - disableDefaultAuthPolicy, - }); - }, - }); + export const factory = () => + createServiceFactory({ + service: coreServices.auth, + deps: { + plugin: coreServices.pluginMetadata, + config: coreServices.rootConfig, + }, + factory({ plugin, config }) { + const disableDefaultAuthPolicy = Boolean( + config.getOptionalBoolean( + 'backend.auth.dangerouslyDisableDefaultAuthPolicy', + ), + ); + return new MockAuthService({ + pluginId: plugin.getId(), + disableDefaultAuthPolicy, + }); + }, + }); export const mock = simpleMock(coreServices.auth, () => ({ authenticate: jest.fn(), getNoneCredentials: jest.fn(), @@ -239,7 +216,7 @@ export namespace mockServices { ); } export namespace discovery { - export const factory = discoveryServiceFactory; + export const factory = () => discoveryServiceFactory; export const mock = simpleMock(coreServices.discovery, () => ({ getBaseUrl: jest.fn(), getExternalBaseUrl: jest.fn(), @@ -277,8 +254,10 @@ export namespace mockServices { * the default mock user principal. This behavior can be configured with the * `defaultCredentials` option. */ - export const factory = createServiceFactory( - (options?: { defaultCredentials?: BackstageCredentials }) => ({ + export const factory = (options?: { + defaultCredentials?: BackstageCredentials; + }) => + createServiceFactory({ service: coreServices.httpAuth, deps: { plugin: coreServices.pluginMetadata }, factory: ({ plugin }) => @@ -286,8 +265,7 @@ export namespace mockServices { plugin.getId(), options?.defaultCredentials ?? mockCredentials.user(), ), - }), - ); + }); export const mock = simpleMock(coreServices.httpAuth, () => ({ credentials: jest.fn(), issueUserCookie: jest.fn(), @@ -313,13 +291,14 @@ export namespace mockServices { * By default it extracts the user's entity ref from a user principal and * returns that as the only ownership entity ref. */ - export const factory = createServiceFactory({ - service: coreServices.userInfo, - deps: {}, - factory() { - return new MockUserInfoService(); - }, - }); + export const factory = () => + createServiceFactory({ + service: coreServices.userInfo, + deps: {}, + factory() { + return new MockUserInfoService(); + }, + }); export const mock = simpleMock(coreServices.userInfo, () => ({ getUserInfo: jest.fn(), })); @@ -329,7 +308,7 @@ export namespace mockServices { // some may need a bit more refactoring for it to be simpler to // re-implement functioning mock versions here. export namespace cache { - export const factory = cacheServiceFactory; + export const factory = () => cacheServiceFactory; export const mock = simpleMock(coreServices.cache, () => ({ delete: jest.fn(), get: jest.fn(), @@ -339,14 +318,14 @@ export namespace mockServices { } export namespace database { - export const factory = databaseServiceFactory; + export const factory = () => databaseServiceFactory; export const mock = simpleMock(coreServices.database, () => ({ getClient: jest.fn(), })); } export namespace rootHealth { - export const factory = rootHealthServiceFactory; + export const factory = () => rootHealthServiceFactory; export const mock = simpleMock(coreServices.rootHealth, () => ({ getLiveness: jest.fn(), getReadiness: jest.fn(), @@ -354,7 +333,7 @@ export namespace mockServices { } export namespace httpRouter { - export const factory = httpRouterServiceFactory; + export const factory = () => httpRouterServiceFactory; export const mock = simpleMock(coreServices.httpRouter, () => ({ use: jest.fn(), addAuthPolicy: jest.fn(), @@ -362,14 +341,14 @@ export namespace mockServices { } export namespace rootHttpRouter { - export const factory = rootHttpRouterServiceFactory; + export const factory = () => rootHttpRouterServiceFactory; export const mock = simpleMock(coreServices.rootHttpRouter, () => ({ use: jest.fn(), })); } export namespace lifecycle { - export const factory = lifecycleServiceFactory; + export const factory = () => lifecycleServiceFactory; export const mock = simpleMock(coreServices.lifecycle, () => ({ addShutdownHook: jest.fn(), addStartupHook: jest.fn(), @@ -377,15 +356,14 @@ export namespace mockServices { } export namespace logger { - export const factory = loggerServiceFactory; - + export const factory = () => loggerServiceFactory; export const mock = simpleMock(coreServices.logger, () => createLoggerMock(), ); } export namespace permissions { - export const factory = permissionsServiceFactory; + export const factory = () => permissionsServiceFactory; export const mock = simpleMock(coreServices.permissions, () => ({ authorize: jest.fn(), authorizeConditional: jest.fn(), @@ -393,7 +371,7 @@ export namespace mockServices { } export namespace rootLifecycle { - export const factory = rootLifecycleServiceFactory; + export const factory = () => rootLifecycleServiceFactory; export const mock = simpleMock(coreServices.rootLifecycle, () => ({ addShutdownHook: jest.fn(), addStartupHook: jest.fn(), @@ -401,7 +379,7 @@ export namespace mockServices { } export namespace scheduler { - export const factory = schedulerServiceFactory; + export const factory = () => schedulerServiceFactory; export const mock = simpleMock(coreServices.scheduler, () => ({ createScheduledTaskRunner: jest.fn(), getScheduledTasks: jest.fn(), @@ -411,7 +389,7 @@ export namespace mockServices { } export namespace urlReader { - export const factory = urlReaderServiceFactory; + export const factory = () => urlReaderServiceFactory; export const mock = simpleMock(coreServices.urlReader, () => ({ readTree: jest.fn(), readUrl: jest.fn(), @@ -420,7 +398,7 @@ export namespace mockServices { } export namespace events { - export const factory = eventsServiceFactory; + export const factory = () => eventsServiceFactory; export const mock = simpleMock(eventsServiceRef, () => ({ publish: jest.fn(), subscribe: 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..6690f76e76 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 () => { @@ -93,10 +93,10 @@ describe('ServiceFactoryTester', () => { deps: { root: rootServiceRef, plugin: pluginServiceRef }, factory: async ({ root, plugin }) => `${root}, ${plugin}`, }), - { dependencies: [rootFactory, pluginFactory()] }, + { 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 () => { @@ -106,14 +106,14 @@ describe('ServiceFactoryTester', () => { deps: { shared: sharedPluginServiceRef, plugin: pluginServiceRef }, factory: async ({ shared, plugin }) => `${shared}, ${plugin}`, }), - { dependencies: [sharedPluginFactory(), pluginFactory] }, + { 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..83a6a42307 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; } /** @@ -43,8 +43,12 @@ export interface ServiceFactoryTesterOptions { * * @public */ -export class ServiceFactoryTester { - readonly #subject: ServiceRef; +export class ServiceFactoryTester< + TService, + TScope extends 'root' | 'plugin', + TInstances extends 'singleton' | 'multiton' = 'singleton', +> { + readonly #subject: ServiceRef; readonly #registry: ServiceRegistry; /** @@ -54,25 +58,24 @@ export class ServiceFactoryTester { * @param options - Additional options * @returns A new tester instance for the provided subject. */ - static from( - subject: - | ServiceFactory - | (() => ServiceFactory), + static from< + TService, + TScope extends 'root' | 'plugin', + TInstances extends 'singleton' | 'multiton' = 'singleton', + >( + subject: ServiceFactory, options?: ServiceFactoryTesterOptions, - ) { - const subjectFactory = typeof subject === 'function' ? subject() : subject; + ): ServiceFactoryTester { 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( - subject: ServiceRef, + subject: ServiceRef, registry: ServiceRegistry, ) { this.#subject = subject; @@ -89,11 +92,12 @@ export class ServiceFactoryTester { * * By default the plugin ID 'test' is used. */ - async get( + async getSubject( ...args: 'root' extends TScope ? [] : [pluginId?: string] - ): Promise { + ): Promise { const [pluginId] = args; - return this.#registry.get(this.#subject, pluginId ?? 'test')!; + const instance = this.#registry.get(this.#subject, pluginId ?? 'test')!; + return instance; } /** @@ -103,10 +107,14 @@ export class ServiceFactoryTester { * * A plugin ID can optionally be provided for plugin scoped services, otherwise the plugin ID 'test' is used. */ - async getService( - service: ServiceRef, + async getService< + TGetService, + TGetScope extends 'root' | 'plugin', + TGetInstances extends 'singleton' | 'multiton' = 'singleton', + >( + service: ServiceRef, ...args: 'root' extends TGetScope ? [] : [pluginId?: string] - ): Promise { + ): Promise { const [pluginId] = args; const instance = await this.#registry.get(service, pluginId ?? 'test'); if (instance === undefined) { 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..7bf3887c93 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts @@ -71,34 +71,34 @@ describe('TestBackend', () => { features: [ // @ts-expect-error [extensionPoint1, { a: 'a' }], - createServiceFactory(() => ({ + createServiceFactory({ service: serviceRef, deps: {}, // @ts-expect-error factory: async () => ({ a: 'a' }), - })), - createServiceFactory(() => ({ + }), + createServiceFactory({ service: serviceRef, deps: {}, factory: async () => ({ a: 'a', b: 'b' }), - })), - createServiceFactory(() => ({ + }), + createServiceFactory({ service: serviceRef, deps: {}, // @ts-expect-error factory: async () => ({ c: 'c' }), - })), - createServiceFactory(() => ({ + }), + createServiceFactory({ service: serviceRef, deps: {}, // @ts-expect-error factory: async () => ({ a: 'a', c: 'c' }), - })), - createServiceFactory(() => ({ + }), + createServiceFactory({ service: serviceRef, deps: {}, factory: async () => ({ a: 'a', b: 'b', c: 'c' }), - })), + }), ], extensionPoints: [ // @ts-expect-error @@ -144,7 +144,7 @@ describe('TestBackend', () => { }); await startTestBackend({ - features: [testModule, sf()], + features: [testModule, sf], }); expect(testFn).toHaveBeenCalledWith('winning'); @@ -198,13 +198,12 @@ describe('TestBackend', () => { rootLifecycle: coreServices.rootLifecycle, rootLogger: coreServices.rootLogger, scheduler: coreServices.scheduler, - tokenManager: coreServices.tokenManager, urlReader: coreServices.urlReader, auth: coreServices.auth, httpAuth: coreServices.httpAuth, }, async init(deps) { - expect(Object.keys(deps)).toHaveLength(17); + expect(Object.keys(deps)).toHaveLength(16); expect(Object.values(deps)).not.toContain(undefined); }, }); @@ -242,6 +241,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..96bf0f7dc1 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -14,15 +14,7 @@ * limitations under the License. */ -import { - Backend, - createSpecializedBackend, - MiddlewareFactory, - createHttpServer, - ExtendedHttpServer, - HostDiscovery, - DefaultRootHttpRouter, -} from '@backstage/backend-app-api'; +import { Backend, createSpecializedBackend } from '@backstage/backend-app-api'; import { createServiceFactory, BackendFeature, @@ -36,7 +28,18 @@ import { ConfigReader } from '@backstage/config'; 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 { + InternalBackendFeature, + InternalBackendRegistrations, +} from '@backstage/backend-plugin-api/src/wiring/types'; +import { + DefaultRootHttpRouter, + ExtendedHttpServer, + MiddlewareFactory, + createHealthRouter, + createHttpServer, +} from '@backstage/backend-defaults/rootHttpRouter'; +import { HostDiscovery } from '@backstage/backend-defaults/discovery'; /** @public */ export interface TestBackendOptions { @@ -48,11 +51,7 @@ export interface TestBackendOptions { ]; }, ]; - features?: Array< - | BackendFeature - | (() => BackendFeature) - | Promise<{ default: BackendFeature | (() => BackendFeature) }> - >; + features?: Array>; } /** @public */ @@ -73,14 +72,13 @@ export const defaultServiceFactories = [ mockServices.database.factory(), mockServices.httpAuth.factory(), mockServices.httpRouter.factory(), - mockServices.identity.factory(), mockServices.lifecycle.factory(), mockServices.logger.factory(), mockServices.permissions.factory(), + mockServices.rootHealth.factory(), mockServices.rootLifecycle.factory(), mockServices.rootLogger.factory(), mockServices.scheduler.factory(), - mockServices.tokenManager.factory(), mockServices.userInfo.factory(), mockServices.urlReader.factory(), mockServices.events.factory(), @@ -96,7 +94,7 @@ function createPluginsForOrphanModules(features: Array) { const modulePluginIds = new Set(); for (const feature of features) { - if (isInternalBackendFeature(feature)) { + if (isInternalBackendRegistrations(feature)) { const registrations = feature.getRegistrations(); for (const registration of registrations) { if (registration.type === 'plugin') { @@ -139,18 +137,7 @@ function createExtensionPointTestModules( } const registrations = features.flatMap(feature => { - if (feature.$$type !== '@backstage/BackendFeature') { - throw new Error( - `Failed to add feature, invalid type '${feature.$$type}'`, - ); - } - - if (isInternalBackendFeature(feature)) { - if (feature.version !== 'v1') { - throw new Error( - `Failed to add feature, invalid version '${feature.version}'`, - ); - } + if (isInternalBackendRegistrations(feature)) { return feature.getRegistrations(); } return []; @@ -255,15 +242,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()); @@ -360,10 +350,28 @@ function registerTestHooks() { registerTestHooks(); -function isInternalBackendFeature( +function toInternalBackendFeature( feature: BackendFeature, -): feature is InternalBackendFeature { - return ( - typeof (feature as InternalBackendFeature).getRegistrations === 'function' - ); +): InternalBackendFeature { + if (feature.$$type !== '@backstage/BackendFeature') { + throw new Error(`Invalid BackendFeature, bad type '${feature.$$type}'`); + } + const internal = feature as InternalBackendFeature; + if (internal.version !== 'v1') { + throw new Error( + `Invalid BackendFeature, bad version '${internal.version}'`, + ); + } + return internal; +} + +function isInternalBackendRegistrations( + feature: BackendFeature, +): feature is InternalBackendRegistrations { + const internal = toInternalBackendFeature(feature); + if (internal.featureType === 'registrations') { + return true; + } + // Backwards compatibility for v1 registrations that use duck typing + return 'getRegistrations' in internal; } 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..8c856f53ca 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,270 @@ # example-backend +## 0.0.29 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.41 + - @backstage/plugin-search-backend-node@1.3.0 + - @backstage/plugin-scaffolder-backend@1.24.0 + - @backstage/plugin-techdocs-backend@1.10.10 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-backend-module-techdocs@0.2.0 + - @backstage/plugin-search-backend-module-explore@0.2.0 + - @backstage/plugin-notifications-backend@0.3.4 + - @backstage/plugin-kubernetes-backend@0.18.4 + - @backstage/plugin-permission-backend@0.5.47 + - @backstage/plugin-devtools-backend@0.3.9 + - @backstage/plugin-signals-backend@0.1.9 + - @backstage/plugin-proxy-backend@0.5.4 + - @backstage/plugin-auth-backend@0.22.10 + - @backstage/plugin-app-backend@0.3.72 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.3.0 + - @backstage/plugin-search-backend-module-catalog@0.2.0 + - @backstage/plugin-search-backend@1.5.15 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.9 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20 + +## 0.0.29-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.3 + - @backstage/plugin-notifications-backend@0.3.4-next.3 + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-techdocs-backend@1.10.10-next.3 + - @backstage/backend-defaults@0.4.2-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-scaffolder-backend@1.23.1-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/plugin-app-backend@0.3.72-next.3 + - @backstage/plugin-auth-backend@0.22.10-next.3 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.3 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.3 + - @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.3 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.3 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.3 + - @backstage/plugin-devtools-backend@0.3.9-next.3 + - @backstage/plugin-kubernetes-backend@0.18.4-next.3 + - @backstage/plugin-permission-backend@0.5.47-next.3 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + - @backstage/plugin-proxy-backend@0.5.4-next.3 + - @backstage/plugin-search-backend@1.5.15-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.3 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.3 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.3 + - @backstage/plugin-search-backend-node@1.2.28-next.3 + - @backstage/plugin-signals-backend@0.1.9-next.3 + +## 0.0.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.4.2-next.2 + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-backend@1.23.1-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.2 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.2 + - @backstage/plugin-notifications-backend@0.3.4-next.2 + - @backstage/plugin-kubernetes-backend@0.18.4-next.2 + - @backstage/plugin-permission-backend@0.5.47-next.2 + - @backstage/plugin-devtools-backend@0.3.9-next.2 + - @backstage/plugin-techdocs-backend@1.10.10-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/plugin-signals-backend@0.1.9-next.2 + - @backstage/plugin-proxy-backend@0.5.4-next.2 + - @backstage/plugin-auth-backend@0.22.10-next.2 + - @backstage/plugin-app-backend@0.3.72-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-search-backend-node@1.2.28-next.2 + - @backstage/plugin-search-backend@1.5.15-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.2 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.2 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.2 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.2 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.2 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.2 + - @backstage/catalog-model@1.5.0 + +## 0.0.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.20-next.1 + - @backstage/plugin-techdocs-backend@1.10.10-next.1 + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.29-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-scaffolder-backend@1.23.1-next.1 + - @backstage/plugin-auth-backend@0.22.10-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.28-next.1 + - @backstage/backend-defaults@0.4.2-next.1 + - @backstage/plugin-app-backend@0.3.72-next.1 + - @backstage/plugin-devtools-backend@0.3.9-next.1 + - @backstage/plugin-proxy-backend@0.5.4-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.10-next.1 + - @backstage/plugin-kubernetes-backend@0.18.4-next.1 + - @backstage/plugin-permission-backend@0.5.47-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.20-next.1 + - @backstage/plugin-search-backend@1.5.15-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.1 + - @backstage/plugin-search-backend-node@1.2.28-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.41-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.9-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.6-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.1 + - @backstage/plugin-notifications-backend@0.3.4-next.1 + - @backstage/plugin-signals-backend@0.1.9-next.1 + +## 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/Dockerfile b/packages/backend/Dockerfile index e0e6ab8a31..b64a26afad 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -35,7 +35,7 @@ USER node # This switches many Node.js dependencies to production mode. -ENV NODE_ENV production +ENV NODE_ENV=production # Copy over Yarn 3 configuration, release, and plugins COPY --chown=node:node .yarn ./.yarn diff --git a/packages/backend/package.json b/packages/backend/package.json index f81701a77d..570a545741 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", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,6 @@ "dependencies": { "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/plugin-app-backend": "workspace:^", "@backstage/plugin-auth-backend": "workspace:^", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 1174b7bf6d..952f3315c4 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -15,9 +15,21 @@ */ import { createBackend } from '@backstage/backend-defaults'; +import { createBackendFeatureLoader } from '@backstage/backend-plugin-api'; const backend = createBackend(); +// An example of how to group together and load multiple features. You can also +// access root-scoped services by adding `deps`. +const searchLoader = createBackendFeatureLoader({ + *loader() { + yield import('@backstage/plugin-search-backend/alpha'); + yield import('@backstage/plugin-search-backend-module-catalog/alpha'); + yield import('@backstage/plugin-search-backend-module-explore/alpha'); + yield import('@backstage/plugin-search-backend-module-techdocs/alpha'); + }, +}); + backend.add(import('@backstage/plugin-auth-backend')); backend.add(import('./authModuleGithubProvider')); backend.add(import('@backstage/plugin-auth-backend-module-guest-provider')); @@ -36,13 +48,10 @@ backend.add(import('@backstage/plugin-permission-backend/alpha')); backend.add(import('@backstage/plugin-proxy-backend/alpha')); backend.add(import('@backstage/plugin-scaffolder-backend/alpha')); backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); -backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha')); -backend.add(import('@backstage/plugin-search-backend-module-explore/alpha')); -backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha')); backend.add( import('@backstage/plugin-catalog-backend-module-backstage-openapi'), ); -backend.add(import('@backstage/plugin-search-backend/alpha')); +backend.add(searchLoader); backend.add(import('@backstage/plugin-techdocs-backend/alpha')); backend.add(import('@backstage/plugin-signals-backend')); backend.add(import('@backstage/plugin-notifications-backend')); diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index da7b16dce1..9ed8a50b91 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/catalog-client +## 1.6.6 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + +## 1.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/errors@1.2.4 + ## 1.6.5 ### Patch Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index c9e7f25723..29c1d3049c 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "1.6.5", + "version": "1.6.6", "description": "An isomorphic client for the catalog backend", "backstage": { "role": "common-library" diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index f47465a03e..1242c48f31 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/catalog-model +## 1.6.0 + +### Minor Changes + +- 34fa803: Introduce an optional spec.type attribute on the Domain and System entity kinds + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.6.0-next.0 + +### Minor Changes + +- 34fa803: Introduce an optional spec.type attribute on the Domain and System entity kinds + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 1.5.0 ### Minor Changes diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index 2d91fa44a5..de941e2502 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -123,6 +123,7 @@ interface DomainEntityV1alpha1 extends Entity { spec: { owner: string; subdomainOf?: string; + type?: string; }; } export { DomainEntityV1alpha1 as DomainEntity }; @@ -454,6 +455,7 @@ interface SystemEntityV1alpha1 extends Entity { spec: { owner: string; domain?: string; + type?: string; }; } export { SystemEntityV1alpha1 as SystemEntity }; diff --git a/packages/catalog-model/examples/domains/artists-domain.yaml b/packages/catalog-model/examples/domains/artists-domain.yaml index 598f8500aa..02370d9a14 100644 --- a/packages/catalog-model/examples/domains/artists-domain.yaml +++ b/packages/catalog-model/examples/domains/artists-domain.yaml @@ -13,3 +13,4 @@ metadata: spec: owner: team-a subdomainOf: audio + type: product-group diff --git a/packages/catalog-model/examples/domains/playback-domain.yaml b/packages/catalog-model/examples/domains/playback-domain.yaml index 58b5afe56e..e058ebf0a2 100644 --- a/packages/catalog-model/examples/domains/playback-domain.yaml +++ b/packages/catalog-model/examples/domains/playback-domain.yaml @@ -6,3 +6,4 @@ metadata: spec: owner: user:frank.tiernan subdomainOf: audio + type: product-group diff --git a/packages/catalog-model/examples/systems/artist-engagement-portal-system.yaml b/packages/catalog-model/examples/systems/artist-engagement-portal-system.yaml index 8de3c00880..17fe21ba69 100644 --- a/packages/catalog-model/examples/systems/artist-engagement-portal-system.yaml +++ b/packages/catalog-model/examples/systems/artist-engagement-portal-system.yaml @@ -8,3 +8,4 @@ metadata: spec: owner: team-a domain: artists + type: service diff --git a/packages/catalog-model/examples/systems/audio-playback-system.yaml b/packages/catalog-model/examples/systems/audio-playback-system.yaml index 7430ae2ff5..f1edb7f17d 100644 --- a/packages/catalog-model/examples/systems/audio-playback-system.yaml +++ b/packages/catalog-model/examples/systems/audio-playback-system.yaml @@ -6,3 +6,4 @@ metadata: spec: owner: team-c domain: playback + type: feature-set diff --git a/packages/catalog-model/examples/systems/podcast-system.yaml b/packages/catalog-model/examples/systems/podcast-system.yaml index 47a2f7ac9f..77f53925c1 100644 --- a/packages/catalog-model/examples/systems/podcast-system.yaml +++ b/packages/catalog-model/examples/systems/podcast-system.yaml @@ -6,3 +6,4 @@ metadata: spec: owner: team-b domain: playback + type: feature-set diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 5a60531cb2..150317aee1 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "1.5.0", + "version": "1.6.0", "description": "Types and validators that help describe the model of a Backstage Catalog", "backstage": { "role": "common-library" diff --git a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts index 3c1e7f0a6d..61408b7e80 100644 --- a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.test.ts @@ -32,6 +32,7 @@ describe('DomainV1alpha1Validator', () => { spec: { owner: 'me', subdomainOf: 'parent-domain', + type: 'domain-type', }, }; }); @@ -84,4 +85,19 @@ describe('DomainV1alpha1Validator', () => { (entity as any).spec.subdomainOf = ''; await expect(validator.check(entity)).rejects.toThrow(/subdomainOf/); }); + + it('accepts missing type', async () => { + delete (entity as any).spec.type; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('rejects wrong type', async () => { + (entity as any).spec.type = 7; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); + + it('rejects empty type', async () => { + (entity as any).spec.type = ''; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); }); diff --git a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts index 1fe3dfb522..765329abb4 100644 --- a/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/DomainEntityV1alpha1.ts @@ -33,6 +33,7 @@ export interface DomainEntityV1alpha1 extends Entity { spec: { owner: string; subdomainOf?: string; + type?: string; }; } diff --git a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts index fd78633a89..828fe7a648 100644 --- a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts +++ b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.test.ts @@ -32,6 +32,7 @@ describe('SystemV1alpha1Validator', () => { spec: { owner: 'me', domain: 'domain', + type: 'system-type', }, }; }); @@ -84,4 +85,19 @@ describe('SystemV1alpha1Validator', () => { (entity as any).spec.domain = ''; await expect(validator.check(entity)).rejects.toThrow(/domain/); }); + + it('accepts missing type', async () => { + delete (entity as any).spec.type; + await expect(validator.check(entity)).resolves.toBe(true); + }); + + it('rejects wrong type', async () => { + (entity as any).spec.type = 7; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); + + it('rejects empty type', async () => { + (entity as any).spec.type = ''; + await expect(validator.check(entity)).rejects.toThrow(/type/); + }); }); diff --git a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts index 88d405ec5b..37ecee8f6c 100644 --- a/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts +++ b/packages/catalog-model/src/kinds/SystemEntityV1alpha1.ts @@ -33,6 +33,7 @@ export interface SystemEntityV1alpha1 extends Entity { spec: { owner: string; domain?: string; + type?: string; }; } diff --git a/packages/catalog-model/src/schema/kinds/Domain.v1alpha1.schema.json b/packages/catalog-model/src/schema/kinds/Domain.v1alpha1.schema.json index 7d0d7e25ff..e4a6c29dd1 100644 --- a/packages/catalog-model/src/schema/kinds/Domain.v1alpha1.schema.json +++ b/packages/catalog-model/src/schema/kinds/Domain.v1alpha1.schema.json @@ -12,7 +12,8 @@ }, "spec": { "owner": "artist-relations-team", - "subdomainOf": "audio" + "subdomainOf": "audio", + "type": "product-group" } } ], @@ -45,6 +46,12 @@ "description": "An entity reference to another domain of which the domain is a part.", "examples": ["audio"], "minLength": 1 + }, + "type": { + "type": "string", + "description": "The type of domain. There is currently no enforced set of values for this field, so it is left up to the adopting organization to choose a nomenclature that matches their catalog hierarchy.", + "examples": ["product-group", "bundle"], + "minLength": 1 } } } diff --git a/packages/catalog-model/src/schema/kinds/System.v1alpha1.schema.json b/packages/catalog-model/src/schema/kinds/System.v1alpha1.schema.json index 2cdbc37076..f91fe751dc 100644 --- a/packages/catalog-model/src/schema/kinds/System.v1alpha1.schema.json +++ b/packages/catalog-model/src/schema/kinds/System.v1alpha1.schema.json @@ -12,7 +12,8 @@ }, "spec": { "owner": "artist-relations-team", - "domain": "artists" + "domain": "artists", + "type": "service" } } ], @@ -45,6 +46,12 @@ "description": "An entity reference to the domain that the system belongs to.", "examples": ["artists"], "minLength": 1 + }, + "type": { + "type": "string", + "description": "The type of system. There is currently no enforced set of values for this field, so it is left up to the adopting organization to choose a nomenclature that matches their catalog hierarchy.", + "examples": ["product", "service", "feature-set"], + "minLength": 1 } } } 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..ec823ef23e 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,196 @@ # @backstage/cli +## 0.27.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 +- b2d97fd: Fixing loading of additional config files with new `ConfigSources` +- fbc7819: Use ES2022 in CLI bundler +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 6d898d8: Switched the `process` polyfill to use `require.resolve` for greater compatability. +- 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 +- e6e7d86: Switched the target from `'ES2022'` to `'es2022'` for better compatibility with older versions of `swc`. +- 2ced236: Updated dependency `@module-federation/enhanced` to `0.3.1` +- 0eedec3: Add support for dynamic plugins via the EXPERIMENTAL_MODULE_FEDERATION environment variable when running `yarn start`. +- adabb40: New command now supports setting package license +- dc4fb4f: Fix for `repo build --all` not properly detecting the experimental public entry point. +- Updated dependencies + - @backstage/config-loader@1.9.0 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.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.27.0-next.4 + +### Patch Changes + +- 6d898d8: Switched the `process` polyfill to use `require.resolve` for greater compatability. +- 2ced236: Updated dependency `@module-federation/enhanced` to `0.3.1` +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.14.0-next.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 0.27.0-next.3 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/config-loader@1.9.0-next.2 + - @backstage/cli-node@0.2.7 + - @backstage/integration@1.14.0-next.0 + - @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.27.0-next.2 + +### Patch Changes + +- b2d97fd: Fixing loading of additional config files with new `ConfigSources` +- adabb40: New command now supports setting package license +- Updated dependencies + - @backstage/catalog-model@1.5.0 + - @backstage/cli-common@0.1.14 + - @backstage/cli-node@0.2.7 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.1 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.8 + - @backstage/integration@1.14.0-next.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## 0.27.0-next.1 + +### Patch Changes + +- e6e7d86: Switched the target from `'ES2022'` to `'es2022'` for better compatibility with older versions of `swc`. +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/integration@1.14.0-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.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..980d305c12 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...] @@ -194,6 +193,7 @@ Options: --scope --npm-registry --baseVersion + --license --no-private -h, --help ``` @@ -607,16 +607,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..c3f70bcbf4 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", "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.3.5", "@octokit/graphql": "^5.0.0", "@octokit/graphql-schema": "^13.7.0", "@octokit/oauth-app": "^4.2.0", @@ -120,7 +121,7 @@ "lodash": "^4.17.21", "mini-css-extract-plugin": "^2.4.2", "minimatch": "^9.0.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "node-libs-browser": "^2.2.1", "npm-packlist": "^5.0.0", "ora": "^5.3.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/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index 7fcb0aef1a..f8d7e54e97 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -19,16 +19,16 @@ import { promisify } from 'util'; import chalk from 'chalk'; import inquirer, { Answers, Question } from 'inquirer'; import { exec as execCb } from 'child_process'; -import { resolve as resolvePath, join as joinPath } from 'path'; +import { join as joinPath, resolve as resolvePath } from 'path'; import camelCase from 'lodash/camelCase'; import upperFirst from 'lodash/upperFirst'; import os from 'os'; import { OptionValues } from 'commander'; import { assertError } from '@backstage/errors'; import { - parseOwnerIds, addCodeownersEntry, getCodeownersFilePath, + parseOwnerIds, } from '../../lib/codeowners'; import { paths } from '../../lib/paths'; import { Task, templatingTask } from '../../lib/tasks'; @@ -266,6 +266,7 @@ export default async (opts: OptionValues) => { const { version: pluginVersion } = isMonoRepo ? await fs.readJson(paths.resolveTargetRoot('lerna.json')) : { version: '0.1.0' }; + const license = opts.license ?? 'Apache-2.0'; let lockfile: Lockfile | undefined; try { @@ -299,6 +300,7 @@ export default async (opts: OptionValues) => { name, privatePackage, npmRegistry, + license, }, createPackageVersionProvider(lockfile), isMonoRepo, diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 25a3a4e26c..7c3192fb2e 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -254,6 +254,10 @@ export function registerCommands(program: Command) { '--baseVersion ', 'The version to use for any new packages (default: 0.1.0)', ) + .option( + '--license ', + 'The license to use for any new packages (default: Apache-2.0)', + ) .option('--no-private', 'Do not mark new packages as private') .action(lazy(() => import('./new/new').then(m => m.default))); @@ -396,7 +400,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/new/new.ts b/packages/cli/src/commands/new/new.ts index 61e27af3c5..2733b0be9b 100644 --- a/packages/cli/src/commands/new/new.ts +++ b/packages/cli/src/commands/new/new.ts @@ -70,11 +70,14 @@ export default async (opts: OptionValues) => { return dir; } + const license = opts.license ?? 'Apache-2.0'; + let modified = false; try { await factory.create(options, { isMonoRepo: await isMonoRepo(), defaultVersion, + license, scope: opts.scope?.replace(/^@/, ''), npmRegistry: opts.npmRegistry, private: Boolean(opts.private), 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..da7b378fbd 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({ @@ -123,23 +180,88 @@ export async function createConfig( // to remove this eventually! plugins.push( new ProvidePlugin({ - process: 'process/browser', + process: require.resolve('process/browser'), Buffer: ['buffer', 'Buffer'], }), ); - 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..aab16a8670 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.flatMap(t => ['--config', paths.resolveTarget(t)]), }); + 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 2d6a4c9c83..59b5403239 100644 --- a/packages/cli/src/lib/new/factories/backendModule.test.ts +++ b/packages/cli/src/lib/new/factories/backendModule.test.ts @@ -79,6 +79,7 @@ describe('backendModule factory', () => { modified = true; }, createTemporaryDirectory: () => fs.mkdtemp('test'), + license: 'Apache-2.0', }); expect(modified).toBe(true); diff --git a/packages/cli/src/lib/new/factories/backendModule.ts b/packages/cli/src/lib/new/factories/backendModule.ts index 67860b812e..de759538b1 100644 --- a/packages/cli/src/lib/new/factories/backendModule.ts +++ b/packages/cli/src/lib/new/factories/backendModule.ts @@ -75,6 +75,7 @@ export const backendModule = createFactory({ packageVersion: ctx.defaultVersion, privatePackage: ctx.private, npmRegistry: ctx.npmRegistry, + license: ctx.license, }, }); diff --git a/packages/cli/src/lib/new/factories/backendPlugin.test.ts b/packages/cli/src/lib/new/factories/backendPlugin.test.ts index f6eeb8cf33..3b90e2b0dd 100644 --- a/packages/cli/src/lib/new/factories/backendPlugin.test.ts +++ b/packages/cli/src/lib/new/factories/backendPlugin.test.ts @@ -78,6 +78,7 @@ describe('backendPlugin factory', () => { modified = true; }, createTemporaryDirectory: () => fs.mkdtemp('test'), + license: 'Apache-2.0', }); expect(modified).toBe(true); diff --git a/packages/cli/src/lib/new/factories/backendPlugin.ts b/packages/cli/src/lib/new/factories/backendPlugin.ts index fc77896461..9129a66075 100644 --- a/packages/cli/src/lib/new/factories/backendPlugin.ts +++ b/packages/cli/src/lib/new/factories/backendPlugin.ts @@ -64,6 +64,7 @@ export const backendPlugin = createFactory({ pluginVersion: ctx.defaultVersion, privatePackage: ctx.private, npmRegistry: ctx.npmRegistry, + license: ctx.license, }, }); diff --git a/packages/cli/src/lib/new/factories/frontendPlugin.test.ts b/packages/cli/src/lib/new/factories/frontendPlugin.test.ts index cca79dea2a..55e569a15f 100644 --- a/packages/cli/src/lib/new/factories/frontendPlugin.test.ts +++ b/packages/cli/src/lib/new/factories/frontendPlugin.test.ts @@ -80,6 +80,7 @@ describe('frontendPlugin factory', () => { modified = true; }, createTemporaryDirectory: () => fs.mkdtemp('test'), + license: 'Apache-2.0', }); expect(modified).toBe(true); @@ -174,6 +175,7 @@ const router = ( defaultVersion: '1.0.0', markAsModified: () => {}, createTemporaryDirectory: () => fs.mkdtemp('test'), + license: 'Apache-2.0', }); await expect( diff --git a/packages/cli/src/lib/new/factories/frontendPlugin.ts b/packages/cli/src/lib/new/factories/frontendPlugin.ts index 1058c879c1..41907d9d3a 100644 --- a/packages/cli/src/lib/new/factories/frontendPlugin.ts +++ b/packages/cli/src/lib/new/factories/frontendPlugin.ts @@ -67,6 +67,7 @@ export const frontendPlugin = createFactory({ pluginVersion: ctx.defaultVersion, privatePackage: ctx.private, npmRegistry: ctx.npmRegistry, + license: ctx.license, }, }); diff --git a/packages/cli/src/lib/new/factories/nodeLibraryPackage.test.ts b/packages/cli/src/lib/new/factories/nodeLibraryPackage.test.ts index e3673d21ba..69999761e9 100644 --- a/packages/cli/src/lib/new/factories/nodeLibraryPackage.test.ts +++ b/packages/cli/src/lib/new/factories/nodeLibraryPackage.test.ts @@ -64,6 +64,7 @@ describe('nodeLibraryPackage factory', () => { modified = true; }, createTemporaryDirectory: () => fs.mkdtemp('test'), + license: 'Apache-2.0', }); expect(modified).toBe(true); @@ -134,6 +135,7 @@ describe('nodeLibraryPackage factory', () => { defaultVersion: '1.0.0', markAsModified: () => {}, createTemporaryDirectory: () => fs.mkdtemp('test'), + license: 'Apache-2.0', }); expect(Task.forCommand).toHaveBeenCalledTimes(2); diff --git a/packages/cli/src/lib/new/factories/nodeLibraryPackage.ts b/packages/cli/src/lib/new/factories/nodeLibraryPackage.ts index 687caa7cd5..3d9029a01d 100644 --- a/packages/cli/src/lib/new/factories/nodeLibraryPackage.ts +++ b/packages/cli/src/lib/new/factories/nodeLibraryPackage.ts @@ -61,6 +61,7 @@ export const nodeLibraryPackage = createFactory({ pluginVersion: ctx.defaultVersion, privatePackage: ctx.private, npmRegistry: ctx.npmRegistry, + license: ctx.license, }, }); diff --git a/packages/cli/src/lib/new/factories/pluginCommon.test.ts b/packages/cli/src/lib/new/factories/pluginCommon.test.ts index 82c1c87169..8f01401435 100644 --- a/packages/cli/src/lib/new/factories/pluginCommon.test.ts +++ b/packages/cli/src/lib/new/factories/pluginCommon.test.ts @@ -62,6 +62,7 @@ describe('pluginCommon factory', () => { modified = true; }, createTemporaryDirectory: () => fs.mkdtemp('test'), + license: 'Apache-2.0', }); expect(modified).toBe(true); diff --git a/packages/cli/src/lib/new/factories/pluginCommon.ts b/packages/cli/src/lib/new/factories/pluginCommon.ts index 5f694124ea..eb17c70a5c 100644 --- a/packages/cli/src/lib/new/factories/pluginCommon.ts +++ b/packages/cli/src/lib/new/factories/pluginCommon.ts @@ -61,6 +61,7 @@ export const pluginCommon = createFactory({ privatePackage: ctx.private, npmRegistry: ctx.npmRegistry, pluginVersion: ctx.defaultVersion, + license: ctx.license, }, }); diff --git a/packages/cli/src/lib/new/factories/pluginNode.test.ts b/packages/cli/src/lib/new/factories/pluginNode.test.ts index d8d9ea33ab..b6aad0a129 100644 --- a/packages/cli/src/lib/new/factories/pluginNode.test.ts +++ b/packages/cli/src/lib/new/factories/pluginNode.test.ts @@ -62,6 +62,7 @@ describe('pluginNode factory', () => { modified = true; }, createTemporaryDirectory: () => fs.mkdtemp('test'), + license: 'Apache-2.0', }); expect(modified).toBe(true); diff --git a/packages/cli/src/lib/new/factories/pluginNode.ts b/packages/cli/src/lib/new/factories/pluginNode.ts index c7f9ba6f6a..8eaab3cadb 100644 --- a/packages/cli/src/lib/new/factories/pluginNode.ts +++ b/packages/cli/src/lib/new/factories/pluginNode.ts @@ -61,6 +61,7 @@ export const pluginNode = createFactory({ privatePackage: ctx.private, npmRegistry: ctx.npmRegistry, pluginVersion: ctx.defaultVersion, + license: ctx.license, }, }); diff --git a/packages/cli/src/lib/new/factories/pluginWeb.test.ts b/packages/cli/src/lib/new/factories/pluginWeb.test.ts index ff4211d8fa..20fbbbbdb7 100644 --- a/packages/cli/src/lib/new/factories/pluginWeb.test.ts +++ b/packages/cli/src/lib/new/factories/pluginWeb.test.ts @@ -62,6 +62,7 @@ describe('pluginWeb factory', () => { modified = true; }, createTemporaryDirectory: () => fs.mkdtemp('test'), + license: 'Apache-2.0', }); expect(modified).toBe(true); diff --git a/packages/cli/src/lib/new/factories/pluginWeb.ts b/packages/cli/src/lib/new/factories/pluginWeb.ts index dc8cb73dce..6635b0b0e1 100644 --- a/packages/cli/src/lib/new/factories/pluginWeb.ts +++ b/packages/cli/src/lib/new/factories/pluginWeb.ts @@ -61,6 +61,7 @@ export const pluginWeb = createFactory({ privatePackage: ctx.private, npmRegistry: ctx.npmRegistry, pluginVersion: ctx.defaultVersion, + license: ctx.license, }, }); diff --git a/packages/cli/src/lib/new/factories/scaffolderModule.test.ts b/packages/cli/src/lib/new/factories/scaffolderModule.test.ts index d83c2ce3f7..c4b9d78fc1 100644 --- a/packages/cli/src/lib/new/factories/scaffolderModule.test.ts +++ b/packages/cli/src/lib/new/factories/scaffolderModule.test.ts @@ -62,6 +62,7 @@ describe('scaffolderModule factory', () => { modified = true; }, createTemporaryDirectory: (name: string) => fs.mkdtemp(name), + license: 'Apache-2.0', }); expect(modified).toBe(true); diff --git a/packages/cli/src/lib/new/factories/scaffolderModule.ts b/packages/cli/src/lib/new/factories/scaffolderModule.ts index 07f67ce78c..4c279a49d4 100644 --- a/packages/cli/src/lib/new/factories/scaffolderModule.ts +++ b/packages/cli/src/lib/new/factories/scaffolderModule.ts @@ -78,6 +78,7 @@ export const scaffolderModule = createFactory({ privatePackage: ctx.private, npmRegistry: ctx.npmRegistry, pluginVersion: ctx.defaultVersion, + license: ctx.license, }, }); diff --git a/packages/cli/src/lib/new/factories/webLibraryPackage.test.ts b/packages/cli/src/lib/new/factories/webLibraryPackage.test.ts index b0c8461189..691f0b9f1c 100644 --- a/packages/cli/src/lib/new/factories/webLibraryPackage.test.ts +++ b/packages/cli/src/lib/new/factories/webLibraryPackage.test.ts @@ -64,6 +64,7 @@ describe('webLibraryPackage factory', () => { modified = true; }, createTemporaryDirectory: () => fs.mkdtemp('test'), + license: 'Apache-2.0', }); expect(modified).toBe(true); @@ -134,6 +135,7 @@ describe('webLibraryPackage factory', () => { defaultVersion: '1.0.0', markAsModified: () => {}, createTemporaryDirectory: () => fs.mkdtemp('test'), + license: 'Apache-2.0', }); expect(Task.forCommand).toHaveBeenCalledTimes(2); diff --git a/packages/cli/src/lib/new/factories/webLibraryPackage.ts b/packages/cli/src/lib/new/factories/webLibraryPackage.ts index 45e1085ff3..3c63127d7d 100644 --- a/packages/cli/src/lib/new/factories/webLibraryPackage.ts +++ b/packages/cli/src/lib/new/factories/webLibraryPackage.ts @@ -61,6 +61,7 @@ export const webLibraryPackage = createFactory({ pluginVersion: ctx.defaultVersion, privatePackage: ctx.private, npmRegistry: ctx.npmRegistry, + license: ctx.license, }, }); diff --git a/packages/cli/src/lib/new/types.ts b/packages/cli/src/lib/new/types.ts index 6a894bf9ef..534f99f94f 100644 --- a/packages/cli/src/lib/new/types.ts +++ b/packages/cli/src/lib/new/types.ts @@ -27,6 +27,8 @@ export interface CreateContext { isMonoRepo: boolean; /** The default version to use for new packages */ defaultVersion: string; + /** License to use for new packages */ + license: string; /** Creates a temporary directory. This will always be deleted after creation is done. */ createTemporaryDirectory(name: string): Promise; 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-module/package.json.hbs b/packages/cli/templates/default-backend-module/package.json.hbs index 07eee73d50..db8b8749b3 100644 --- a/packages/cli/templates/default-backend-module/package.json.hbs +++ b/packages/cli/templates/default-backend-module/package.json.hbs @@ -4,7 +4,7 @@ "version": "{{packageVersion}}", "main": "src/index.ts", "types": "src/index.ts", - "license": "Apache-2.0", + "license": "{{license}}", {{#if privatePackage}} "private": {{privatePackage}}, {{/if}} diff --git a/packages/cli/templates/default-backend-plugin/package.json.hbs b/packages/cli/templates/default-backend-plugin/package.json.hbs index 3b808fc5e5..b05d1a844b 100644 --- a/packages/cli/templates/default-backend-plugin/package.json.hbs +++ b/packages/cli/templates/default-backend-plugin/package.json.hbs @@ -3,7 +3,7 @@ "version": "{{pluginVersion}}", "main": "src/index.ts", "types": "src/index.ts", - "license": "Apache-2.0", + "license": "{{license}}", {{#if privatePackage}} "private": {{privatePackage}}, {{/if}} @@ -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-common-plugin-package/package.json.hbs b/packages/cli/templates/default-common-plugin-package/package.json.hbs index 992599ef6d..689c3e6f53 100644 --- a/packages/cli/templates/default-common-plugin-package/package.json.hbs +++ b/packages/cli/templates/default-common-plugin-package/package.json.hbs @@ -4,7 +4,7 @@ "version": "{{pluginVersion}}", "main": "src/index.ts", "types": "src/index.ts", - "license": "Apache-2.0", + "license": "{{license}}", {{#if privatePackage}} "private": {{privatePackage}}, {{/if}} diff --git a/packages/cli/templates/default-node-plugin-package/package.json.hbs b/packages/cli/templates/default-node-plugin-package/package.json.hbs index 45becd1bea..d58f95b3b2 100644 --- a/packages/cli/templates/default-node-plugin-package/package.json.hbs +++ b/packages/cli/templates/default-node-plugin-package/package.json.hbs @@ -4,7 +4,7 @@ "version": "{{pluginVersion}}", "main": "src/index.ts", "types": "src/index.ts", - "license": "Apache-2.0", + "license": "{{license}}", {{#if privatePackage}} "private": {{privatePackage}}, {{/if}} diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index c833c125f6..b4034fd742 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -3,7 +3,7 @@ "version": "{{pluginVersion}}", "main": "src/index.ts", "types": "src/index.ts", - "license": "Apache-2.0", + "license": "{{license}}", {{#if privatePackage}} "private": {{privatePackage}}, {{/if}} 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/cli/templates/default-react-plugin-package/package.json.hbs b/packages/cli/templates/default-react-plugin-package/package.json.hbs index 79f3430f46..bea066630f 100644 --- a/packages/cli/templates/default-react-plugin-package/package.json.hbs +++ b/packages/cli/templates/default-react-plugin-package/package.json.hbs @@ -4,7 +4,7 @@ "version": "{{pluginVersion}}", "main": "src/index.ts", "types": "src/index.ts", - "license": "Apache-2.0", + "license": "{{license}}", {{#if privatePackage}} "private": {{privatePackage}}, {{/if}} diff --git a/packages/cli/templates/node-library-package/package.json.hbs b/packages/cli/templates/node-library-package/package.json.hbs index a6ede31893..d56a51bcd6 100644 --- a/packages/cli/templates/node-library-package/package.json.hbs +++ b/packages/cli/templates/node-library-package/package.json.hbs @@ -3,7 +3,7 @@ "version": "{{pluginVersion}}", "main": "src/index.ts", "types": "src/index.ts", - "license": "Apache-2.0", + "license": "{{license}}", {{#if privatePackage}} "private": {{privatePackage}}, {{/if}} diff --git a/packages/cli/templates/scaffolder-module/package.json.hbs b/packages/cli/templates/scaffolder-module/package.json.hbs index 0ebe5dd5f4..df4bf352f4 100644 --- a/packages/cli/templates/scaffolder-module/package.json.hbs +++ b/packages/cli/templates/scaffolder-module/package.json.hbs @@ -4,7 +4,7 @@ "version": "{{pluginVersion}}", "main": "src/index.ts", "types": "src/index.ts", - "license": "Apache-2.0", + "license": "{{license}}", {{#if privatePackage}} "private": {{privatePackage}}, {{/if}} diff --git a/packages/cli/templates/web-library-package/package.json.hbs b/packages/cli/templates/web-library-package/package.json.hbs index 271417a525..64a693dd4c 100644 --- a/packages/cli/templates/web-library-package/package.json.hbs +++ b/packages/cli/templates/web-library-package/package.json.hbs @@ -3,7 +3,7 @@ "version": "{{pluginVersion}}", "main": "src/index.ts", "types": "src/index.ts", - "license": "Apache-2.0", + "license": "{{license}}", {{#if privatePackage}} "private": {{privatePackage}}, {{/if}} diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index fb048e7237..a60e86a6c8 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,63 @@ # @backstage/config-loader +## 1.9.0 + +### Minor Changes + +- 274428f: Add configuration key to File and Remote `ConfigSource`s that enables configuration of parsing logic. Previously limited to yaml, these `ConfigSource`s now allow for a multitude of parsing options (e.g. JSON). + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 1edd6c2: The `env` option of `ConfigSources.default` now correctly allows undefined members. +- 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.9.0-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.9.0-next.1 + +### Minor Changes + +- 274428f: Add configuration key to File and Remote `ConfigSource`s that enables configuration of parsing logic. Previously limited to yaml, these `ConfigSource`s now allow for a multitude of parsing options (e.g. JSON). + +### Patch Changes + +- 1edd6c2: The `env` option of `ConfigSources.default` now correctly allows undefined members. +- 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.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..e2741d609c 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 @@ -141,6 +142,7 @@ export class FileConfigSource implements ConfigSource { // @public export interface FileConfigSourceOptions { + parser?: Parser; path: string; substitutionFunc?: EnvFunc; watch?: boolean; @@ -218,6 +220,11 @@ export interface MutableConfigSourceOptions { data?: JsonObject; } +// @public +export type Parser = ({ contents }: { contents: string }) => Promise<{ + result?: JsonObject; +}>; + // @public export interface ReadConfigDataOptions { // (undocumented) @@ -242,6 +249,7 @@ export class RemoteConfigSource implements ConfigSource { // @public export interface RemoteConfigSourceOptions { + parser?: Parser; reloadInterval?: HumanDuration; substitutionFunc?: EnvFunc; url: string; diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 28718b8290..98d233b348 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.9.0", "description": "Config loading functionality used by Backstage backend, and CLI", "backstage": { "role": "node-library" @@ -49,7 +49,7 @@ "json-schema-traverse": "^1.0.0", "lodash": "^4.17.21", "minimist": "^1.2.5", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "typescript-json-schema": "^0.63.0", "yaml": "^2.0.0" }, 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/FileConfigSource.test.ts b/packages/config-loader/src/sources/FileConfigSource.test.ts index 61cdeed272..4ed31ccee0 100644 --- a/packages/config-loader/src/sources/FileConfigSource.test.ts +++ b/packages/config-loader/src/sources/FileConfigSource.test.ts @@ -59,6 +59,19 @@ describe('FileConfigSource', () => { ]); }); + it('should read a config file with optional parser', async () => { + const tmp = await tmpFiles({ 'a.json': JSON.stringify({ a: 1 }) }); + + const source = FileConfigSource.create({ + path: tmp.resolve('a.json'), + parser: async ({ contents }) => ({ result: JSON.parse(contents) }), + }); + + await expect(readN(source, 1)).resolves.toEqual([ + [{ data: { a: 1 }, context: 'a.json', path: tmp.resolve('a.json') }], + ]); + }); + it('should watch config files', async () => { const tmp = await tmpFiles({ 'a.yaml': 'a: 1' }); diff --git a/packages/config-loader/src/sources/FileConfigSource.ts b/packages/config-loader/src/sources/FileConfigSource.ts index 74266de227..c0697c1180 100644 --- a/packages/config-loader/src/sources/FileConfigSource.ts +++ b/packages/config-loader/src/sources/FileConfigSource.ts @@ -17,16 +17,17 @@ import chokidar, { FSWatcher } from 'chokidar'; import fs from 'fs-extra'; import { basename, dirname, isAbsolute, resolve as resolvePath } from 'path'; -import yaml from 'yaml'; import { AsyncConfigSourceGenerator, ConfigSource, ConfigSourceData, SubstitutionFunc, + Parser, ReadConfigDataOptions, } from './types'; import { createConfigTransformer } from './transform'; import { NotFoundError } from '@backstage/errors'; +import { parseYamlContent } from './utils'; /** * Options for {@link FileConfigSource.create}. @@ -48,6 +49,11 @@ export interface FileConfigSourceOptions { * A substitution function to use instead of the default environment substitution. */ substitutionFunc?: SubstitutionFunc; + + /** + * A content parsing function to transform string content to configuration values. + */ + parser?: Parser; } async function readFile(path: string): Promise { @@ -95,11 +101,13 @@ export class FileConfigSource implements ConfigSource { readonly #path: string; readonly #substitutionFunc?: SubstitutionFunc; readonly #watch?: boolean; + readonly #parser: Parser; private constructor(options: FileConfigSourceOptions) { this.#path = options.path; this.#substitutionFunc = options.substitutionFunc; this.#watch = options.watch ?? true; + this.#parser = options.parser ?? parseYamlContent; } // Work is duplicated across each read, in practice that should not @@ -154,12 +162,12 @@ export class FileConfigSource implements ConfigSource { watchedPaths.push(this.#path); } - const content = await readFile(this.#path); - if (content === undefined) { + const contents = await readFile(this.#path); + if (contents === undefined) { throw new NotFoundError(`Config file "${this.#path}" does not exist`); } - const parsed = yaml.parse(content); - if (parsed === null) { + const { result: parsed } = await this.#parser({ contents }); + if (parsed === undefined) { return []; } try { diff --git a/packages/config-loader/src/sources/RemoteConfigSource.test.ts b/packages/config-loader/src/sources/RemoteConfigSource.test.ts index 6082c05e65..372dc91823 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( @@ -59,6 +59,45 @@ app: ]); }); + it('should load and parse config from a remote URL', async () => { + worker.use( + rest.get('http://localhost/config.json', (_req, res, ctx) => + res( + ctx.body( + JSON.stringify({ + app: { + title: 'Example App', + substituted: 'x', + escaped: '$${VALUE}', + }, + }), + ), + ), + ), + ); + + const source = RemoteConfigSource.create({ + url: 'http://localhost/config.json', + substitutionFunc: async () => 'x', + parser: async ({ contents }) => ({ result: JSON.parse(contents) }), + }); + + await expect(readN(source, 1)).resolves.toEqual([ + [ + { + context: 'http://localhost/config.json', + data: { + app: { + title: 'Example App', + substituted: 'x', + escaped: '${VALUE}', + }, + }, + }, + ], + ]); + }); + it('should reload config from a remote URL', async () => { let fetched = false; diff --git a/packages/config-loader/src/sources/RemoteConfigSource.ts b/packages/config-loader/src/sources/RemoteConfigSource.ts index 10a64ee635..1492cd4d28 100644 --- a/packages/config-loader/src/sources/RemoteConfigSource.ts +++ b/packages/config-loader/src/sources/RemoteConfigSource.ts @@ -22,14 +22,15 @@ import { } from '@backstage/types'; import isEqual from 'lodash/isEqual'; import fetch from 'node-fetch'; -import yaml from 'yaml'; import { ConfigTransformer, createConfigTransformer } from './transform'; import { AsyncConfigSourceGenerator, ConfigSource, SubstitutionFunc, ReadConfigDataOptions, + Parser, } from './types'; +import { parseYamlContent } from './utils'; const DEFAULT_RELOAD_INTERVAL = { seconds: 60 }; @@ -55,6 +56,11 @@ export interface RemoteConfigSourceOptions { * A substitution function to use instead of the default environment substitution. */ substitutionFunc?: SubstitutionFunc; + + /** + * A content parsing function to transform string content to configuration values. + */ + parser?: Parser; } /** @@ -84,6 +90,7 @@ export class RemoteConfigSource implements ConfigSource { readonly #url: string; readonly #reloadIntervalMs: number; readonly #transformer: ConfigTransformer; + readonly #parser: Parser; private constructor(options: RemoteConfigSourceOptions) { this.#url = options.url; @@ -93,6 +100,7 @@ export class RemoteConfigSource implements ConfigSource { this.#transformer = createConfigTransformer({ substitutionFunc: options.substitutionFunc, }); + this.#parser = options.parser ?? parseYamlContent; } async *readConfigData( @@ -135,11 +143,21 @@ export class RemoteConfigSource implements ConfigSource { throw await ResponseError.fromResponse(res); } - const content = await res.text(); - const data = await this.#transformer(yaml.parse(content)); - if (data === null) { + const contents = await res.text(); + const { result: rawData } = await this.#parser({ contents }); + if (rawData === undefined) { + /** + * This error message is/was coupled to the implementation and with refactoring it is no longer truly accurate + * This behavior is also inconsistent with {@link FileConfigSource}, which doesn't error on unparseable or empty + * content + * + * Preserving to not make a breaking change + */ throw new Error('configuration data is null'); - } else if (typeof data !== 'object') { + } + + const data = await this.#transformer(rawData); + if (typeof data !== 'object') { throw new Error('configuration data is not an object'); } else if (Array.isArray(data)) { throw new Error( diff --git a/packages/config-loader/src/sources/index.ts b/packages/config-loader/src/sources/index.ts index a25f689b40..3bf8e71990 100644 --- a/packages/config-loader/src/sources/index.ts +++ b/packages/config-loader/src/sources/index.ts @@ -38,4 +38,5 @@ export type { ConfigSourceData, ReadConfigDataOptions, AsyncConfigSourceGenerator, + Parser, } from './types'; diff --git a/packages/config-loader/src/sources/types.ts b/packages/config-loader/src/sources/types.ts index dec6f415f2..abdf2f5c8e 100644 --- a/packages/config-loader/src/sources/types.ts +++ b/packages/config-loader/src/sources/types.ts @@ -15,6 +15,7 @@ */ import { AppConfig } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; /** * The data returned by {@link ConfigSource.readConfigData}. @@ -89,3 +90,18 @@ export interface ConfigSource { * @public */ export type SubstitutionFunc = (name: string) => Promise; + +/** + * A custom function to be used for parsing configuration content. + * + * @remarks + * + * The default parsing function will parse configuration content as yaml. + * + * @public + */ +export type Parser = ({ + contents, +}: { + contents: string; +}) => Promise<{ result?: JsonObject }>; diff --git a/packages/config-loader/src/sources/utils.ts b/packages/config-loader/src/sources/utils.ts index 9978234195..b948cc47b4 100644 --- a/packages/config-loader/src/sources/utils.ts +++ b/packages/config-loader/src/sources/utils.ts @@ -14,6 +14,9 @@ * limitations under the License. */ +import { Parser } from './types'; +import yaml from 'yaml'; + /** @internal */ export interface SimpleDeferred { promise: Promise; @@ -55,3 +58,9 @@ export async function waitOrAbort( signals.forEach(s => s.addEventListener('abort', onAbort)); }); } + +/** @internal */ +export const parseYamlContent: Parser = async ({ contents }) => { + const parsed = yaml.parse(contents); + return { result: parsed === null ? undefined : parsed }; +}; diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 32edc95e49..2ae26f8b97 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,62 @@ # @backstage/core-app-api +## 1.14.2 + +### 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.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/package.json b/packages/core-app-api/package.json index bdd6a1f663..ad8b4b49c0 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.2", "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..35849d453c 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,74 @@ # @backstage/core-compat-api +## 0.2.8 + +### Patch Changes + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- 16cf96c: Both `compatWrapper` and `convertLegacyRouteRef` now support converting from the new system to the old. +- 519b8e0: Added new utilities for converting legacy plugins and extensions to the new system. The `convertLegacyPlugin` option will convert an existing plugin to the new system, although you need to supply extensions for the plugin yourself. To help out with this, there is also a new `convertLegacyPageExtension` which converts an existing page extension to the new system. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## 0.2.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## 0.2.8-next.2 + +### Patch Changes + +- 72754db: Updated usage of `useRouteRef`, which can now always return `undefined`. +- 16cf96c: Both `compatWrapper` and `convertLegacyRouteRef` now support converting from the new system to the old. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## 0.2.8-next.1 + +### Patch Changes + +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-plugin-api@1.9.3 + - @backstage/version-bridge@1.0.8 + +## 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/api-report.md b/packages/core-compat-api/api-report.md index be80ca50e9..75b91cc055 100644 --- a/packages/core-compat-api/api-report.md +++ b/packages/core-compat-api/api-report.md @@ -8,6 +8,10 @@ import { AnalyticsApi as AnalyticsApi_2 } from '@backstage/frontend-plugin-api'; import { AnalyticsEvent } from '@backstage/core-plugin-api'; import { AnalyticsEvent as AnalyticsEvent_2 } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/core-plugin-api'; +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { BackstagePlugin as BackstagePlugin_2 } from '@backstage/frontend-plugin-api'; +import { ComponentType } from 'react'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { ExternalRouteRef as ExternalRouteRef_2 } from '@backstage/frontend-plugin-api'; import { FrontendFeature } from '@backstage/frontend-plugin-api'; @@ -26,6 +30,23 @@ export function convertLegacyApp( rootElement: React_2.JSX.Element, ): FrontendFeature[]; +// @public (undocumented) +export function convertLegacyPageExtension( + LegacyExtension: ComponentType<{}>, + overrides?: { + name?: string; + defaultPath?: string; + }, +): ExtensionDefinition; + +// @public (undocumented) +export function convertLegacyPlugin( + legacyPlugin: BackstagePlugin, + options: { + extensions: ExtensionDefinition[]; + }, +): BackstagePlugin_2; + // @public export function convertLegacyRouteRef( ref: RouteRef, @@ -37,12 +58,24 @@ export function convertLegacyRouteRef( ): SubRouteRef_2; // @public -export function convertLegacyRouteRef< - TParams extends AnyRouteRefParams, - TOptional extends boolean, ->( - ref: ExternalRouteRef, -): ExternalRouteRef_2; +export function convertLegacyRouteRef( + ref: ExternalRouteRef, +): ExternalRouteRef_2; + +// @public +export function convertLegacyRouteRef( + ref: RouteRef_2, +): RouteRef; + +// @public +export function convertLegacyRouteRef( + ref: SubRouteRef_2, +): SubRouteRef; + +// @public +export function convertLegacyRouteRef( + ref: ExternalRouteRef_2, +): ExternalRouteRef; // @public export function convertLegacyRouteRefs< @@ -75,8 +108,8 @@ export type ToNewRouteRef = ? RouteRef_2 : T extends SubRouteRef ? SubRouteRef_2 - : T extends ExternalRouteRef - ? ExternalRouteRef_2 + : T extends ExternalRouteRef + ? ExternalRouteRef_2 : never; // (No @packageDocumentation comment for this package) diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 954cd6deb3..5af7d1b960 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", "backstage": { "role": "web-library" }, @@ -34,7 +34,8 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/version-bridge": "workspace:^", - "@types/react": "^16.13.1 || ^17.0.0" + "@types/react": "^16.13.1 || ^17.0.0", + "lodash": "^4.17.21" }, "devDependencies": { "@backstage-community/plugin-puppetdb": "^0.1.18", @@ -44,6 +45,7 @@ "@backstage/frontend-app-api": "workspace:^", "@backstage/frontend-test-utils": "workspace:^", "@backstage/plugin-catalog": "workspace:^", + "@backstage/test-utils": "workspace:^", "@oriflame/backstage-plugin-score-card": "^0.8.0", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^15.0.0" diff --git a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx index aa3beee679..f3e2baee38 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx @@ -31,7 +31,7 @@ import { Navigate, Route, Routes } from 'react-router-dom'; import { collectLegacyRoutes } from './collectLegacyRoutes'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { toInternalBackstagePlugin } from '../../frontend-plugin-api/src/wiring/createPlugin'; +import { toInternalBackstagePlugin } from '../../frontend-plugin-api/src/wiring/createFrontendPlugin'; import { createPlugin, createRoutableExtension, @@ -70,11 +70,11 @@ describe('collectLegacyRoutes', () => { id: 'page:score-card', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'score-board' }, + defaultConfig: {}, }, { id: 'api:plugin.scoringdata.service', - attachTo: { id: 'app', input: 'apis' }, + attachTo: { id: 'root', input: 'apis' }, disabled: false, }, ], @@ -86,11 +86,11 @@ describe('collectLegacyRoutes', () => { id: 'page:stackstorm', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'stackstorm' }, + defaultConfig: {}, }, { id: 'api:plugin.stackstorm.service', - attachTo: { id: 'app', input: 'apis' }, + attachTo: { id: 'root', input: 'apis' }, disabled: false, }, ], @@ -102,17 +102,17 @@ describe('collectLegacyRoutes', () => { id: 'page:puppetDb', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'puppetdb' }, + defaultConfig: {}, }, { id: 'page:puppetDb/1', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'puppetdb' }, + defaultConfig: {}, }, { id: 'api:plugin.puppetdb.service', - attachTo: { id: 'app', input: 'apis' }, + attachTo: { id: 'root', input: 'apis' }, disabled: false, }, ], @@ -173,12 +173,12 @@ describe('collectLegacyRoutes', () => { id: 'page:catalog', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'catalog' }, + defaultConfig: {}, }, { id: 'page:catalog/1', attachTo: { id: 'app/routes', input: 'routes' }, - defaultConfig: { path: 'catalog/:namespace/:kind/:name' }, + defaultConfig: {}, disabled: false, }, { @@ -211,7 +211,7 @@ describe('collectLegacyRoutes', () => { { id: 'api:plugin.catalog.service', attachTo: { - id: 'app', + id: 'root', input: 'apis', }, defaultConfig: undefined, @@ -220,7 +220,7 @@ describe('collectLegacyRoutes', () => { { id: 'api:catalog-react.starred-entities', attachTo: { - id: 'app', + id: 'root', input: 'apis', }, defaultConfig: undefined, @@ -229,7 +229,7 @@ describe('collectLegacyRoutes', () => { { id: 'api:plugin.catalog.entity-presentation', attachTo: { - id: 'app', + id: 'root', input: 'apis', }, defaultConfig: undefined, @@ -242,7 +242,7 @@ describe('collectLegacyRoutes', () => { extensions: [ { id: 'api:plugin.scoringdata.service', - attachTo: { id: 'app', input: 'apis' }, + attachTo: { id: 'root', input: 'apis' }, disabled: false, }, ], diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx index b9f4e5528c..c5cb13b6e1 100644 --- a/packages/core-compat-api/src/collectLegacyRoutes.tsx +++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx @@ -24,11 +24,11 @@ import { BackstagePlugin, ExtensionDefinition, coreExtensionData, - createApiExtension, createExtension, createExtensionInput, - createPageExtension, - createPlugin, + createFrontendPlugin, + ApiBlueprint, + PageBlueprint, } from '@backstage/frontend-plugin-api'; import React, { Children, ReactNode, isValidElement } from 'react'; import { Route, Routes } from 'react-router-dom'; @@ -80,19 +80,24 @@ function makeRoutingShimExtension(options: { name, attachTo: { id: parentExtensionId, input: 'childRoutingShims' }, inputs: { - childRoutingShims: createExtensionInput({ - routePath: coreExtensionData.routePath.optional(), - routeRef: coreExtensionData.routeRef.optional(), - }), + childRoutingShims: createExtensionInput([ + coreExtensionData.routePath.optional(), + coreExtensionData.routeRef.optional(), + ]), }, - output: { - routePath: coreExtensionData.routePath.optional(), - routeRef: coreExtensionData.routeRef.optional(), + output: [ + coreExtensionData.routePath.optional(), + coreExtensionData.routeRef.optional(), + ], + *factory() { + if (routePath !== undefined) { + yield coreExtensionData.routePath(routePath); + } + + if (routeRef) { + yield coreExtensionData.routeRef(convertLegacyRouteRef(routeRef)); + } }, - factory: () => ({ - routePath, - routeRef: routeRef ? convertLegacyRouteRef(routeRef) : undefined, - }), }); } @@ -101,7 +106,7 @@ function visitRouteChildren(options: { parentExtensionId: string; context: { pluginId: string; - extensions: ExtensionDefinition[]; + extensions: ExtensionDefinition[]; getUniqueName: () => string; discoverPlugin: (plugin: LegacyBackstagePlugin) => void; }; @@ -154,7 +159,7 @@ export function collectLegacyRoutes( ): BackstagePlugin[] { const pluginExtensions = new Map< LegacyBackstagePlugin, - ExtensionDefinition[] + ExtensionDefinition[] >(); const getUniqueName = (() => { @@ -214,28 +219,33 @@ export function collectLegacyRoutes( }`; extensions.push( - createPageExtension({ + PageBlueprint.makeWithOverrides({ name: pageExtensionName, - defaultPath: path[0] === '/' ? path.slice(1) : path, - routeRef: routeRef ? convertLegacyRouteRef(routeRef) : undefined, inputs: { - childRoutingShims: createExtensionInput({ - routePath: coreExtensionData.routePath.optional(), - routeRef: coreExtensionData.routeRef.optional(), - }), + childRoutingShims: createExtensionInput([ + coreExtensionData.routePath.optional(), + coreExtensionData.routeRef.optional(), + ]), + }, + factory(originalFactory, { inputs: _inputs }) { + // todo(blam): why do we not use the inputs here? + return originalFactory({ + defaultPath: path[0] === '/' ? path.slice(1) : path, + routeRef: routeRef ? convertLegacyRouteRef(routeRef) : undefined, + loader: async () => + compatWrapper( + route.props.children ? ( + + + + + + ) : ( + routeElement + ), + ), + }); }, - loader: async () => - compatWrapper( - route.props.children ? ( - - - - - - ) : ( - routeElement - ), - ), }), ); @@ -253,12 +263,12 @@ export function collectLegacyRoutes( ); return Array.from(pluginExtensions).map(([plugin, extensions]) => - createPlugin({ + createFrontendPlugin({ id: plugin.getId(), extensions: [ ...extensions, ...Array.from(plugin.getApis()).map(factory => - createApiExtension({ factory }), + ApiBlueprint.make({ namespace: factory.api.id, params: { factory } }), ), ], routes: convertLegacyRouteRefs(plugin.routes ?? {}), diff --git a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx index 288f3f900b..445f42124f 100644 --- a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx +++ b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx @@ -21,7 +21,7 @@ import { AppContextProvider } from '../../../core-app-api/src/app/AppContext'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { RouteResolver } from '../../../core-plugin-api/src/routing/useRouteRef'; import { - createPlugin as createNewPlugin, + createFrontendPlugin as createNewPlugin, BackstagePlugin as NewBackstagePlugin, appTreeApiRef, componentsApiRef, @@ -50,7 +50,9 @@ const legacyPluginStore = getOrCreateGlobalSingleton( () => new WeakMap(), ); -function toLegacyPlugin(plugin: NewBackstagePlugin): LegacyBackstagePlugin { +export function toLegacyPlugin( + plugin: NewBackstagePlugin, +): LegacyBackstagePlugin { let legacy = legacyPluginStore.get(plugin); if (legacy) { return legacy; diff --git a/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx index 5544862aee..9c646d5b00 100644 --- a/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx +++ b/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx @@ -14,10 +14,155 @@ * limitations under the License. */ -import React from 'react'; +import { + ApiHolder, + ApiRef, + AppContext, + useApp, +} from '@backstage/core-plugin-api'; +import { + AnyRouteRefParams, + ComponentRef, + ComponentsApi, + CoreErrorBoundaryFallbackProps, + CoreNotFoundErrorPageProps, + CoreProgressProps, + ExternalRouteRef, + IconComponent, + IconsApi, + RouteFunc, + RouteRef, + RouteResolutionApi, + RouteResolutionApiResolveOptions, + SubRouteRef, + componentsApiRef, + coreComponentRefs, + iconsApiRef, + routeResolutionApiRef, +} from '@backstage/frontend-plugin-api'; +import React, { ComponentType, useMemo } from 'react'; import { ReactNode } from 'react'; +import { toLegacyPlugin } from './BackwardsCompatProvider'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { ApiProvider } from '../../../core-app-api/src/apis/system/ApiProvider'; +import { useVersionedContext } from '@backstage/version-bridge'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { type RouteResolver } from '../../../core-plugin-api/src/routing/useRouteRef'; +import { convertLegacyRouteRef } from '../convertLegacyRouteRef'; + +class CompatComponentsApi implements ComponentsApi { + readonly #Progress: ComponentType; + readonly #NotFoundErrorPage: ComponentType; + readonly #ErrorBoundaryFallback: ComponentType; + + constructor(app: AppContext) { + const components = app.getComponents(); + const ErrorBoundaryFallback = (props: CoreErrorBoundaryFallbackProps) => ( + + ); + this.#Progress = components.Progress; + this.#NotFoundErrorPage = components.NotFoundErrorPage; + this.#ErrorBoundaryFallback = ErrorBoundaryFallback; + } + + getComponent(ref: ComponentRef): ComponentType { + switch (ref.id) { + case coreComponentRefs.progress.id: + return this.#Progress as ComponentType; + case coreComponentRefs.notFoundErrorPage.id: + return this.#NotFoundErrorPage as ComponentType; + case coreComponentRefs.errorBoundaryFallback.id: + return this.#ErrorBoundaryFallback as ComponentType; + default: + throw new Error( + `No backwards compatible component is available for ref '${ref.id}'`, + ); + } + } +} + +class CompatIconsApi implements IconsApi { + readonly #app: AppContext; + + constructor(app: AppContext) { + this.#app = app; + } + + getIcon(key: string): IconComponent | undefined { + return this.#app.getSystemIcon(key); + } + + listIconKeys(): string[] { + return Object.keys(this.#app.getSystemIcons()); + } +} + +class CompatRouteResolutionApi implements RouteResolutionApi { + readonly #routeResolver: RouteResolver; + + constructor(routeResolver: RouteResolver) { + this.#routeResolver = routeResolver; + } + + resolve( + anyRouteRef: + | RouteRef + | SubRouteRef + | ExternalRouteRef, + options?: RouteResolutionApiResolveOptions | undefined, + ): RouteFunc | undefined { + const legacyRef = convertLegacyRouteRef(anyRouteRef as RouteRef); + return this.#routeResolver.resolve(legacyRef, options?.sourcePath ?? '/'); + } +} + +class ForwardsCompatApis implements ApiHolder { + readonly #componentsApi: ComponentsApi; + readonly #iconsApi: IconsApi; + readonly #routeResolutionApi: RouteResolutionApi; + + constructor(app: AppContext, routeResolver: RouteResolver) { + this.#componentsApi = new CompatComponentsApi(app); + this.#iconsApi = new CompatIconsApi(app); + this.#routeResolutionApi = new CompatRouteResolutionApi(routeResolver); + } + + get(ref: ApiRef): T | undefined { + if (ref.id === componentsApiRef.id) { + return this.#componentsApi as T; + } else if (ref.id === iconsApiRef.id) { + return this.#iconsApi as T; + } else if (ref.id === routeResolutionApiRef.id) { + return this.#routeResolutionApi as T; + } + return undefined; + } +} + +function NewAppApisProvider(props: { children: ReactNode }) { + const app = useApp(); + const versionedRouteResolverContext = useVersionedContext<{ + 1: RouteResolver; + }>('routing-context'); + if (!versionedRouteResolverContext) { + throw new Error('Routing context is not available'); + } + const routeResolver = versionedRouteResolverContext.atVersion(1); + if (!routeResolver) { + throw new Error('RoutingContext v1 not available'); + } + + const appFallbackApis = useMemo( + () => new ForwardsCompatApis(app, routeResolver), + [app, routeResolver], + ); + + return {props.children}; +} export function ForwardsCompatProvider(props: { children: ReactNode }) { - // TODO(Rugvip): Implement - return <>{props.children}; + return {props.children}; } diff --git a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx index 74b9f209eb..6357f13906 100644 --- a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx +++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx @@ -16,21 +16,28 @@ import React from 'react'; import { + componentsApiRef, + coreComponentRefs, coreExtensionData, createExtension, + iconsApiRef, + useRouteRef as useNewRouteRef, + createRouteRef as createNewRouteRef, + useApi, } from '@backstage/frontend-plugin-api'; import { createExtensionTester, - renderInTestApp, + renderInTestApp as renderInNewTestApp, } from '@backstage/frontend-test-utils'; import { screen } from '@testing-library/react'; import { compatWrapper } from './compatWrapper'; import { - createRouteRef, useApp, - useRouteRef, + useRouteRef as useOldRouteRef, + createRouteRef as createOldRouteRef, } from '@backstage/core-plugin-api'; import { convertLegacyRouteRef } from '../convertLegacyRouteRef'; +import { renderInTestApp as renderInOldTestApp } from '@backstage/test-utils'; describe('BackwardsCompatProvider', () => { it('should convert the app context', () => { @@ -38,9 +45,7 @@ describe('BackwardsCompatProvider', () => { createExtensionTester( createExtension({ attachTo: { id: 'ignored', input: 'ignored' }, - output: { - element: coreExtensionData.reactElement, - }, + output: [coreExtensionData.reactElement], factory() { function Component() { const app = useApp(); @@ -59,9 +64,7 @@ describe('BackwardsCompatProvider', () => { ); } - return { - element: compatWrapper(), - }; + return [coreExtensionData.reactElement(compatWrapper())]; }, }), ).render(); @@ -74,14 +77,58 @@ describe('BackwardsCompatProvider', () => { }); it('should convert the routing context', () => { - const routeRef = createRouteRef({ id: 'test' }); + const routeRef = createOldRouteRef({ id: 'test' }); function Component() { - const link = useRouteRef(routeRef); + const link = useOldRouteRef(routeRef); return
link: {link()}
; } - renderInTestApp(compatWrapper(), { + renderInNewTestApp(compatWrapper(), { + mountedRoutes: { '/test': convertLegacyRouteRef(routeRef) }, + }); + + expect(screen.getByText('link: /test')).toBeInTheDocument(); + }); +}); + +describe('ForwardsCompatProvider', () => { + it('should convert the app context', async () => { + function Component() { + const components = useApi(componentsApiRef); + const icons = useApi(iconsApiRef); + return ( +
+ components:{' '} + {Object.entries(coreComponentRefs) + .map( + ([name, ref]) => + `${name}=${Boolean(components.getComponent(ref))}`, + ) + .join(', ')} + {'\n'} + icons: {icons.listIconKeys().join(', ')} +
+ ); + } + + await renderInOldTestApp(compatWrapper()); + + expect(screen.getByTestId('ctx').textContent).toMatchInlineSnapshot(` + "components: progress=true, notFoundErrorPage=true, errorBoundaryFallback=true + icons: kind:api, kind:component, kind:domain, kind:group, kind:location, kind:system, kind:user, kind:resource, kind:template, brokenImage, catalog, scaffolder, techdocs, search, chat, dashboard, docs, email, github, group, help, user, warning" + `); + }); + + it('should convert the routing context', async () => { + const routeRef = createNewRouteRef(); + + function Component() { + const link = useNewRouteRef(routeRef); + return
link: {link?.()}
; + } + + await renderInOldTestApp(compatWrapper(), { mountedRoutes: { '/test': convertLegacyRouteRef(routeRef) }, }); diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx index 2e7be66915..b76a6c2d71 100644 --- a/packages/core-compat-api/src/convertLegacyApp.test.tsx +++ b/packages/core-compat-api/src/convertLegacyApp.test.tsx @@ -62,11 +62,11 @@ describe('convertLegacyApp', () => { id: 'page:score-card', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'score-board' }, + defaultConfig: {}, }, { id: 'api:plugin.scoringdata.service', - attachTo: { id: 'app', input: 'apis' }, + attachTo: { id: 'root', input: 'apis' }, disabled: false, }, ], @@ -78,11 +78,11 @@ describe('convertLegacyApp', () => { id: 'page:stackstorm', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'stackstorm' }, + defaultConfig: {}, }, { id: 'api:plugin.stackstorm.service', - attachTo: { id: 'app', input: 'apis' }, + attachTo: { id: 'root', input: 'apis' }, disabled: false, }, ], @@ -94,17 +94,17 @@ describe('convertLegacyApp', () => { id: 'page:puppetDb', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'puppetdb' }, + defaultConfig: {}, }, { id: 'page:puppetDb/1', attachTo: { id: 'app/routes', input: 'routes' }, disabled: false, - defaultConfig: { path: 'puppetdb' }, + defaultConfig: {}, }, { id: 'api:plugin.puppetdb.service', - attachTo: { id: 'app', input: 'apis' }, + attachTo: { id: 'root', input: 'apis' }, disabled: false, }, ], diff --git a/packages/core-compat-api/src/convertLegacyApp.ts b/packages/core-compat-api/src/convertLegacyApp.ts index 72ce4b5e61..ffeab3bdc6 100644 --- a/packages/core-compat-api/src/convertLegacyApp.ts +++ b/packages/core-compat-api/src/convertLegacyApp.ts @@ -107,33 +107,30 @@ export function convertLegacyApp( name: 'layout', attachTo: { id: 'app', input: 'root' }, inputs: { - content: createExtensionInput( - { - element: coreExtensionData.reactElement, - }, - { singleton: true }, - ), - }, - output: { - element: coreExtensionData.reactElement, + content: createExtensionInput([coreExtensionData.reactElement], { + singleton: true, + }), }, + output: [coreExtensionData.reactElement], factory({ inputs }) { // Clone the root element, this replaces the FlatRoutes declared in the app with out content input - return { - element: React.cloneElement( - rootEl, - undefined, - inputs.content.output.element, + return [ + coreExtensionData.reactElement( + React.cloneElement( + rootEl, + undefined, + inputs.content.get(coreExtensionData.reactElement), + ), ), - }; + ]; }, }); const CoreNavOverride = createExtension({ namespace: 'app', name: 'nav', attachTo: { id: 'app/layout', input: 'nav' }, - output: {}, - factory: () => ({}), + output: [], + factory: () => [], disabled: true, }); diff --git a/packages/core-compat-api/src/convertLegacyPageExtension.test.tsx b/packages/core-compat-api/src/convertLegacyPageExtension.test.tsx new file mode 100644 index 0000000000..482474d580 --- /dev/null +++ b/packages/core-compat-api/src/convertLegacyPageExtension.test.tsx @@ -0,0 +1,99 @@ +/* + * 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 { + createPlugin as createLegacyPlugin, + createRouteRef as createLegacyRouteRef, + createRoutableExtension, +} from '@backstage/core-plugin-api'; +import { coreExtensionData } from '@backstage/frontend-plugin-api'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { convertLegacyPageExtension } from './convertLegacyPageExtension'; +import { convertLegacyRouteRef } from './convertLegacyRouteRef'; + +const routeRef = createLegacyRouteRef({ id: 'test' }); +const legacyPlugin = createLegacyPlugin({ + id: 'test', + routes: { + test: routeRef, + }, +}); + +describe('convertLegacyPageExtension', () => { + it('should convert a page extension', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'ExamplePage', + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ); + + const converted = convertLegacyPageExtension(LegacyExtension); + expect(converted.kind).toBe('page'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('example'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(coreExtensionData.routePath)).toBe('/example'); + expect(tester.get(coreExtensionData.routeRef)).toBe(routeRef); + }); + + it('should convert a page extension with overrides', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'ExamplePage', + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ); + + const converted = convertLegacyPageExtension(LegacyExtension, { + name: 'other', + defaultPath: '/other', + }); + expect(converted.kind).toBe('page'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('other'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(coreExtensionData.routePath)).toBe('/other'); + expect(tester.get(coreExtensionData.routeRef)).toBe(routeRef); + }); +}); diff --git a/packages/core-compat-api/src/convertLegacyPageExtension.tsx b/packages/core-compat-api/src/convertLegacyPageExtension.tsx new file mode 100644 index 0000000000..7ef4e0faff --- /dev/null +++ b/packages/core-compat-api/src/convertLegacyPageExtension.tsx @@ -0,0 +1,64 @@ +/* + * 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 { + getComponentData, + RouteRef as LegacyRouteRef, +} from '@backstage/core-plugin-api'; +import { + ExtensionDefinition, + PageBlueprint, +} from '@backstage/frontend-plugin-api'; +import kebabCase from 'lodash/kebabCase'; +import { convertLegacyRouteRef } from './convertLegacyRouteRef'; +import { ComponentType } from 'react'; +import React from 'react'; +import { compatWrapper } from './compatWrapper'; + +/** @public */ +export function convertLegacyPageExtension( + LegacyExtension: ComponentType<{}>, + overrides?: { + name?: string; + defaultPath?: string; + }, +): ExtensionDefinition { + const element = ; + + const extName = getComponentData(element, 'core.extensionName'); + if (!extName) { + throw new Error('Extension has no name'); + } + + const mountPoint = getComponentData( + element, + 'core.mountPoint', + ); + + const name = extName.endsWith('Page') + ? extName.slice(0, -'Page'.length) + : extName; + const kebabName = kebabCase(name); + + return PageBlueprint.make({ + name: overrides?.name ?? kebabName, + params: { + defaultPath: overrides?.defaultPath ?? `/${kebabName}`, + routeRef: mountPoint && convertLegacyRouteRef(mountPoint), + loader: async () => compatWrapper(element), + }, + }); +} diff --git a/packages/core-compat-api/src/convertLegacyPlugin.test.tsx b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx new file mode 100644 index 0000000000..657ee91725 --- /dev/null +++ b/packages/core-compat-api/src/convertLegacyPlugin.test.tsx @@ -0,0 +1,91 @@ +/* + * 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 { + createPlugin as createLegacyPlugin, + createRouteRef as createLegacyRouteRef, + createExternalRouteRef as createLegacyExternalRouteRef, + createApiFactory, + createApiRef, +} from '@backstage/core-plugin-api'; +import { convertLegacyPlugin } from './convertLegacyPlugin'; +import { PageBlueprint } from '@backstage/frontend-plugin-api'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalBackstagePlugin } from '../../frontend-plugin-api/src/wiring/createFrontendPlugin'; + +describe('convertLegacyPlugin', () => { + it('should convert a plain legacy plugin to a new plugin', () => { + expect( + convertLegacyPlugin(createLegacyPlugin({ id: 'test' }), { + extensions: [], + }), + ).toMatchInlineSnapshot(` + { + "$$type": "@backstage/BackstagePlugin", + "extensions": [], + "externalRoutes": {}, + "featureFlags": [], + "getExtension": [Function], + "id": "test", + "routes": {}, + "toString": [Function], + "version": "v1", + "withOverrides": [Function], + } + `); + }); + + it('should convert a legacy plugin with options to a new plugin', () => { + const apiRef = createApiRef({ id: 'plugin.test.client' }); + + const routeRef = createLegacyRouteRef({ id: 'test' }); + const extRouteRef = createLegacyExternalRouteRef({ id: 'testExt' }); + + const converted = convertLegacyPlugin( + createLegacyPlugin({ + id: 'test', + apis: [createApiFactory(apiRef, 'hello')], + routes: { test: routeRef }, + externalRoutes: { + testExt: extRouteRef, + }, + featureFlags: [{ name: 'test-flag' }], + }), + { + extensions: [ + PageBlueprint.make({ + params: { defaultPath: '/test', loader: async () => ({} as any) }, + }), + ], + }, + ); + + const internalConverted = toInternalBackstagePlugin(converted); + + expect(internalConverted.id).toBe('test'); + expect(internalConverted.routes).toEqual({ + test: routeRef, + }); + expect(internalConverted.externalRoutes).toEqual({ + testExt: extRouteRef, + }); + expect(internalConverted.featureFlags).toEqual([{ name: 'test-flag' }]); + expect(internalConverted.extensions.map(e => e.id)).toEqual([ + 'api:plugin.test.client', + 'page:test', + ]); + }); +}); diff --git a/packages/core-compat-api/src/convertLegacyPlugin.ts b/packages/core-compat-api/src/convertLegacyPlugin.ts new file mode 100644 index 0000000000..5856108f29 --- /dev/null +++ b/packages/core-compat-api/src/convertLegacyPlugin.ts @@ -0,0 +1,41 @@ +/* + * 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 { BackstagePlugin as LegacyBackstagePlugin } from '@backstage/core-plugin-api'; +import { + ApiBlueprint, + ExtensionDefinition, + BackstagePlugin as NewBackstagePlugin, + createFrontendPlugin, +} from '@backstage/frontend-plugin-api'; +import { convertLegacyRouteRefs } from './convertLegacyRouteRef'; + +/** @public */ +export function convertLegacyPlugin( + legacyPlugin: LegacyBackstagePlugin, + options: { extensions: ExtensionDefinition[] }, +): NewBackstagePlugin { + const apiExtensions = Array.from(legacyPlugin.getApis()).map(factory => + ApiBlueprint.make({ namespace: factory.api.id, params: { factory } }), + ); + return createFrontendPlugin({ + id: legacyPlugin.getId(), + featureFlags: [...legacyPlugin.getFeatureFlags()], + routes: convertLegacyRouteRefs(legacyPlugin.routes ?? {}), + externalRoutes: convertLegacyRouteRefs(legacyPlugin.externalRoutes ?? {}), + extensions: [...apiExtensions, ...options.extensions], + }); +} diff --git a/packages/core-compat-api/src/convertLegacyRouteRef.test.ts b/packages/core-compat-api/src/convertLegacyRouteRef.test.ts new file mode 100644 index 0000000000..f09060212c --- /dev/null +++ b/packages/core-compat-api/src/convertLegacyRouteRef.test.ts @@ -0,0 +1,203 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + RouteRef as OldRouteRef, + SubRouteRef as OldSubRouteRef, + ExternalRouteRef as OldExternalRouteRef, + createRouteRef as createOldRouteRef, + createSubRouteRef as createOldSubRouteRef, + createExternalRouteRef as createOldExternalRouteRef, +} from '@backstage/core-plugin-api'; +import { + RouteRef as NewRouteRef, + SubRouteRef as NewSubRouteRef, + ExternalRouteRef as NewExternalRouteRef, + createRouteRef as createNewRouteRef, + createSubRouteRef as createNewSubRouteRef, + createExternalRouteRef as createNewExternalRouteRef, +} from '@backstage/frontend-plugin-api'; +import { convertLegacyRouteRef } from './convertLegacyRouteRef'; + +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalRouteRef as toInternalNewRouteRef } from '../../frontend-plugin-api/src/routing/RouteRef'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalSubRouteRef as toInternalNewSubRouteRef } from '../../frontend-plugin-api/src/routing/SubRouteRef'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { toInternalExternalRouteRef as toInternalNewExternalRouteRef } from '../../frontend-plugin-api/src/routing/ExternalRouteRef'; + +describe('convertLegacyRouteRef', () => { + it('converts old to new', () => { + const ref1 = createOldRouteRef({ id: 'ref1' }); + const ref2 = createOldRouteRef({ id: 'ref2', params: ['p1', 'p2'] }); + const ref1sub1 = createOldSubRouteRef({ + id: 'sub1', + parent: ref1, + path: '/sub1', + }); + const ref1sub2 = createOldSubRouteRef({ + id: 'sub2', + parent: ref1, + path: '/sub2/:p3', + }); + const ref2sub1 = createOldSubRouteRef({ + id: 'sub1', + parent: ref2, + path: '/sub1/:p3', + }); + const ref3 = createOldExternalRouteRef({ + id: 'ref3', + }); + const ref4 = createOldExternalRouteRef({ + id: 'ref4', + optional: true, + defaultTarget: 'ref2', + params: ['p1', 'p2'], + }); + + const ref1Converted: NewRouteRef = convertLegacyRouteRef(ref1); + const ref2Converted: NewRouteRef = convertLegacyRouteRef(ref2); + const ref1sub1Converted: NewSubRouteRef = convertLegacyRouteRef(ref1sub1); + const ref1sub2Converted: NewSubRouteRef = convertLegacyRouteRef(ref1sub2); + const ref2sub1Converted: NewSubRouteRef = convertLegacyRouteRef(ref2sub1); + const ref3Converted: NewExternalRouteRef = convertLegacyRouteRef(ref3); + const ref4Converted: NewExternalRouteRef = convertLegacyRouteRef(ref4); + + // Check for reference equality + expect(ref1).toBe(ref1Converted); + expect(ref2).toBe(ref2Converted); + expect(ref1sub1).toBe(ref1sub1Converted); + expect(ref1sub2).toBe(ref1sub2Converted); + expect(ref2sub1).toBe(ref2sub1Converted); + expect(ref3).toBe(ref3Converted); + expect(ref4).toBe(ref4Converted); + + const ref1Internal = toInternalNewRouteRef(ref1Converted); + const ref2Internal = toInternalNewRouteRef(ref2Converted); + const ref1sub1Internal = toInternalNewSubRouteRef(ref1sub1Converted); + const ref1sub2Internal = toInternalNewSubRouteRef(ref1sub2Converted); + const ref2sub1Internal = toInternalNewSubRouteRef(ref2sub1Converted); + const ref3Internal = toInternalNewExternalRouteRef(ref3Converted); + const ref4Internal = toInternalNewExternalRouteRef(ref4Converted); + + expect(ref1Internal.getDescription()).toBe( + 'routeRef{type=absolute,id=ref1}', + ); + expect(ref1Internal.getParams()).toEqual([]); + expect(ref2Internal.getDescription()).toBe( + 'routeRef{type=absolute,id=ref2}', + ); + expect(ref2Internal.getParams()).toEqual(['p1', 'p2']); + + expect(ref1sub1Internal.getDescription()).toBe( + 'routeRef{type=sub,id=sub1}', + ); + expect(ref1sub1Internal.getParams()).toEqual([]); + expect(ref1sub1Internal.getParent()).toBe(ref1); + expect(ref1sub2Internal.getDescription()).toBe( + 'routeRef{type=sub,id=sub2}', + ); + expect(ref1sub2Internal.getParams()).toEqual(['p3']); + expect(ref1sub2Internal.getParent()).toBe(ref1); + expect(ref2sub1Internal.getDescription()).toBe( + 'routeRef{type=sub,id=sub1}', + ); + expect(ref2sub1Internal.getParams()).toEqual(['p1', 'p2', 'p3']); + expect(ref2sub1Internal.getParent()).toBe(ref2); + + expect(ref3Internal.getDefaultTarget()).toBe(undefined); + expect(ref3Internal.getDescription()).toBe( + 'routeRef{type=external,id=ref3}', + ); + expect(ref3Internal.getParams()).toEqual([]); + expect(ref4Internal.getDefaultTarget()).toBe('ref2'); + expect(ref4Internal.getDescription()).toBe( + 'routeRef{type=external,id=ref4}', + ); + expect(ref4Internal.getParams()).toEqual(['p1', 'p2']); + }); + + it('converts new to old', () => { + const ref1 = createNewRouteRef(); + const ref2 = createNewRouteRef({ params: ['p1', 'p2'] }); + const ref1sub1 = createNewSubRouteRef({ + parent: ref1, + path: '/sub1', + }); + const ref1sub2 = createNewSubRouteRef({ + parent: ref1, + path: '/sub2/:p3', + }); + const ref2sub1 = createNewSubRouteRef({ + parent: ref2, + path: '/sub1/:p3', + }); + const ref3 = createNewExternalRouteRef(); + const ref4 = createNewExternalRouteRef({ + defaultTarget: 'ref2', + params: ['p1', 'p2'], + }); + + const ref1Converted: OldRouteRef = convertLegacyRouteRef(ref1); + const ref2Converted: OldRouteRef = convertLegacyRouteRef(ref2); + const ref1sub1Converted: OldSubRouteRef = convertLegacyRouteRef(ref1sub1); + const ref1sub2Converted: OldSubRouteRef = convertLegacyRouteRef(ref1sub2); + const ref2sub1Converted: OldSubRouteRef = convertLegacyRouteRef(ref2sub1); + const ref3Converted: OldExternalRouteRef = convertLegacyRouteRef(ref3); + const ref4Converted: OldExternalRouteRef = convertLegacyRouteRef(ref4); + + // Check for reference equality + expect(ref1).toBe(ref1Converted); + expect(ref2).toBe(ref2Converted); + expect(ref1sub1).toBe(ref1sub1Converted); + expect(ref1sub2).toBe(ref1sub2Converted); + expect(ref2sub1).toBe(ref2sub1Converted); + expect(ref3).toBe(ref3Converted); + expect(ref4).toBe(ref4Converted); + + expect(String(ref1Converted)).toMatch(/^RouteRef\{created at '.*'\}$/); + expect(ref1Converted.params).toEqual([]); + expect(String(ref2Converted)).toMatch(/^RouteRef\{created at '.*'\}$/); + expect(ref2Converted.params).toEqual(['p1', 'p2']); + + expect(String(ref1sub1Converted)).toMatch( + /^SubRouteRef\{at \/sub1 with parent created at '.*'\}$/, + ); + expect(ref1sub1Converted.params).toEqual([]); + expect(ref1sub1Converted.parent).toBe(ref1); + expect(String(ref1sub2Converted)).toMatch( + /^SubRouteRef\{at \/sub2\/:p3 with parent created at '.*'\}$/, + ); + expect(ref1sub2Converted.params).toEqual(['p3']); + expect(ref1sub2Converted.parent).toBe(ref1); + expect(String(ref2sub1Converted)).toMatch( + /^SubRouteRef\{at \/sub1\/:p3 with parent created at '.*'\}$/, + ); + expect(ref2sub1Converted.params).toEqual(['p1', 'p2', 'p3']); + expect(ref2sub1Converted.parent).toBe(ref2); + + expect(String(ref3Converted)).toMatch( + /^ExternalRouteRef\{created at '.*'\}$/, + ); + expect(ref3Converted.params).toEqual([]); + expect(ref3Converted.optional).toBe(true); + expect(String(ref4Converted)).toMatch( + /^ExternalRouteRef\{created at '.*'\}$/, + ); + expect(ref4Converted.params).toEqual(['p1', 'p2']); + expect(ref4Converted.optional).toBe(true); + }); +}); diff --git a/packages/core-compat-api/src/convertLegacyRouteRef.ts b/packages/core-compat-api/src/convertLegacyRouteRef.ts index c967f9e9b5..40a95a81ff 100644 --- a/packages/core-compat-api/src/convertLegacyRouteRef.ts +++ b/packages/core-compat-api/src/convertLegacyRouteRef.ts @@ -51,8 +51,8 @@ export type ToNewRouteRef< ? RouteRef : T extends LegacySubRouteRef ? SubRouteRef - : T extends LegacyExternalRouteRef - ? ExternalRouteRef + : T extends LegacyExternalRouteRef + ? ExternalRouteRef : never; /** @@ -109,23 +109,115 @@ export function convertLegacyRouteRef( * * In the future the legacy createExternalRouteRef will instead create refs compatible with both systems. */ -export function convertLegacyRouteRef< - TParams extends AnyRouteRefParams, - TOptional extends boolean, ->( - ref: LegacyExternalRouteRef, -): ExternalRouteRef; +export function convertLegacyRouteRef( + ref: LegacyExternalRouteRef, +): ExternalRouteRef; +/** + * A temporary helper to convert a new route ref to the legacy system. + * + * @public + * @remarks + * + * In the future the legacy createRouteRef will instead create refs compatible with both systems. + */ +export function convertLegacyRouteRef( + ref: RouteRef, +): LegacyRouteRef; + +/** + * A temporary helper to convert a new sub route ref to the legacy system. + * + * @public + * @remarks + * + * In the future the legacy createSubRouteRef will instead create refs compatible with both systems. + */ +export function convertLegacyRouteRef( + ref: SubRouteRef, +): LegacySubRouteRef; + +/** + * A temporary helper to convert a new external route ref to the legacy system. + * + * @public + * @remarks + * + * In the future the legacy createExternalRouteRef will instead create refs compatible with both systems. + */ +export function convertLegacyRouteRef( + ref: ExternalRouteRef, +): LegacyExternalRouteRef; export function convertLegacyRouteRef( - ref: LegacyRouteRef | LegacySubRouteRef | LegacyExternalRouteRef, -): RouteRef | SubRouteRef | ExternalRouteRef { + ref: + | LegacyRouteRef + | LegacySubRouteRef + | LegacyExternalRouteRef + | RouteRef + | SubRouteRef + | ExternalRouteRef, +): + | RouteRef + | SubRouteRef + | ExternalRouteRef + | LegacyRouteRef + | LegacySubRouteRef + | LegacyExternalRouteRef { + const isNew = '$$type' in ref; + const oldType = (ref as unknown as { [routeRefType]: unknown })[routeRefType]; + // Ref has already been converted - if ('$$type' in ref) { - return ref as unknown as RouteRef | SubRouteRef | ExternalRouteRef; + if (isNew && oldType) { + return ref as any; } - const type = (ref as unknown as { [routeRefType]: unknown })[routeRefType]; + if (isNew) { + return convertNewToOld( + ref as unknown as RouteRef | SubRouteRef | ExternalRouteRef, + ); + } + return convertOldToNew(ref, oldType); +} + +function convertNewToOld( + ref: RouteRef | SubRouteRef | ExternalRouteRef, +): LegacyRouteRef | LegacySubRouteRef | LegacyExternalRouteRef { + if (ref.$$type === '@backstage/RouteRef') { + const newRef = toInternalRouteRef(ref); + return Object.assign(ref, { + [routeRefType]: 'absolute', + params: newRef.getParams(), + title: newRef.getDescription(), + } as Omit) as unknown as LegacyRouteRef; + } + if (ref.$$type === '@backstage/SubRouteRef') { + const newRef = toInternalSubRouteRef(ref); + return Object.assign(ref, { + [routeRefType]: 'sub', + parent: convertLegacyRouteRef(newRef.getParent()), + params: newRef.getParams(), + } as Omit) as unknown as LegacySubRouteRef; + } + if (ref.$$type === '@backstage/ExternalRouteRef') { + const newRef = toInternalExternalRouteRef(ref); + return Object.assign(ref, { + [routeRefType]: 'external', + optional: true, + params: newRef.getParams(), + defaultTarget: newRef.getDefaultTarget(), + } as Omit) as unknown as LegacyExternalRouteRef; + } + + throw new Error( + `Failed to convert route ref, unknown type '${(ref as any).$$type}'`, + ); +} + +function convertOldToNew( + ref: LegacyRouteRef | LegacySubRouteRef | LegacyExternalRouteRef, + type: unknown, +): RouteRef | SubRouteRef | ExternalRouteRef { if (type === 'absolute') { const legacyRef = ref as LegacyRouteRef; const legacyRefStr = String(legacyRef); @@ -185,7 +277,6 @@ export function convertLegacyRouteRef( const newRef = toInternalExternalRouteRef( createExternalRouteRef<{ [key in string]: string }>({ params: legacyRef.params as string[], - optional: legacyRef.optional, defaultTarget: 'getDefaultTarget' in legacyRef ? (legacyRef.getDefaultTarget as () => string | undefined)() @@ -196,7 +287,6 @@ export function convertLegacyRouteRef( $$type: '@backstage/ExternalRouteRef' as const, version: 'v1', T: newRef.T, - optional: newRef.optional, getParams() { return newRef.getParams(); }, diff --git a/packages/core-compat-api/src/index.ts b/packages/core-compat-api/src/index.ts index 88e1892eac..7f99a7ab7a 100644 --- a/packages/core-compat-api/src/index.ts +++ b/packages/core-compat-api/src/index.ts @@ -18,6 +18,8 @@ export * from './compatWrapper'; export * from './apis'; export { convertLegacyApp } from './convertLegacyApp'; +export { convertLegacyPlugin } from './convertLegacyPlugin'; +export { convertLegacyPageExtension } from './convertLegacyPageExtension'; export { convertLegacyRouteRef, convertLegacyRouteRefs, diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index f42b6396ce..f50b830702 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/core-components +## 0.14.10 + +### Patch Changes + +- 678971a: Move the `Link` component to the `RoutedTabs` instead of the `HeaderTabs` component +- 13a9c63: Corrected the documentation for the GCP IAP auth module and updated the configuration to follow proxy configuration conventions by ignoring authEnv +- 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.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..3de470cfba 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.2.22 + +### Patch Changes + +- 3fca643: Added method `listBranchesByRepository` to `BitbucketCloudClient` +- Updated dependencies + - @backstage/integration@1.14.0 + +## 0.2.22-next.1 + +### Patch Changes + +- 3fca643: Added method `listBranchesByRepository` to `BitbucketCloudClient` +- Updated dependencies + - @backstage/integration@1.14.0-next.0 + +## 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/api-report.md b/plugins/bitbucket-cloud-common/api-report.md index 34c245facc..833a367c03 100644 --- a/plugins/bitbucket-cloud-common/api-report.md +++ b/plugins/bitbucket-cloud-common/api-report.md @@ -12,6 +12,12 @@ export class BitbucketCloudClient { config: BitbucketCloudIntegrationConfig, ): BitbucketCloudClient; // (undocumented) + listBranchesByRepository( + repository: string, + workspace: string, + options?: FilterAndSortOptions & PartialResponseOptions, + ): WithPagination; + // (undocumented) listProjectsByWorkspace( workspace: string, options?: FilterAndSortOptions & PartialResponseOptions, @@ -209,6 +215,9 @@ export namespace Models { size?: number; values?: Array | Set; } + export interface PaginatedBranches extends Paginated { + values?: Set; + } export interface PaginatedProjects extends Paginated { values?: Set; } diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index 0e98fcc505..eeb5bd6ff6 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", "description": "Common functionalities for bitbucket-cloud plugins", "backstage": { "role": "common-library", diff --git a/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.test.ts b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.test.ts index f625f0daf8..7558c92138 100644 --- a/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.test.ts +++ b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.test.ts @@ -162,4 +162,33 @@ describe('BitbucketCloudClient', () => { expect(results).toHaveLength(1); expect(results[0].slug).toEqual('workspace1'); }); + + it('listBranchesByRepository', async () => { + server.use( + rest.get( + 'https://api.bitbucket.org/2.0/repositories/workspace1/repo1/refs/branches', + (_, res, ctx) => { + const response = { + values: [ + { + type: 'branch', + name: 'branch1', + } as Models.Branch, + ], + }; + return res(ctx.json(response)); + }, + ), + ); + + const pagination = client.listBranchesByRepository('repo1', 'workspace1'); + + const results = []; + for await (const result of pagination.iterateResults()) { + results.push(result); + } + + expect(results).toHaveLength(1); + expect(results[0].name).toEqual('branch1'); + }); }); diff --git a/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts index f1b7d60e41..86f56dd70d 100644 --- a/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts +++ b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts @@ -95,6 +95,26 @@ export class BitbucketCloudClient { ); } + listBranchesByRepository( + repository: string, + workspace: string, + options?: FilterAndSortOptions & PartialResponseOptions, + ): WithPagination { + const workspaceEnc = encodeURIComponent(workspace); + + return new WithPagination( + paginationOptions => + this.createUrl( + `/repositories/${workspaceEnc}/${repository}/refs/branches`, + { + ...paginationOptions, + ...options, + }, + ), + url => this.getTypeMapped(url), + ); + } + private createUrl(endpoint: string, options?: RequestOptions): URL { const request = new URL(this.config.apiBaseUrl + endpoint); for (const key in options) { diff --git a/plugins/bitbucket-cloud-common/src/models/index.ts b/plugins/bitbucket-cloud-common/src/models/index.ts index 2cdc143d06..28b1b45e1b 100644 --- a/plugins/bitbucket-cloud-common/src/models/index.ts +++ b/plugins/bitbucket-cloud-common/src/models/index.ts @@ -275,6 +275,17 @@ export namespace Models { values?: Set; } + /** + * A paginated list of branches. + * @public + */ + export interface PaginatedBranches extends Paginated { + /** + * The values of the current page. + */ + values?: Set; + } + /** * Object describing a user's role on resources like commits or pull requests. * @public diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index bd5663758a..dcad12d670 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,134 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.4.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### 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. +- 9342ac8: Removed unused dependency +- 389f5a4: Update deprecated url-reader-related imports. +- 90a7340: `AwsOrganizationCloudAccountProcessor` configuration field `roleArn` is deprecated in favor of new field `accountId` +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.3.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-kubernetes-common@0.8.2-next.2 + +## 0.3.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.0 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 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-alpha.md b/plugins/catalog-backend-module-aws/api-report-alpha.md index 1c01744215..495abb6b48 100644 --- a/plugins/catalog-backend-module-aws/api-report-alpha.md +++ b/plugins/catalog-backend-module-aws/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const catalogModuleAwsS3EntityProvider: BackendFeatureCompat; +const catalogModuleAwsS3EntityProvider: BackendFeature; export default catalogModuleAwsS3EntityProvider; // (No @packageDocumentation comment for this package) diff --git a/plugins/catalog-backend-module-aws/api-report.md b/plugins/catalog-backend-module-aws/api-report.md index 5098ab5c32..98d757270e 100644 --- a/plugins/catalog-backend-module-aws/api-report.md +++ b/plugins/catalog-backend-module-aws/api-report.md @@ -15,9 +15,9 @@ import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; -import { TaskRunner } from '@backstage/backend-tasks'; -import { UrlReader } from '@backstage/backend-common'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public export const ANNOTATION_AWS_ACCOUNT_ID: string; @@ -75,7 +75,7 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { // @public @deprecated export class AwsS3DiscoveryProcessor implements CatalogProcessor { - constructor(reader: UrlReader); + constructor(reader: UrlReaderService); // (undocumented) getProcessorName(): string; // (undocumented) @@ -96,8 +96,8 @@ export class AwsS3EntityProvider implements EntityProvider { configRoot: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): AwsS3EntityProvider[]; // (undocumented) @@ -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..3a6178e614 100644 --- a/plugins/catalog-backend-module-aws/config.d.ts +++ b/plugins/catalog-backend-module-aws/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -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; }; }; }; @@ -63,7 +69,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; } | { [name: string]: { @@ -91,7 +97,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 3b75c3cfcd..2de0cf81a7 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.4.0", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module", @@ -57,9 +57,8 @@ "@aws-sdk/credential-providers": "^3.350.0", "@aws-sdk/middleware-endpoint": "^3.347.0", "@aws-sdk/types": "^3.347.0", - "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", 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/module/catalogModuleAwsS3EntityProvider.test.ts b/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.test.ts index 9165eb4322..b8a4256092 100644 --- a/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.test.ts +++ b/plugins/catalog-backend-module-aws/src/module/catalogModuleAwsS3EntityProvider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { catalogModuleAwsS3EntityProvider } from './catalogModuleAwsS3EntityProvider'; @@ -23,7 +23,7 @@ import { AwsS3EntityProvider } from '../providers'; describe('catalogModuleAwsS3EntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { 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/processors/AwsS3DiscoveryProcessor.test.ts b/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.test.ts index 804a17f4f0..70b4ddf2d4 100644 --- a/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReaders } from '@backstage/backend-common'; +import { UrlReaders } from '@backstage/backend-defaults/urlReader'; import { ConfigReader } from '@backstage/config'; import { AwsS3DiscoveryProcessor } from './AwsS3DiscoveryProcessor'; import { diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.ts b/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.ts index d6722d046e..dfff1f5d4f 100644 --- a/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsS3DiscoveryProcessor.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { isError } from '@backstage/errors'; import { CatalogProcessor, @@ -34,7 +34,7 @@ import limiterFactory from 'p-limit'; * @deprecated Use the `AwsS3EntityProvider` instead (see https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-aws/CHANGELOG.md#014). */ export class AwsS3DiscoveryProcessor implements CatalogProcessor { - constructor(private readonly reader: UrlReader) {} + constructor(private readonly reader: UrlReaderService) {} getProcessorName(): string { return 'AwsS3DiscoveryProcessor'; diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts index 5367c17742..c5215438a7 100644 --- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts +++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts @@ -15,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { AwsS3EntityProvider } from './AwsS3EntityProvider'; @@ -27,14 +27,14 @@ import 'aws-sdk-client-mock-jest'; import { ListObjectsV2Command, S3Client } from '@aws-sdk/client-s3'; import { mockServices } from '@backstage/backend-test-utils'; -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -117,7 +117,7 @@ describe('AwsS3EntityProvider', () => { if (scheduleInConfig) { schedulingConfig.scheduler = { createScheduledTaskRunner: (_: any) => schedule, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; } else { schedulingConfig.schedule = schedule; } @@ -392,7 +392,7 @@ describe('AwsS3EntityProvider', () => { it('fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader({ catalog: { providers: { diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts index 91ba76b695..f9246d01dd 100644 --- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts +++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AwsS3Integration, ScmIntegrations } from '@backstage/integration'; import { @@ -36,7 +35,11 @@ import { AwsCredentialsManager, DefaultAwsCredentialsManager, } from '@backstage/integration-aws-node'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; // TODO: event-based updates using S3 events (+ queue like SQS)? /** @@ -57,8 +60,8 @@ export class AwsS3EntityProvider implements EntityProvider { configRoot: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): AwsS3EntityProvider[] { const providerConfigs = readAwsS3Configs(configRoot); @@ -107,7 +110,7 @@ export class AwsS3EntityProvider implements EntityProvider { private readonly integration: AwsS3Integration, private readonly awsCredentialsManager: AwsCredentialsManager, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, ) { this.logger = logger.child({ target: this.getProviderName(), @@ -116,7 +119,9 @@ export class AwsS3EntityProvider implements EntityProvider { this.scheduleFn = this.createScheduleFn(taskRunner); } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ @@ -154,6 +159,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-aws/src/providers/config.ts b/plugins/catalog-backend-module-aws/src/providers/config.ts index 1ab52f9fd4..b9c49599f6 100644 --- a/plugins/catalog-backend-module-aws/src/providers/config.ts +++ b/plugins/catalog-backend-module-aws/src/providers/config.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AwsS3Config } from './types'; +import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api'; const DEFAULT_PROVIDER_ID = 'default'; @@ -49,7 +49,9 @@ function readAwsS3Config(id: string, config: Config): AwsS3Config { const accountId = config.getOptionalString('accountId'); const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-aws/src/providers/types.ts b/plugins/catalog-backend-module-aws/src/providers/types.ts index 8e12b5f096..da470e4dfe 100644 --- a/plugins/catalog-backend-module-aws/src/providers/types.ts +++ b/plugins/catalog-backend-module-aws/src/providers/types.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; export type AwsS3Config = { id: string; bucketName: string; prefix?: string; region?: string; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; accountId?: string; }; diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 598636a239..1d9e799c20 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,101 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + +## 0.1.43-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## 0.1.43-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + +## 0.1.43-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/config@1.2.0 + +## 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/api-report-alpha.md b/plugins/catalog-backend-module-azure/api-report-alpha.md index 8f6c4f0317..fc48df304a 100644 --- a/plugins/catalog-backend-module-azure/api-report-alpha.md +++ b/plugins/catalog-backend-module-azure/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const catalogModuleAzureDevOpsEntityProvider: BackendFeatureCompat; +const catalogModuleAzureDevOpsEntityProvider: BackendFeature; export default catalogModuleAzureDevOpsEntityProvider; // (No @packageDocumentation comment for this package) diff --git a/plugins/catalog-backend-module-azure/api-report.md b/plugins/catalog-backend-module-azure/api-report.md index a449909dbf..9220b08ab4 100644 --- a/plugins/catalog-backend-module-azure/api-report.md +++ b/plugins/catalog-backend-module-azure/api-report.md @@ -10,9 +10,9 @@ import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { TaskRunner } from '@backstage/backend-tasks'; // @public export class AzureDevOpsDiscoveryProcessor implements CatalogProcessor { @@ -46,8 +46,8 @@ export class AzureDevOpsEntityProvider implements EntityProvider { configRoot: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): AzureDevOpsEntityProvider[]; // (undocumented) diff --git a/plugins/catalog-backend-module-azure/config.d.ts b/plugins/catalog-backend-module-azure/config.d.ts index 757ed2bfd3..f6c3bab068 100644 --- a/plugins/catalog-backend-module-azure/config.d.ts +++ b/plugins/catalog-backend-module-azure/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -52,7 +52,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index bf0b69f177..186e12e8e4 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.2.0", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module", @@ -51,14 +51,12 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "uuid": "^9.0.0" }, "devDependencies": { 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-azure/src/module/catalogModuleAzureDevOpsEntityProvider.test.ts b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.test.ts index e406c61e07..32eb190460 100644 --- a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.test.ts +++ b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { catalogModuleAzureDevOpsEntityProvider } from './catalogModuleAzureDevOpsEntityProvider'; @@ -23,7 +23,7 @@ import { AzureDevOpsEntityProvider } from '../providers'; describe('catalogModuleAzureDevOpsEntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts index b7ada509a7..0c462f2906 100644 --- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts +++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts @@ -15,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { CodeSearchResultItem } from '../lib'; @@ -29,14 +29,14 @@ import { mockServices } from '@backstage/backend-test-utils'; jest.mock('../lib'); const mockCodeSearch = codeSearch as jest.MockedFunction; -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -83,7 +83,7 @@ describe('AzureDevOpsEntityProvider', () => { if (scheduleInConfig) { schedulingConfig.scheduler = { createScheduledTaskRunner: (_: any) => schedule, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; } else { schedulingConfig.schedule = schedule; } @@ -275,7 +275,7 @@ describe('AzureDevOpsEntityProvider', () => { it('fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader({ catalog: { providers: { diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts index 60c7354636..cd3b4bbbad 100644 --- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts +++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AzureDevOpsCredentialsProvider, @@ -32,7 +31,11 @@ import { readAzureDevOpsConfigs } from './config'; import { AzureDevOpsConfig } from './types'; import * as uuid from 'uuid'; import { codeSearch, CodeSearchResultItem } from '../lib'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + SchedulerService, + SchedulerServiceTaskRunner, + LoggerService, +} from '@backstage/backend-plugin-api'; /** * Provider which discovers catalog files within an Azure DevOps repositories. @@ -50,8 +53,8 @@ export class AzureDevOpsEntityProvider implements EntityProvider { configRoot: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): AzureDevOpsEntityProvider[] { const providerConfigs = readAzureDevOpsConfigs(configRoot); @@ -99,7 +102,7 @@ export class AzureDevOpsEntityProvider implements EntityProvider { private readonly integration: AzureIntegration, private readonly credentialsProvider: AzureDevOpsCredentialsProvider, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, ) { this.logger = logger.child({ target: this.getProviderName(), @@ -108,7 +111,9 @@ export class AzureDevOpsEntityProvider implements EntityProvider { this.scheduleFn = this.createScheduleFn(taskRunner); } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-azure/src/providers/config.ts b/plugins/catalog-backend-module-azure/src/providers/config.ts index 3c10a50863..17616b06bc 100644 --- a/plugins/catalog-backend-module-azure/src/providers/config.ts +++ b/plugins/catalog-backend-module-azure/src/providers/config.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; +import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { AzureDevOpsConfig } from './types'; @@ -45,7 +45,9 @@ function readAzureDevOpsConfig(id: string, config: Config): AzureDevOpsConfig { const path = config.getOptionalString('path') || '/catalog-info.yaml'; const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-azure/src/providers/types.ts b/plugins/catalog-backend-module-azure/src/providers/types.ts index 3cd827b380..5ca9dc94dc 100644 --- a/plugins/catalog-backend-module-azure/src/providers/types.ts +++ b/plugins/catalog-backend-module-azure/src/providers/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; export type AzureDevOpsConfig = { id: string; @@ -24,5 +24,5 @@ export type AzureDevOpsConfig = { repository: string; branch?: string; path: string; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; diff --git a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md index fa23d67c67..ccee7e4572 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,99 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/backend-openapi-utils@0.1.16 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.2.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-openapi-utils@0.1.16-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## 0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/backend-openapi-utils@0.1.16-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-openapi-utils@0.1.16-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 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/api-report.md b/plugins/catalog-backend-module-backstage-openapi/api-report.md index 247cc4ba8e..06334182cb 100644 --- a/plugins/catalog-backend-module-backstage-openapi/api-report.md +++ b/plugins/catalog-backend-module-backstage-openapi/api-report.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @public (undocumented) -const catalogModuleInternalOpenApiSpec: BackendFeatureCompat; +const catalogModuleInternalOpenApiSpec: BackendFeature; export { catalogModuleInternalOpenApiSpec }; export default catalogModuleInternalOpenApiSpec; diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 4d06de5ddb..ca1e220c62 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.3.0", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", @@ -35,7 +35,6 @@ "dependencies": { "@backstage/backend-openapi-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts index fe6425ffb9..959d07ad76 100644 --- a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts +++ b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts @@ -37,10 +37,11 @@ import { AuthService, DiscoveryService, LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, } from '@backstage/backend-plugin-api'; import * as uuid from 'uuid'; import lodash from 'lodash'; -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; const HTTP_VERBS: (keyof PathItemObject)[] = [ 'get', @@ -163,7 +164,7 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { public readonly discovery: DiscoveryService, public readonly logger: LoggerService, public readonly auth: AuthService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, ) { this.scheduleFn = this.createScheduleFn(taskRunner); } @@ -173,7 +174,7 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { options: { discovery: DiscoveryService; logger: LoggerService; - schedule: PluginTaskScheduler; + schedule: SchedulerService; auth: AuthService; }, ) { @@ -204,7 +205,9 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider { return await this.scheduleFn(); } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index 2a977a4f7b..05b0f40fb7 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,127 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.3.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + +## 0.2.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @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-events-node@0.3.9-next.1 + +## 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/api-report-alpha.md b/plugins/catalog-backend-module-bitbucket-cloud/api-report-alpha.md index 8c91e1454f..34ec7a692a 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/api-report-alpha.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha (undocumented) -const catalogModuleBitbucketCloudEntityProvider: BackendFeatureCompat; +const catalogModuleBitbucketCloudEntityProvider: BackendFeature; export default catalogModuleBitbucketCloudEntityProvider; // (No @packageDocumentation comment for this package) diff --git a/plugins/catalog-backend-module-bitbucket-cloud/api-report.md b/plugins/catalog-backend-module-bitbucket-cloud/api-report.md index 22845cf260..bba0b754e2 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/api-report.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/api-report.md @@ -10,8 +10,8 @@ import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { Events } from '@backstage/plugin-bitbucket-cloud-common'; import { EventsService } from '@backstage/plugin-events-node'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; -import { TaskRunner } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { TokenManager } from '@backstage/backend-common'; // @public @@ -25,8 +25,8 @@ export class BitbucketCloudEntityProvider implements EntityProvider { catalogApi?: CatalogApi; events?: EventsService; logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; tokenManager?: TokenManager; }, ): BitbucketCloudEntityProvider[]; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts index fa7c070042..1051976b24 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -58,7 +58,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the discovery. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; } | { [name: string]: { @@ -91,7 +91,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the discovery. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index d0261ad01f..13ee89e71c 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.3.0", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", "backstage": { "role": "backend-plugin-module", @@ -53,7 +53,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts index bcf13e786d..12c747d090 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { createServiceFactory } from '@backstage/backend-plugin-api'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { + createServiceFactory, + SchedulerServiceTaskScheduleDefinition, +} from '@backstage/backend-plugin-api'; import { startTestBackend, mockServices } from '@backstage/backend-test-utils'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; @@ -35,7 +37,7 @@ describe('catalogModuleBitbucketCloudEntityProvider', () => { }, }); let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const catalogExtensionPointImpl = { addEntityProvider: (providers: any) => { @@ -56,7 +58,7 @@ describe('catalogModuleBitbucketCloudEntityProvider', () => { [catalogProcessingExtensionPoint, catalogExtensionPointImpl], ], features: [ - eventsServiceFactory(), + eventsServiceFactory, catalogModuleBitbucketCloudEntityProvider, mockServices.rootConfig.factory({ data: { diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts index 21d86ce8e6..179e74b8c0 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts @@ -40,23 +40,13 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({ events: eventsServiceRef, logger: coreServices.logger, scheduler: coreServices.scheduler, - tokenManager: coreServices.tokenManager, }, - async init({ - catalog, - catalogApi, - config, - events, - logger, - scheduler, - tokenManager, - }) { + async init({ catalog, catalogApi, config, events, logger, scheduler }) { const providers = BitbucketCloudEntityProvider.fromConfig(config, { catalogApi, events, logger, scheduler, - tokenManager, }); catalog.addEntityProvider(providers); 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..4fbbb67c7d 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 @@ -16,13 +16,13 @@ import { TokenManager } from '@backstage/backend-common'; import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskInvocationDefinition, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { CatalogApi } from '@backstage/catalog-client'; import { Entity, LocationEntity } from '@backstage/catalog-model'; @@ -40,14 +40,14 @@ import { BitbucketCloudEntityProvider, } from './BitbucketCloudEntityProvider'; -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -62,7 +62,7 @@ const logger = mockServices.logger.mock(); const server = setupServer(); describe('BitbucketCloudEntityProvider', () => { - setupRequestMockHandlers(server); + registerMswTestHooks(server); const simpleConfig = new ConfigReader({ catalog: { @@ -190,7 +190,7 @@ describe('BitbucketCloudEntityProvider', () => { }); it('fail with scheduler but no schedule config', () => { - const scheduler = jest.fn() as unknown as PluginTaskScheduler; + const scheduler = jest.fn() as unknown as SchedulerService; const config = new ConfigReader({ catalog: { providers: { @@ -214,7 +214,7 @@ describe('BitbucketCloudEntityProvider', () => { it('single simple provider config with schedule in config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader({ catalog: { providers: { diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts index 9135e0717f..6b19bea4f2 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts @@ -15,8 +15,11 @@ */ import { TokenManager } from '@backstage/backend-common'; -import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { LocationEntity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; @@ -80,8 +83,8 @@ export class BitbucketCloudEntityProvider implements EntityProvider { catalogApi?: CatalogApi; events?: EventsService; logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; tokenManager?: TokenManager; }, ): BitbucketCloudEntityProvider[] { @@ -124,7 +127,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider { config: BitbucketCloudEntityProviderConfig, integration: BitbucketCloudIntegration, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, catalogApi?: CatalogApi, events?: EventsService, tokenManager?: TokenManager, @@ -140,7 +143,9 @@ export class BitbucketCloudEntityProvider implements EntityProvider { this.tokenManager = tokenManager; } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn( + schedule: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = this.getTaskId(); return schedule.run({ diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.ts index def88b0951..5a806d2e0b 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProviderConfig.ts @@ -15,9 +15,9 @@ */ import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; @@ -31,7 +31,7 @@ export type BitbucketCloudEntityProviderConfig = { projectKey?: RegExp; repoSlug?: RegExp; }; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; export function readProviderConfigs( @@ -67,7 +67,9 @@ function readProviderConfig( const repoSlugPattern = config.getOptionalString('filters.repoSlug'); const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index f395e731ec..5946dace67 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,108 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.37-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## 0.1.37-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 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/api-report-alpha.md b/plugins/catalog-backend-module-bitbucket-server/api-report-alpha.md index 0f8da2c4d5..7edb314814 100644 --- a/plugins/catalog-backend-module-bitbucket-server/api-report-alpha.md +++ b/plugins/catalog-backend-module-bitbucket-server/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha (undocumented) -const catalogModuleBitbucketServerEntityProvider: BackendFeatureCompat; +const catalogModuleBitbucketServerEntityProvider: BackendFeature; export default catalogModuleBitbucketServerEntityProvider; // (No @packageDocumentation comment for this package) diff --git a/plugins/catalog-backend-module-bitbucket-server/api-report.md b/plugins/catalog-backend-module-bitbucket-server/api-report.md index e5987e44cf..211096639c 100644 --- a/plugins/catalog-backend-module-bitbucket-server/api-report.md +++ b/plugins/catalog-backend-module-bitbucket-server/api-report.md @@ -10,9 +10,9 @@ import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { LocationSpec } from '@backstage/plugin-catalog-node'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Response as Response_2 } from 'node-fetch'; -import { TaskRunner } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; // @public export class BitbucketServerClient { @@ -57,8 +57,8 @@ export class BitbucketServerEntityProvider implements EntityProvider { options: { logger: LoggerService; parser?: BitbucketServerLocationParser; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): BitbucketServerEntityProvider[]; // (undocumented) diff --git a/plugins/catalog-backend-module-bitbucket-server/config.d.ts b/plugins/catalog-backend-module-bitbucket-server/config.d.ts index b02e0e9a24..0f6889efa7 100644 --- a/plugins/catalog-backend-module-bitbucket-server/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-server/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -54,7 +54,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; } | { [name: string]: { @@ -86,7 +86,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 4d2e0a1e78..c53dd1a1c3 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.2.0", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", @@ -50,16 +50,14 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "@types/node-fetch": "^2.5.12", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "uuid": "^9.0.0" }, "devDependencies": { 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/module/catalogModuleBitbucketServerEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.test.ts index 5335b04e2d..fbd4fa7a36 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/module/catalogModuleBitbucketServerEntityProvider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { catalogModuleBitbucketServerEntityProvider } from './catalogModuleBitbucketServerEntityProvider'; @@ -23,7 +23,7 @@ import { BitbucketServerEntityProvider } from '../providers'; describe('catalogModuleBitbucketServerEntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { 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..05e0ea6a01 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 @@ -15,13 +15,13 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; @@ -30,14 +30,14 @@ import { setupServer } from 'msw/node'; import { BitbucketServerEntityProvider } from './BitbucketServerEntityProvider'; import { BitbucketServerPagedResponse } from '../lib'; -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -103,7 +103,7 @@ function setupStubs(projects: Project[], baseUrl: string) { } describe('BitbucketServerEntityProvider', () => { - setupRequestMockHandlers(server); + registerMswTestHooks(server); afterEach(() => { jest.clearAllMocks(); }); @@ -412,7 +412,7 @@ describe('BitbucketServerEntityProvider', () => { it('fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader({ catalog: { providers: { @@ -469,7 +469,7 @@ describe('BitbucketServerEntityProvider', () => { const schedule = new PersistingTaskRunner(); const scheduler = { createScheduledTaskRunner: (_: any) => schedule, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const entityProviderConnection: EntityProviderConnection = { applyMutation: jest.fn(), refresh: jest.fn(), diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts index 88f86eb87c..8e26d6a866 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; @@ -36,7 +35,11 @@ import { BitbucketServerLocationParser, defaultBitbucketServerLocationParser, } from './BitbucketServerLocationParser'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; /** * Discovers catalog files located in Bitbucket Server. @@ -59,8 +62,8 @@ export class BitbucketServerEntityProvider implements EntityProvider { options: { logger: LoggerService; parser?: BitbucketServerLocationParser; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): BitbucketServerEntityProvider[] { const integrations = ScmIntegrations.fromConfig(config); @@ -103,7 +106,7 @@ export class BitbucketServerEntityProvider implements EntityProvider { config: BitbucketServerEntityProviderConfig, integration: BitbucketServerIntegration, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, parser?: BitbucketServerLocationParser, ) { this.integration = integration; @@ -115,7 +118,9 @@ export class BitbucketServerEntityProvider implements EntityProvider { this.scheduleFn = this.createScheduleFn(taskRunner); } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts index 2f1bb7227d..018ff23069 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts @@ -15,9 +15,9 @@ */ import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; @@ -32,7 +32,7 @@ export type BitbucketServerEntityProviderConfig = { repoSlug?: RegExp; skipArchivedRepos?: boolean; }; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; export function readProviderConfigs( @@ -70,7 +70,9 @@ function readProviderConfig( ); const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index 5948b92338..7b6ca7f55e 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,99 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.1.24-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-kubernetes-common@0.8.2-next.2 + +## 0.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.0 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 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/api-report.md b/plugins/catalog-backend-module-gcp/api-report.md index ea9a73f0e6..6260d07c88 100644 --- a/plugins/catalog-backend-module-gcp/api-report.md +++ b/plugins/catalog-backend-module-gcp/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import * as container from '@google-cloud/container'; import { EntityProvider } from '@backstage/plugin-catalog-node'; @@ -12,7 +12,7 @@ import { LoggerService } from '@backstage/backend-plugin-api'; import { SchedulerService } from '@backstage/backend-plugin-api'; // @public -const catalogModuleGcpGkeEntityProvider: BackendFeatureCompat; +const catalogModuleGcpGkeEntityProvider: BackendFeature; export default catalogModuleGcpGkeEntityProvider; // @public diff --git a/plugins/catalog-backend-module-gcp/config.d.ts b/plugins/catalog-backend-module-gcp/config.d.ts index d6884d32ce..dd657420f7 100644 --- a/plugins/catalog-backend-module-gcp/config.d.ts +++ b/plugins/catalog-backend-module-gcp/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -37,7 +37,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule: TaskScheduleDefinitionConfig; + schedule: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 5cfcca3ba9..4793e6b167 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.2.0", "description": "A Backstage catalog backend module that helps integrate towards GCP", "backstage": { "role": "backend-plugin-module", @@ -51,9 +51,7 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", diff --git a/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts index 637963d8e3..b9eb4cf2aa 100644 --- a/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.test.ts @@ -15,7 +15,7 @@ */ import { GkeEntityProvider } from './GkeEntityProvider'; -import { TaskRunner } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import * as container from '@google-cloud/container'; import { ConfigReader } from '@backstage/config'; @@ -30,7 +30,7 @@ describe('GkeEntityProvider', () => { const taskRunner = { createScheduleFn: jest.fn(), run: jest.fn(), - } as TaskRunner; + } as SchedulerServiceTaskRunner; const schedulerMock = { createScheduledTaskRunner: jest.fn(), } as any; diff --git a/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts index d644a8383c..13bf245f4e 100644 --- a/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts +++ b/plugins/catalog-backend-module-gcp/src/providers/GkeEntityProvider.ts @@ -13,10 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - readTaskScheduleDefinitionFromConfig, - TaskRunner, -} from '@backstage/backend-tasks'; import { DeferredEntity, EntityProvider, @@ -31,7 +27,12 @@ import { ANNOTATION_KUBERNETES_DASHBOARD_PARAMETERS, } from '@backstage/plugin-kubernetes-common'; import { Config } from '@backstage/config'; -import { LoggerService, SchedulerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -51,7 +52,7 @@ export class GkeEntityProvider implements EntityProvider { private constructor( logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, gkeParents: string[], clusterManagerClient: container.v1.ClusterManagerClient, ) { @@ -90,7 +91,7 @@ export class GkeEntityProvider implements EntityProvider { clusterManagerClient: container.v1.ClusterManagerClient; }) { const gkeProviderConfig = config.getConfig('catalog.providers.gcp.gke'); - const schedule = readTaskScheduleDefinitionFromConfig( + const schedule = readSchedulerServiceTaskScheduleDefinitionFromConfig( gkeProviderConfig.getConfig('schedule'), ); return new GkeEntityProvider( @@ -172,7 +173,9 @@ export class GkeEntityProvider implements EntityProvider { }; } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 5cab7509bc..74977e4c9f 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,101 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.40-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## 0.1.40-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 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/api-report-alpha.md b/plugins/catalog-backend-module-gerrit/api-report-alpha.md index 60167b95fa..02aceb07c6 100644 --- a/plugins/catalog-backend-module-gerrit/api-report-alpha.md +++ b/plugins/catalog-backend-module-gerrit/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha (undocumented) -const catalogModuleGerritEntityProvider: BackendFeatureCompat; +const catalogModuleGerritEntityProvider: BackendFeature; export default catalogModuleGerritEntityProvider; // (No @packageDocumentation comment for this package) diff --git a/plugins/catalog-backend-module-gerrit/api-report.md b/plugins/catalog-backend-module-gerrit/api-report.md index a4620a1c15..abf2a83e7b 100644 --- a/plugins/catalog-backend-module-gerrit/api-report.md +++ b/plugins/catalog-backend-module-gerrit/api-report.md @@ -7,8 +7,8 @@ import { Config } from '@backstage/config'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; -import { TaskRunner } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; // @public (undocumented) export class GerritEntityProvider implements EntityProvider { @@ -19,8 +19,8 @@ export class GerritEntityProvider implements EntityProvider { configRoot: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GerritEntityProvider[]; // (undocumented) diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 7bac2566da..6b6c9d1a5c 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.2.0", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", @@ -47,15 +47,13 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", "fs-extra": "^11.2.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "uuid": "^9.0.0" }, "devDependencies": { diff --git a/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.test.ts b/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.test.ts index dc40e0525d..503b2a41d0 100644 --- a/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gerrit/src/module/catalogModuleGerritEntityProvider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { catalogModuleGerritEntityProvider } from './catalogModuleGerritEntityProvider'; @@ -23,7 +23,7 @@ import { GerritEntityProvider } from '../providers/GerritEntityProvider'; describe('catalogModuleGerritEntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { 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..5f07f96452 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts @@ -15,13 +15,13 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; @@ -41,14 +41,14 @@ const getJsonFixture = (fileName: string) => ), ); -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -57,7 +57,7 @@ class PersistingTaskRunner implements TaskRunner { const logger = mockServices.logger.mock(); describe('GerritEntityProvider', () => { - setupRequestMockHandlers(server); + registerMswTestHooks(server); afterEach(() => { jest.clearAllMocks(); @@ -228,7 +228,7 @@ describe('GerritEntityProvider', () => { it('fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; expect(() => GerritEntityProvider.fromConfig(config, { logger, @@ -270,7 +270,7 @@ describe('GerritEntityProvider', () => { }); const scheduler = { createScheduledTaskRunner: (_: any) => schedule, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const repoBuffer = fs.readFileSync( path.resolve(__dirname, '__fixtures__/listProjectsBody.txt'), diff --git a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts index ee4a33f395..bd03b90c52 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; import { @@ -35,7 +34,11 @@ import * as uuid from 'uuid'; import { readGerritConfigs } from './config'; import { GerritProjectQueryResult, GerritProviderConfig } from './types'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; /** @public */ export class GerritEntityProvider implements EntityProvider { @@ -49,8 +52,8 @@ export class GerritEntityProvider implements EntityProvider { configRoot: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GerritEntityProvider[] { if (!options.schedule && !options.scheduler) { @@ -95,7 +98,7 @@ export class GerritEntityProvider implements EntityProvider { config: GerritProviderConfig, integration: GerritIntegration, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, ) { this.config = config; this.integration = integration; @@ -114,7 +117,9 @@ export class GerritEntityProvider implements EntityProvider { await this.scheduleFn(); } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-gerrit/src/providers/config.ts b/plugins/catalog-backend-module-gerrit/src/providers/config.ts index dd7ee9ec43..36c8283545 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/config.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/config.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; +import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { GerritProviderConfig } from './types'; @@ -24,7 +24,9 @@ function readGerritConfig(id: string, config: Config): GerritProviderConfig { const query = config.getString('query'); const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-gerrit/src/providers/types.ts b/plugins/catalog-backend-module-gerrit/src/providers/types.ts index 4a8329df86..338ae41427 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/types.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; export type GerritProjectInfo = { id: string; @@ -30,5 +30,5 @@ export type GerritProviderConfig = { query: string; id: string; branch?: string; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index 950185b415..c130f00d2b 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,99 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-backend-module-github@0.7.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + +## 0.1.18-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-backend-module-github@0.6.6-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.1.18-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-catalog-backend-module-github@0.6.6-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-catalog-backend-module-github@0.6.6-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report.md b/plugins/catalog-backend-module-github-org/api-report.md index c0309c9ccf..44fa408e35 100644 --- a/plugins/catalog-backend-module-github-org/api-report.md +++ b/plugins/catalog-backend-module-github-org/api-report.md @@ -3,14 +3,14 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; import { TeamTransformer } from '@backstage/plugin-catalog-backend-module-github'; import { UserTransformer } from '@backstage/plugin-catalog-backend-module-github'; // @public -const catalogModuleGithubOrgEntityProvider: BackendFeatureCompat; +const catalogModuleGithubOrgEntityProvider: BackendFeature; export default catalogModuleGithubOrgEntityProvider; export { GithubMultiOrgEntityProvider }; diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index 31daeea5ee..a902d80c47 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.2.0", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", @@ -33,9 +33,7 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-catalog-backend-module-github": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", diff --git a/plugins/catalog-backend-module-github-org/src/module.test.ts b/plugins/catalog-backend-module-github-org/src/module.test.ts index 76ab4d1fbc..ba69f3c842 100644 --- a/plugins/catalog-backend-module-github-org/src/module.test.ts +++ b/plugins/catalog-backend-module-github-org/src/module.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; @@ -23,7 +23,7 @@ import { catalogModuleGithubOrgEntityProvider } from './module'; describe('catalogModuleGithubOrgEntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (...providers: any) => { diff --git a/plugins/catalog-backend-module-github-org/src/module.ts b/plugins/catalog-backend-module-github-org/src/module.ts index 31d4cef1c0..adb6f6c42d 100644 --- a/plugins/catalog-backend-module-github-org/src/module.ts +++ b/plugins/catalog-backend-module-github-org/src/module.ts @@ -18,11 +18,9 @@ import { coreServices, createBackendModule, createExtensionPoint, + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, } from '@backstage/backend-plugin-api'; -import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GithubMultiOrgEntityProvider, @@ -134,7 +132,7 @@ function readDefinitionsFromConfig(rootConfig: Config): Array<{ id: string; githubUrl: string; orgs?: string[]; - schedule: TaskScheduleDefinition; + schedule: SchedulerServiceTaskScheduleDefinition; }> { const baseKey = 'catalog.providers.githubOrg'; const baseConfig = rootConfig.getOptional(baseKey); @@ -150,6 +148,8 @@ function readDefinitionsFromConfig(rootConfig: Config): Array<{ id: c.getString('id'), githubUrl: c.getString('githubUrl'), orgs: c.getOptionalStringArray('orgs'), - schedule: readTaskScheduleDefinitionFromConfig(c.getConfig('schedule')), + schedule: readSchedulerServiceTaskScheduleDefinitionFromConfig( + c.getConfig('schedule'), + ), })); } diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index d9dc710268..4d7a38f5d7 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,188 @@ # @backstage/plugin-catalog-backend-module-github +## 0.7.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- c1eb809: Fix GitHub `repository` event support. + + - `$.repository.organization` is only provided for `push` events. Switched to `$.organization.login` instead. + - `$.repository.url` is not always returning the expected and required value. Use `$.repository.html_url` instead. + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + +## 0.6.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.6.6-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.6.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report-alpha.md b/plugins/catalog-backend-module-github/api-report-alpha.md index 2f9e12d24a..faa5423d82 100644 --- a/plugins/catalog-backend-module-github/api-report-alpha.md +++ b/plugins/catalog-backend-module-github/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const githubCatalogModule: BackendFeatureCompat; +const githubCatalogModule: BackendFeature; export default githubCatalogModule; // (No @packageDocumentation comment for this package) diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 0b68170255..dad81f8681 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -20,10 +20,10 @@ import { graphql } from '@octokit/graphql'; import { LocationSpec } from '@backstage/plugin-catalog-node'; import { LoggerService } from '@backstage/backend-plugin-api'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-node'; -import { TaskRunner } from '@backstage/backend-tasks'; import { TokenManager } from '@backstage/backend-common'; import { UserEntity } from '@backstage/catalog-model'; @@ -70,8 +70,8 @@ export class GitHubEntityProvider implements EntityProvider { config: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GitHubEntityProvider[]; // (undocumented) @@ -90,8 +90,8 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { options: { events?: EventsService; logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GithubEntityProvider[]; // (undocumented) @@ -173,7 +173,7 @@ export interface GithubMultiOrgEntityProviderOptions { id: string; logger: LoggerService; orgs?: string[]; - schedule?: 'manual' | TaskRunner; + schedule?: 'manual' | SchedulerServiceTaskRunner; teamTransformer?: TeamTransformer; userTransformer?: UserTransformer; } @@ -251,7 +251,7 @@ export interface GithubOrgEntityProviderOptions { id: string; logger: LoggerService; orgUrl: string; - schedule?: 'manual' | TaskRunner; + schedule?: 'manual' | SchedulerServiceTaskRunner; teamTransformer?: TeamTransformer; userTransformer?: UserTransformer; } diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts index 8b0f266c14..cc197f3ec7 100644 --- a/plugins/catalog-backend-module-github/config.d.ts +++ b/plugins/catalog-backend-module-github/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -116,7 +116,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; } | { [name: string]: { @@ -185,7 +185,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; @@ -220,7 +220,7 @@ export interface Config { /** * The refresh schedule to use. */ - schedule: TaskScheduleDefinitionConfig; + schedule: SchedulerServiceTaskScheduleDefinitionConfig; } | Array<{ /** @@ -249,7 +249,7 @@ export interface Config { /** * The refresh schedule to use. */ - schedule: TaskScheduleDefinitionConfig; + schedule: SchedulerServiceTaskScheduleDefinitionConfig; }>; }; }; diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 3528786e6d..81cc498ce5 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.7.0", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module", @@ -53,7 +53,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", @@ -67,7 +66,7 @@ "git-url-parse": "^14.0.0", "lodash": "^4.17.21", "minimatch": "^9.0.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "uuid": "^9.0.0" }, "devDependencies": { 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/deprecated.ts b/plugins/catalog-backend-module-github/src/deprecated.ts index cd1d7db62a..2a82714b4c 100644 --- a/plugins/catalog-backend-module-github/src/deprecated.ts +++ b/plugins/catalog-backend-module-github/src/deprecated.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { EntityProvider, @@ -25,7 +24,11 @@ import { GithubOrgEntityProvider, GithubOrgEntityProviderOptions, } from './providers/GithubOrgEntityProvider'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; /** * @public @@ -58,8 +61,8 @@ export class GitHubEntityProvider implements EntityProvider { config: Config, options: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GitHubEntityProvider[] { options.logger.warn( 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/module/githubCatalogModule.test.ts b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts index 018d339de8..3509823819 100644 --- a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts +++ b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { @@ -27,7 +27,7 @@ import { GithubLocationAnalyzer } from '../analyzers/GithubLocationAnalyzer'; describe('githubCatalogModule', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { 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 002b2bbfd1..624deaa38e 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -15,10 +15,10 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { Config, ConfigReader } from '@backstage/config'; import { DeferredEntity, @@ -41,14 +41,14 @@ jest.mock('../lib/github', () => { getOrganizationRepositories: jest.fn(), }; }); -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -624,7 +624,7 @@ describe('GithubEntityProvider', () => { it('fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = createSingleProviderConfig(); expect(() => @@ -641,7 +641,7 @@ describe('GithubEntityProvider', () => { const schedule = new PersistingTaskRunner(); const scheduler = { createScheduledTaskRunner: (_: any) => schedule, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = createSingleProviderConfig({ providerConfig: { schedule: { @@ -665,7 +665,7 @@ describe('GithubEntityProvider', () => { event: EventParams, options?: { branch?: string; catalogFilePath?: string }, ): DeferredEntity[] => { - const url = `${event.eventPayload.repository.url}/blob/${ + const url = `${event.eventPayload.repository.html_url}/blob/${ options?.branch ?? 'main' }/${options?.catalogFilePath ?? 'catalog-info.yaml'}`; return createExpectedEntitiesForUrl(url); @@ -687,6 +687,7 @@ describe('GithubEntityProvider', () => { name: 'test-repo', organization, topics: [], + html_url: `https://github.com/${organization}/test-repo`, url: `https://github.com/${organization}/test-repo`, } as Partial; @@ -702,6 +703,9 @@ describe('GithubEntityProvider', () => { const event = { ref: options?.ref ?? 'refs/heads/main', repository: repo as PushEvent['repository'], + organization: { + login: organization, + }, created: true, deleted: false, forced: false, @@ -960,10 +964,10 @@ describe('GithubEntityProvider', () => { ): EventParams => { const repo = { name: 'test-repo', - url: 'https://github.com/test-org/test-repo', + html_url: 'https://github.com/test-org/test-repo', + url: 'https://api.github.com/repos/test-org/test-repo', default_branch: 'main', master_branch: 'main', - organization: 'test-org', topics: [], archived: action === 'archived', private: action !== 'publicized', @@ -972,6 +976,9 @@ describe('GithubEntityProvider', () => { const event = { action, repository: repo as RepositoryEvent['repository'], + organization: { + login: 'test-org', + }, } as RepositoryEvent; if (action === 'renamed') { @@ -1285,7 +1292,7 @@ describe('GithubEntityProvider', () => { 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 urlOldRepo = `https://github.com/${event.eventPayload.organization?.login}/${event.eventPayload.changes.repository.name.from}/blob/main/catalog-info.yaml`; const expectedEntitiesRemoved = createExpectedEntitiesForUrl(urlOldRepo); @@ -1314,7 +1321,7 @@ describe('GithubEntityProvider', () => { 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 urlOldRepo = `https://github.com/${event.eventPayload.organization?.login}/${event.eventPayload.changes.repository.name.from}/blob/main/catalog-info.yaml`; const expectedEntitiesRemoved = createExpectedEntitiesForUrl(urlOldRepo); const expectedEntitiesAdded = createExpectedEntitiesForEvent(event); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index daade9660a..1c64174ee7 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GithubCredentialsProvider, @@ -66,7 +65,11 @@ import { RepositoryUnarchivedEvent, } from '@octokit/webhooks-types'; import { Minimatch } from 'minimatch'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; const EVENT_TOPICS = ['github.push', 'github.repository']; @@ -103,8 +106,8 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { options: { events?: EventsService; logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GithubEntityProvider[] { if (!options.schedule && !options.scheduler) { @@ -147,7 +150,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { config: GithubEntityProviderConfig, integration: GithubIntegration, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, events?: EventsService, ) { this.config = config; @@ -177,7 +180,9 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { return await this.scheduleFn(); } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ @@ -294,7 +299,7 @@ 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}`); + this.logger.debug(`Received event for topic ${params.topic}`); if (EVENT_TOPICS.some(topic => topic === params.topic)) { if (!this.connection) { throw new Error('Not initialized'); @@ -323,15 +328,15 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } private async onPush(event: PushEvent) { - if (this.config.organization !== event.repository.organization) { + if (this.config.organization !== event.organization?.login) { this.logger.debug( - `skipping push event from organization ${event.repository.organization}`, + `skipping push event from organization ${event.organization?.login}`, ); return; } const repoName = event.repository.name; - const repoUrl = event.repository.url; + const repoUrl = event.repository.html_url; this.logger.debug(`handle github:push event for ${repoName} - ${repoUrl}`); const branch = @@ -356,13 +361,13 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { // so we will process the change based in this data // https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#push const added = this.collectDeferredEntitiesFromCommit( - event.repository.url, + repoUrl, branch, event.commits, (commit: Commit) => [...commit.added], ); const removed = this.collectDeferredEntitiesFromCommit( - event.repository.url, + repoUrl, branch, event.commits, (commit: Commit) => [...commit.removed], @@ -381,14 +386,12 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { keys: [ ...new Set([ ...modified.map( - filePath => - `url:${event.repository.url}/tree/${branch}/${filePath}`, + filePath => `url:${repoUrl}/tree/${branch}/${filePath}`, ), ...modified.map( - filePath => - `url:${event.repository.url}/blob/${branch}/${filePath}`, + filePath => `url:${repoUrl}/blob/${branch}/${filePath}`, ), - `url:${event.repository.url}/tree/${branch}/${catalogPath}`, + `url:${repoUrl}/tree/${branch}/${catalogPath}`, ]), ], }); @@ -408,9 +411,9 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { } private async onRepoChange(event: RepositoryEvent) { - if (this.config.organization !== event.repository.organization) { + if (this.config.organization !== event.organization?.login) { this.logger.debug( - `skipping repository event from organization ${event.repository.organization}`, + `skipping repository event from organization ${event.organization?.login}`, ); return; } @@ -527,7 +530,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { private async onRepoRenamed(event: RepositoryRenamedEvent) { const repository = this.createRepoFromEvent(event); const oldRepoName = event.changes.repository.name.from; - const urlParts = event.repository.url.split('/'); + const urlParts = repository.url.split('/'); urlParts[urlParts.length - 1] = oldRepoName; const oldRepoUrl = urlParts.join('/'); const oldRepository: Repository = { @@ -540,7 +543,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { 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`, + `skipping repository renamed event for repository ${repository.name} because it didn't match provider filters`, ); return; } @@ -587,7 +590,7 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { 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`, + `skipping repository unarchived event for repository ${repository.name} because it didn't match provider filters`, ); return; } @@ -632,7 +635,10 @@ export class GithubEntityProvider implements EntityProvider, EventSubscriber { private createRepoFromEvent(event: RepositoryEvent | PushEvent): Repository { return { - url: event.repository.url, + // $.repository.url can be a value like + // "https://api.github.com/repos/{org}/{repo}" + // or "https://github.com/{org}/{repo}" + url: event.repository.html_url, name: event.repository.name, defaultBranchRef: event.repository.default_branch, repositoryTopics: event.repository.topics, diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts index 296a508adf..073c92701f 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts @@ -15,9 +15,9 @@ */ import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; @@ -36,7 +36,7 @@ export type GithubEntityProviderConfig = { visibility?: string[]; }; validateLocationsExist: boolean; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; export type GithubTopicFilters = { @@ -96,7 +96,9 @@ function readProviderConfig( } const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts index 756b9902c6..4367383cd2 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -78,7 +77,10 @@ import { } from '../lib/github'; import { splitTeamSlug } from '../lib/util'; import { areGroupEntities, areUserEntities } from '../lib/guards'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; const EVENT_TOPICS = [ 'github.installation', @@ -128,10 +130,10 @@ export interface GithubMultiOrgEntityProviderOptions { * manually at some interval. * * But more commonly you will pass in the result of - * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} + * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner} * to enable automatic scheduling of tasks. */ - schedule?: 'manual' | TaskRunner; + schedule?: 'manual' | SchedulerServiceTaskRunner; /** * The logger to use. diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index ba7602f962..f7183b4df9 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; import { Entity, isGroupEntity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { @@ -103,10 +103,10 @@ export interface GithubOrgEntityProviderOptions { * manually at some interval. * * But more commonly you will pass in the result of - * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} + * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner} * to enable automatic scheduling of tasks. */ - schedule?: 'manual' | TaskRunner; + schedule?: 'manual' | SchedulerServiceTaskRunner; /** * The logger to use. diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 3ce5fef0b4..b6000e7a6d 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,86 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.1.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend-module-gitlab@0.4.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/plugin-events-node@0.3.9 + +## 0.0.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + +## 0.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.3.22-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report.md b/plugins/catalog-backend-module-gitlab-org/api-report.md index d9c5c62b52..b3af579255 100644 --- a/plugins/catalog-backend-module-gitlab-org/api-report.md +++ b/plugins/catalog-backend-module-gitlab-org/api-report.md @@ -3,9 +3,9 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @public -const catalogModuleGitlabOrgDiscoveryEntityProvider: BackendFeatureCompat; +const catalogModuleGitlabOrgDiscoveryEntityProvider: BackendFeature; export default catalogModuleGitlabOrgDiscoveryEntityProvider; ``` diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 0861d4ec92..ba4fcd844b 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.1.0", "description": "The gitlab-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", @@ -40,7 +40,6 @@ "@backstage/plugin-events-node": "workspace:^" }, "devDependencies": { - "@backstage/backend-tasks": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-events-backend-test-utils": "workspace:^", diff --git a/plugins/catalog-backend-module-gitlab-org/src/catalogModuleGitlabOrgDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab-org/src/catalogModuleGitlabOrgDiscoveryEntityProvider.test.ts index 5fbc321cc2..107de6b7b3 100644 --- a/plugins/catalog-backend-module-gitlab-org/src/catalogModuleGitlabOrgDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab-org/src/catalogModuleGitlabOrgDiscoveryEntityProvider.test.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { createServiceFactory } from '@backstage/backend-plugin-api'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { + createServiceFactory, + SchedulerServiceTaskScheduleDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { GitlabOrgDiscoveryEntityProvider } from '@backstage/plugin-catalog-backend-module-gitlab'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; @@ -35,7 +37,7 @@ describe('catalogModuleGitlabOrgDiscoveryEntityProvider', () => { }, }); let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { @@ -81,7 +83,7 @@ describe('catalogModuleGitlabOrgDiscoveryEntityProvider', () => { await startTestBackend({ extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], features: [ - eventsServiceFactory(), + eventsServiceFactory, catalogModuleGitlabOrgDiscoveryEntityProvider, mockServices.rootConfig.factory({ data: config }), mockServices.logger.factory(), diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 6fd86bb15d..e8e40a7391 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,118 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.4.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- c7b14ed: Adds new optional `excludeRepos` configuration option to the Gitlab catalog provider. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + +## 0.3.22-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.3.22-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.3.22-next.1 + +### Patch Changes + +- c7b14ed: Adds new optional `excludeRepos` configuration option to the Gitlab catalog provider. +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report-alpha.md b/plugins/catalog-backend-module-gitlab/api-report-alpha.md index 1c27e1ee6f..0b1c7df3cc 100644 --- a/plugins/catalog-backend-module-gitlab/api-report-alpha.md +++ b/plugins/catalog-backend-module-gitlab/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const catalogModuleGitlabDiscoveryEntityProvider: BackendFeatureCompat; +const catalogModuleGitlabDiscoveryEntityProvider: BackendFeature; export default catalogModuleGitlabDiscoveryEntityProvider; // (No @packageDocumentation comment for this package) diff --git a/plugins/catalog-backend-module-gitlab/api-report.md b/plugins/catalog-backend-module-gitlab/api-report.md index 2b78c67205..8a21e237b5 100644 --- a/plugins/catalog-backend-module-gitlab/api-report.md +++ b/plugins/catalog-backend-module-gitlab/api-report.md @@ -13,9 +13,9 @@ import { GitLabIntegrationConfig } from '@backstage/integration'; import { GroupEntity } from '@backstage/catalog-model'; import { LocationSpec } from '@backstage/plugin-catalog-node'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; -import { TaskRunner } from '@backstage/backend-tasks'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { UserEntity } from '@backstage/catalog-model'; // @public @@ -28,8 +28,8 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { options: { logger: LoggerService; events?: EventsService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GitlabDiscoveryEntityProvider[]; // (undocumented) @@ -83,8 +83,8 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { options: { logger: LoggerService; events?: EventsService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; userTransformer?: UserTransformer; groupEntitiesTransformer?: GroupTransformer; groupNameTransformer?: GroupNameTransformer; @@ -108,8 +108,9 @@ export type GitlabProviderConfig = { groupPattern: RegExp; allowInherited?: boolean; orgEnabled?: boolean; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; skipForkedRepos?: boolean; + excludeRepos?: string[]; }; // @public diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts index 7d183b6e58..e249f13d4c 100644 --- a/plugins/catalog-backend-module-gitlab/config.d.ts +++ b/plugins/catalog-backend-module-gitlab/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -46,7 +46,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; /** * (Optional) RegExp for the Project Name Pattern */ @@ -63,6 +63,11 @@ export interface Config { * (Optional) Skip forked repository */ skipForkedRepos?: boolean; + /** + * (Optional) A list of strings containing the paths of the repositories to skip + * Should be in the format group/subgroup/repo, with no leading or trailing slashes. + */ + excludeRepos?: string[]; }; }; }; diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 51538cc178..ef2bd69960 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.4.0", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module", @@ -51,9 +51,8 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/integration": "workspace:^", @@ -62,7 +61,7 @@ "@backstage/plugin-events-node": "workspace:^", "@gitbeaker/rest": "^40.0.3", "lodash": "^4.17.21", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "uuid": "^9.0.0" }, "devDependencies": { 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/GitLabDiscoveryProcessor.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts index b8e0cf3669..29914db705 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts @@ -14,11 +14,6 @@ * limitations under the License. */ -import { - CacheClient, - CacheManager, - PluginCacheManager, -} from '@backstage/backend-common'; import { Config } from '@backstage/config'; import { ScmIntegrationRegistry, @@ -31,7 +26,11 @@ import { processingResult, } from '@backstage/plugin-catalog-node'; import { GitLabClient, GitLabProject, paginated } from './lib'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { CacheService, LoggerService } from '@backstage/backend-plugin-api'; +import { + CacheManager, + PluginCacheManager, +} from '@backstage/backend-defaults/cache'; /** * Extracts repositories out of an GitLab instance. @@ -40,7 +39,7 @@ import { LoggerService } from '@backstage/backend-plugin-api'; export class GitLabDiscoveryProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; private readonly logger: LoggerService; - private readonly cache: CacheClient; + private readonly cache: CacheService; private readonly skipReposWithoutExactFileMatch: boolean; private readonly skipForkedRepos: boolean; diff --git a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts index 798df32695..4a173c6b50 100644 --- a/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts +++ b/plugins/catalog-backend-module-gitlab/src/__testUtils__/mocks.ts @@ -299,6 +299,33 @@ export const config_single_integration_skip_forks: MockObject = { }, }; +export const config_single_integration_exclude_repos: MockObject = { + integrations: { + gitlab: [ + { + host: 'example.com', + apiBaseUrl: 'https://example.com/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'example.com', + group: 'group1', + excludeRepos: ['group1/test-repo1'], + schedule: { + frequency: 'PT30M', + timeout: 'PT3M', + }, + }, + }, + }, + }, +}; + export const config_no_schedule: MockObject = { integrations: { gitlab: [ 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/lib/types.ts b/plugins/catalog-backend-module-gitlab/src/lib/types.ts index 0b9ea6a868..61f76d03de 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/types.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/types.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { GitLabIntegrationConfig } from '@backstage/integration'; @@ -196,11 +196,16 @@ export type GitlabProviderConfig = { allowInherited?: boolean; orgEnabled?: boolean; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; /** * If the project is a fork, skip repository */ skipForkedRepos?: boolean; + /** + * List of repositories to exclude from discovery, should be the full path to the repository, e.g. `group/project` + * Paths should not start or end with a slash. + */ + excludeRepos?: string[]; }; /** diff --git a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts index 636405220b..7c9fc068a0 100644 --- a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { createServiceFactory } from '@backstage/backend-plugin-api'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { + createServiceFactory, + SchedulerServiceTaskScheduleDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; @@ -35,7 +37,7 @@ describe('catalogModuleGitlabDiscoveryEntityProvider', () => { }, }); let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { @@ -80,7 +82,7 @@ describe('catalogModuleGitlabDiscoveryEntityProvider', () => { await startTestBackend({ extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], features: [ - eventsServiceFactory(), + eventsServiceFactory, catalogModuleGitlabDiscoveryEntityProvider, mockServices.rootConfig.factory({ data: config }), mockServices.logger.factory(), 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..e6309a8516 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts @@ -15,13 +15,13 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; @@ -32,17 +32,17 @@ 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 { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -75,7 +75,7 @@ describe('GitlabDiscoveryEntityProvider - configuration', () => { it('should fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader(mock.config_no_schedule_integration); expect(() => @@ -130,6 +130,7 @@ describe('GitlabDiscoveryEntityProvider - configuration', () => { ); }); }); + describe('GitlabDiscoveryEntityProvider - refresh', () => { it('should apply full update on scheduled execution', async () => { const config = new ConfigReader(mock.config_no_org_integration); @@ -231,6 +232,38 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { }); }); + it('should filter repositories that are excluded', async () => { + const config = new ConfigReader( + mock.config_single_integration_exclude_repos, + ); + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + const provider = GitlabDiscoveryEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + + await provider.connect(entityProviderConnection); + + await provider.refresh(logger); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: mock.expected_location_entities_default_branch.filter( + entity => + !entity.entity.metadata.annotations[ + 'backstage.io/managed-by-location' + ].includes('test-repo1') && + !entity.entity.metadata.annotations[ + 'backstage.io/managed-by-location' + ].includes('awesome'), + ), + }); + }); + // branch and fallback branch are undefined in the config it('should ingest catalog from project default branch only', async () => { const config = new ConfigReader(mock.config_single_integration); diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts index b2ef475d9d..fdd884e1fd 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts @@ -14,8 +14,11 @@ * limitations under the License. */ -import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { GitLabIntegration, ScmIntegrations } from '@backstage/integration'; import { LocationSpec } from '@backstage/plugin-catalog-common'; @@ -68,8 +71,8 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { options: { logger: LoggerService; events?: EventsService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; }, ): GitlabDiscoveryEntityProvider[] { if (!options.schedule && !options.scheduler) { @@ -121,7 +124,7 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { integration: GitLabIntegration; logger: LoggerService; events?: EventsService; - taskRunner: TaskRunner; + taskRunner: SchedulerServiceTaskRunner; }) { this.config = options.config; this.integration = options.integration; @@ -165,7 +168,9 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { * @param taskRunner - The task runner instance. * @returns The scheduled function. */ - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ @@ -470,6 +475,13 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { return false; } + if (this.config.excludeRepos?.includes(project.path_with_namespace ?? '')) { + this.logger.debug( + `Skipping project ${project.path_with_namespace} as it is excluded.`, + ); + return false; + } + const project_branch = this.config.branch ?? project.default_branch ?? 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..a080948d65 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.test.ts @@ -15,13 +15,13 @@ */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; @@ -33,17 +33,17 @@ import { GroupNameTransformerOptions } from '../lib/types'; import { GitlabOrgDiscoveryEntityProvider } from './GitlabOrgDiscoveryEntityProvider'; const server = setupServer(...handlers); -setupRequestMockHandlers(server); +registerMswTestHooks(server); afterEach(() => jest.clearAllMocks()); -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -113,7 +113,7 @@ describe('GitlabOrgDiscoveryEntityProvider - configuration', () => { it('should fail with scheduler but no schedule config', () => { const scheduler = { createScheduledTaskRunner: (_: any) => jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader(mock.config_org_integration_saas); expect(() => @@ -130,7 +130,7 @@ describe('GitlabOrgDiscoveryEntityProvider - configuration', () => { const schedule = new PersistingTaskRunner(); const scheduler = { createScheduledTaskRunner: (_: any) => schedule, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const config = new ConfigReader(mock.config_org_integration_saas_sched); const providers = GitlabOrgDiscoveryEntityProvider.fromConfig(config, { logger, diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts index d525e3290c..7f06079f42 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabOrgDiscoveryEntityProvider.ts @@ -13,8 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -114,8 +117,8 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { options: { logger: LoggerService; events?: EventsService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; userTransformer?: UserTransformer; groupEntitiesTransformer?: GroupEntitiesTransformer; groupNameTransformer?: GroupNameTransformer; @@ -176,7 +179,7 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { integration: GitLabIntegration; logger: LoggerService; events?: EventsService; - taskRunner: TaskRunner; + taskRunner: SchedulerServiceTaskRunner; userTransformer?: UserTransformer; groupEntitiesTransformer?: GroupEntitiesTransformer; groupNameTransformer?: GroupNameTransformer; @@ -321,7 +324,9 @@ export class GitlabOrgDiscoveryEntityProvider implements EntityProvider { } } - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts index 4418dba50b..28df8c88b7 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts @@ -60,6 +60,7 @@ describe('config', () => { allowInherited: false, schedule: undefined, skipForkedRepos: false, + excludeRepos: [], restrictUsersToGroup: false, }), ); @@ -99,6 +100,7 @@ describe('config', () => { allowInherited: false, schedule: undefined, skipForkedRepos: false, + excludeRepos: [], restrictUsersToGroup: false, }), ); @@ -139,11 +141,54 @@ describe('config', () => { allowInherited: false, schedule: undefined, restrictUsersToGroup: false, + excludeRepos: [], skipForkedRepos: true, }), ); }); + it('valid config with excludeRepos', () => { + const config = new ConfigReader({ + catalog: { + providers: { + gitlab: { + test: { + group: 'group', + host: 'host', + branch: 'not-master', + fallbackBranch: 'main', + entityFilename: 'custom-file.yaml', + skipForkedRepos: false, + excludeRepos: ['foo/bar', 'quz/qux'], + }, + }, + }, + }, + }); + + const result = readGitlabConfigs(config); + expect(result).toHaveLength(1); + result.forEach(r => + expect(r).toStrictEqual({ + id: 'test', + group: 'group', + branch: 'not-master', + fallbackBranch: 'main', + host: 'host', + catalogFile: 'custom-file.yaml', + projectPattern: /[\s\S]*/, + groupPattern: /[\s\S]*/, + userPattern: /[\s\S]*/, + orgEnabled: false, + allowInherited: false, + schedule: undefined, + restrictUsersToGroup: false, + skipForkedRepos: false, + excludeRepos: ['foo/bar', 'quz/qux'], + }), + ); + }); + it('valid config with schedule', () => { const config = new ConfigReader({ catalog: { @@ -181,6 +226,7 @@ describe('config', () => { allowInherited: false, skipForkedRepos: false, restrictUsersToGroup: false, + excludeRepos: [], schedule: { frequency: { minutes: 30 }, timeout: { diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.ts index 68eba7165a..c0fac9a166 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; +import { readSchedulerServiceTaskScheduleDefinitionFromConfig } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { GitlabProviderConfig } from '../lib'; @@ -47,9 +47,13 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { config.getOptionalBoolean('allowInherited') ?? false; const skipForkedRepos: boolean = config.getOptionalBoolean('skipForkedRepos') ?? false; + const excludeRepos: string[] = + config.getOptionalStringArray('excludeRepos') ?? []; const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; const restrictUsersToGroup = config.getOptionalBoolean('restrictUsersToGroup') ?? false; @@ -68,6 +72,7 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { orgEnabled, allowInherited, skipForkedRepos, + excludeRepos, restrictUsersToGroup, }; } diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index 04da7cc46e..b46b77b0c1 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,121 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.5.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.9 + +## 0.4.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## 0.4.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.4.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report-alpha.md b/plugins/catalog-backend-module-incremental-ingestion/api-report-alpha.md index f6ee4c1414..cfc4bca6f2 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/api-report-alpha.md +++ b/plugins/catalog-backend-module-incremental-ingestion/api-report-alpha.md @@ -3,13 +3,13 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { IncrementalEntityProvider } from '@backstage/plugin-catalog-backend-module-incremental-ingestion'; import { IncrementalEntityProviderOptions } from '@backstage/plugin-catalog-backend-module-incremental-ingestion'; // @alpha -const catalogModuleIncrementalIngestionEntityProvider: BackendFeatureCompat; +const catalogModuleIncrementalIngestionEntityProvider: BackendFeature; export default catalogModuleIncrementalIngestionEntityProvider; // @alpha diff --git a/plugins/catalog-backend-module-incremental-ingestion/api-report.md b/plugins/catalog-backend-module-incremental-ingestion/api-report.md index 4088b05bb5..22b497b2f7 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/api-report.md +++ b/plugins/catalog-backend-module-incremental-ingestion/api-report.md @@ -14,9 +14,9 @@ import { EventSubscriber } from '@backstage/plugin-events-node'; import type { Logger } from 'winston'; import type { PermissionEvaluator } from '@backstage/plugin-permission-common'; import type { PluginDatabaseManager } from '@backstage/backend-common'; -import type { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Router } from 'express'; -import type { UrlReader } from '@backstage/backend-common'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public export type EntityIteratorResult = @@ -89,9 +89,9 @@ export interface IncrementalEntityProviderOptions { export type PluginEnvironment = { logger: Logger; database: PluginDatabaseManager; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; config: Config; - reader: UrlReader; + reader: UrlReaderService; permissions: PermissionEvaluator; }; ``` diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 2941fcd3f4..466aca22ec 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.5.0", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module", @@ -53,7 +53,6 @@ "dependencies": { "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts index eccfaee655..0f98384893 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { TestDatabases, mockServices } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { IncrementalEntityProvider } from '../types'; @@ -66,8 +66,7 @@ describe('WrapperProviders', () => { config, logger, client, - scheduler: - scheduler as Partial as PluginTaskScheduler, + scheduler: scheduler as Partial as SchedulerService, applyDatabaseMigrations, }); const wrapped1 = providers.wrap(provider1, { diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/types.ts b/plugins/catalog-backend-module-incremental-ingestion/src/types.ts index 78309afb36..79368638f2 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/types.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/types.ts @@ -14,14 +14,7 @@ * limitations under the License. */ -import type { - PluginDatabaseManager, - UrlReader, -} from '@backstage/backend-common'; -import type { - PluginTaskScheduler, - TaskFunction, -} from '@backstage/backend-tasks'; +import type { PluginDatabaseManager } from '@backstage/backend-common'; import type { Config } from '@backstage/config'; import type { DeferredEntity, @@ -32,7 +25,12 @@ import type { PermissionEvaluator } from '@backstage/plugin-permission-common'; import type { DurationObjectUnits } from 'luxon'; import type { Logger } from 'winston'; import { IncrementalIngestionDatabaseManager } from './database/IncrementalIngestionDatabaseManager'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + UrlReaderService, + SchedulerService, + SchedulerServiceTaskFunction, +} from '@backstage/backend-plugin-api'; /** * Ingest entities into the catalog in bite-sized chunks. @@ -190,14 +188,14 @@ export interface IncrementalEntityProviderOptions { export type PluginEnvironment = { logger: Logger; database: PluginDatabaseManager; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; config: Config; - reader: UrlReader; + reader: UrlReaderService; permissions: PermissionEvaluator; }; export interface IterationEngine { - taskFn: TaskFunction; + taskFn: SchedulerServiceTaskFunction; } export interface IterationEngineOptions { diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index b975322d0d..4abf2a3d3b 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,119 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.8.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + +## 0.7.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## 0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.7.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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 2ee3c1ec79..40bb9d81ae 100644 --- a/plugins/catalog-backend-module-ldap/api-report.md +++ b/plugins/catalog-backend-module-ldap/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node'; import { Client } from 'ldapjs'; @@ -31,7 +31,7 @@ export type BindConfig = { }; // @public -const catalogModuleLdapOrgEntityProvider: BackendFeatureCompat; +const catalogModuleLdapOrgEntityProvider: BackendFeature; export default catalogModuleLdapOrgEntityProvider; // @public diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index f719673682..abb11f8096 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.8.0", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module", @@ -39,7 +39,6 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/catalog-backend-module-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md index 7e5ae51a99..a95b43851b 100644 --- a/plugins/catalog-backend-module-logs/CHANGELOG.md +++ b/plugins/catalog-backend-module-logs/CHANGELOG.md @@ -1,5 +1,73 @@ # @backstage/plugin-catalog-backend-module-logs +## 0.0.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-events-node@0.3.9 + +## 0.0.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + +## 0.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report.md b/plugins/catalog-backend-module-logs/api-report.md index 0be2197c9a..fe68c7a560 100644 --- a/plugins/catalog-backend-module-logs/api-report.md +++ b/plugins/catalog-backend-module-logs/api-report.md @@ -3,9 +3,9 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @public -const catalogModuleLogs: BackendFeatureCompat; +const catalogModuleLogs: BackendFeature; export default catalogModuleLogs; ``` diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index e0d2e46c78..43afb06dde 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", "description": "A module that subscribes to catalog releated events and logs them.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-logs/src/module.test.ts b/plugins/catalog-backend-module-logs/src/module.test.ts index 79f1b8c295..037523a902 100644 --- a/plugins/catalog-backend-module-logs/src/module.test.ts +++ b/plugins/catalog-backend-module-logs/src/module.test.ts @@ -34,8 +34,8 @@ describe('catalogModuleLogs', () => { await startTestBackend({ features: [ mockServices.logger.factory(), - eventsServiceFactory(), - catalogModuleLogs(), + eventsServiceFactory, + catalogModuleLogs, ], }); diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 0f74e4591b..a394664d88 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,104 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.6.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 58dff4d: Added option to ingest groups based on their group membership in Azure Entra ID +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26 + +## 0.5.31-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## 0.5.31-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 0.5.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + +## 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..8129aec50f 100644 --- a/plugins/catalog-backend-module-msgraph/api-report-alpha.md +++ b/plugins/catalog-backend-module-msgraph/api-report-alpha.md @@ -3,14 +3,15 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } 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 -const catalogModuleMicrosoftGraphOrgEntityProvider: BackendFeatureCompat; +const catalogModuleMicrosoftGraphOrgEntityProvider: BackendFeature; export default catalogModuleMicrosoftGraphOrgEntityProvider; // @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..acc3b8531b 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -12,10 +12,10 @@ import { GroupEntity } from '@backstage/catalog-model'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { LoggerService } from '@backstage/backend-plugin-api'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Response as Response_2 } from 'node-fetch'; -import { TaskRunner } from '@backstage/backend-tasks'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { TokenCredential } from '@azure/identity'; import { UserEntity } from '@backstage/catalog-model'; @@ -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,7 +146,8 @@ export interface MicrosoftGraphOrgEntityProviderLegacyOptions { id: string; logger: LoggerService; organizationTransformer?: OrganizationTransformer; - schedule: 'manual' | TaskRunner; + providerConfigTransformer?: ProviderConfigTransformer; + schedule: 'manual' | SchedulerServiceTaskRunner; target: string; userTransformer?: UserTransformer; } @@ -155,13 +157,16 @@ export type MicrosoftGraphOrgEntityProviderOptions = | MicrosoftGraphOrgEntityProviderLegacyOptions | { logger: LoggerService; - schedule?: 'manual' | TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: 'manual' | SchedulerServiceTaskRunner; + scheduler?: SchedulerService; userTransformer?: UserTransformer | Record; groupTransformer?: GroupTransformer | Record; organizationTransformer?: | OrganizationTransformer | Record; + providerConfigTransformer?: + | ProviderConfigTransformer + | Record; }; // @public @deprecated @@ -210,9 +215,10 @@ export type MicrosoftGraphProviderConfig = { groupFilter?: string; groupSearch?: string; groupSelect?: string[]; + groupIncludeSubGroups?: boolean; queryMode?: 'basic' | 'advanced'; loadUserPhotos?: boolean; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; // @public @@ -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..b93026e1bb 100644 --- a/plugins/catalog-backend-module-msgraph/config.d.ts +++ b/plugins/catalog-backend-module-msgraph/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinitionConfig } from '@backstage/backend-plugin-api'; export interface Config { catalog?: { @@ -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?: { @@ -212,7 +217,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; } | { [name: string]: { @@ -299,7 +304,7 @@ export interface Config { /** * (Optional) TaskScheduleDefinition for the refresh. */ - schedule?: TaskScheduleDefinitionConfig; + schedule?: SchedulerServiceTaskScheduleDefinitionConfig; }; }; }; diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 7ba2b7e1e6..68e67c1cfc 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.6.0", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module", @@ -52,9 +52,7 @@ }, "dependencies": { "@azure/identity": "^4.0.0", - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", @@ -62,13 +60,12 @@ "@microsoft/microsoft-graph-types": "^2.6.0", "@types/node-fetch": "^2.5.12", "lodash": "^4.17.21", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "p-limit": "^3.0.2", "qs": "^6.9.4", "uuid": "^9.0.0" }, "devDependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/lodash": "^4.14.151", 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..e47181a1c4 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts @@ -15,9 +15,9 @@ */ import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { trimEnd } from 'lodash'; @@ -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 @@ -135,7 +141,7 @@ export type MicrosoftGraphProviderConfig = { /** * Schedule configuration for refresh tasks. */ - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; /** @@ -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 ( @@ -329,7 +338,9 @@ export function readProviderConfig( } const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { @@ -347,6 +358,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.test.ts b/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.test.ts index 2a3e66a9b3..9f4c616c09 100644 --- a/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/module/catalogModuleMicrosoftGraphOrgEntityProvider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { catalogModuleMicrosoftGraphOrgEntityProvider } from './catalogModuleMicrosoftGraphOrgEntityProvider'; @@ -23,7 +23,7 @@ import { MicrosoftGraphOrgEntityProvider } from '../processors'; describe('catalogModuleMicrosoftGraphOrgEntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { 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.test.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts index 78efa1b3c1..1d68f706bc 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts @@ -14,10 +14,10 @@ * limitations under the License. */ import { - PluginTaskScheduler, - TaskInvocationDefinition, - TaskRunner, -} from '@backstage/backend-tasks'; + SchedulerService, + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { ANNOTATION_LOCATION, @@ -48,14 +48,14 @@ const readMicrosoftGraphOrgMocked = readMicrosoftGraphOrg as jest.Mock< Promise<{ users: UserEntity[]; groups: GroupEntity[] }> >; -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } @@ -102,7 +102,7 @@ describe('MicrosoftGraphOrgEntityProvider', () => { const taskRunner = new PersistingTaskRunner(); const scheduler = { createScheduledTaskRunner: (_: any) => taskRunner, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const entityProviderConnection: EntityProviderConnection = { applyMutation: jest.fn(), refresh: jest.fn(), diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index 3c8b903e69..4c92a5f17e 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -34,13 +33,18 @@ import { MICROSOFT_GRAPH_USER_ID_ANNOTATION, MicrosoftGraphClient, MicrosoftGraphProviderConfig, + ProviderConfigTransformer, OrganizationTransformer, readMicrosoftGraphConfig, readMicrosoftGraphOrg, UserTransformer, } from '../microsoftGraph'; import { readProviderConfigs } from '../microsoftGraph/config'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; /** * Options for {@link MicrosoftGraphOrgEntityProvider}. @@ -64,16 +68,16 @@ export type MicrosoftGraphOrgEntityProviderOptions = * manually at some interval. * * But more commonly you will pass in the result of - * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} + * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner} * to enable automatic scheduling of tasks. */ - schedule?: 'manual' | TaskRunner; + schedule?: 'manual' | SchedulerServiceTaskRunner; /** * Scheduler used to schedule refreshes based on * the schedule config. */ - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; /** * The function that transforms a user entry in msgraph to an entity. @@ -94,6 +98,13 @@ export type MicrosoftGraphOrgEntityProviderOptions = organizationTransformer?: | OrganizationTransformer | Record; + + /** + * The function that transforms provider config dynamically. + */ + providerConfigTransformer?: + | ProviderConfigTransformer + | Record; }; /** @@ -133,10 +144,10 @@ export interface MicrosoftGraphOrgEntityProviderLegacyOptions { * manually at some interval. * * But more commonly you will pass in the result of - * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} + * {@link @backstage/backend-plugin-api#SchedulerService.createScheduledTaskRunner} * to enable automatic scheduling of tasks. */ - schedule: 'manual' | TaskRunner; + schedule: 'manual' | SchedulerServiceTaskRunner; /** * The function that transforms a user entry in msgraph to an entity. @@ -152,6 +163,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 +232,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { providerConfig.id, options.organizationTransformer, ), + providerConfigTransformer: getTransformer( + providerConfig.id, + options.providerConfigTransformer, + ), }); if (taskRunner !== 'manual') { @@ -257,6 +277,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { userTransformer: options.userTransformer, groupTransformer: options.groupTransformer, organizationTransformer: options.organizationTransformer, + providerConfigTransformer: options.providerConfigTransformer, logger, provider, }); @@ -276,6 +297,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { userTransformer?: UserTransformer; groupTransformer?: GroupTransformer; organizationTransformer?: OrganizationTransformer; + providerConfigTransformer?: ProviderConfigTransformer; }, ) {} @@ -300,7 +322,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 +341,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, @@ -338,7 +363,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { markCommitComplete(); } - private schedule(taskRunner: TaskRunner) { + private schedule(taskRunner: SchedulerServiceTaskRunner) { this.scheduleFn = async () => { const id = `${this.getProviderName()}:refresh`; await taskRunner.run({ diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index 8003dc39ac..c24a7bb60b 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,111 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.41 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-catalog-backend@1.25.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + +## 0.1.41-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.14.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-backend@1.24.1-next.3 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## 0.1.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-catalog-backend@1.24.1-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 0.1.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.24.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 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/api-report.md b/plugins/catalog-backend-module-openapi/api-report.md index b1685c3584..d79d852387 100644 --- a/plugins/catalog-backend-module-openapi/api-report.md +++ b/plugins/catalog-backend-module-openapi/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; import { Entity } from '@backstage/catalog-model'; @@ -12,10 +12,10 @@ import { LocationSpec } from '@backstage/plugin-catalog-common'; import { Logger } from 'winston'; import { PlaceholderResolverParams } from '@backstage/plugin-catalog-backend'; import { ScmIntegrations } from '@backstage/integration'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public -const catalogModuleJsonSchemaRefPlaceholderResolver: BackendFeatureCompat; +const catalogModuleJsonSchemaRefPlaceholderResolver: BackendFeature; export default catalogModuleJsonSchemaRefPlaceholderResolver; // @public (undocumented) @@ -31,14 +31,14 @@ export class OpenApiRefProcessor implements CatalogProcessor { constructor(options: { integrations: ScmIntegrations; logger: Logger; - reader: UrlReader; + reader: UrlReaderService; }); // (undocumented) static fromConfig( config: Config, options: { logger: Logger; - reader: UrlReader; + reader: UrlReaderService; }, ): OpenApiRefProcessor; // (undocumented) diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 7016701fc0..4bdfe575e2 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", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.ts b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.ts index 6b00519b2f..39de58466e 100644 --- a/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.ts +++ b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; @@ -29,11 +29,11 @@ import { Logger } from 'winston'; export class OpenApiRefProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrations; private readonly logger: Logger; - private readonly reader: UrlReader; + private readonly reader: UrlReaderService; static fromConfig( config: Config, - options: { logger: Logger; reader: UrlReader }, + options: { logger: Logger; reader: UrlReaderService }, ) { const integrations = ScmIntegrations.fromConfig(config); @@ -46,7 +46,7 @@ export class OpenApiRefProcessor implements CatalogProcessor { constructor(options: { integrations: ScmIntegrations; logger: Logger; - reader: UrlReader; + reader: UrlReaderService; }) { this.integrations = options.integrations; this.logger = options.logger; diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md index 4e5b25479a..0bb7df6890 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,108 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.2.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 9342ac8: Removed unused dependency +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.1.29-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-node@1.12.5-next.3 + +## 0.1.29-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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/api-report-alpha.md b/plugins/catalog-backend-module-puppetdb/api-report-alpha.md index 41af9e4eca..211add3895 100644 --- a/plugins/catalog-backend-module-puppetdb/api-report-alpha.md +++ b/plugins/catalog-backend-module-puppetdb/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const catalogModulePuppetDbEntityProvider: BackendFeatureCompat; +const catalogModulePuppetDbEntityProvider: BackendFeature; export default catalogModulePuppetDbEntityProvider; // (No @packageDocumentation comment for this package) diff --git a/plugins/catalog-backend-module-puppetdb/api-report.md b/plugins/catalog-backend-module-puppetdb/api-report.md index 3361313392..691b752a6b 100644 --- a/plugins/catalog-backend-module-puppetdb/api-report.md +++ b/plugins/catalog-backend-module-puppetdb/api-report.md @@ -8,10 +8,10 @@ import { EntityProvider } from '@backstage/plugin-catalog-node'; import { EntityProviderConnection } from '@backstage/plugin-catalog-node'; import { JsonValue } from '@backstage/types'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { ResourceEntity } from '@backstage/catalog-model'; -import { TaskRunner } from '@backstage/backend-tasks'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; // @public export const ANNOTATION_PUPPET_CERTNAME = 'puppet.com/certname'; @@ -30,8 +30,8 @@ export class PuppetDbEntityProvider implements EntityProvider { config: Config, deps: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; transformer?: ResourceTransformer; }, ): PuppetDbEntityProvider[]; @@ -45,7 +45,7 @@ export type PuppetDbEntityProviderConfig = { id: string; baseUrl: string; query?: string; - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; // @public diff --git a/plugins/catalog-backend-module-puppetdb/config.d.ts b/plugins/catalog-backend-module-puppetdb/config.d.ts index 8aab239047..39091efa93 100644 --- a/plugins/catalog-backend-module-puppetdb/config.d.ts +++ b/plugins/catalog-backend-module-puppetdb/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; /** * Represents the configuration for the Backstage. @@ -44,7 +44,7 @@ export interface Config { /** * (Optional) Task schedule definition for the refresh. */ - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; } | { [name: string]: { @@ -59,7 +59,7 @@ export interface Config { /** * (Optional) Task schedule definition for the refresh. */ - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; }; }; diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 1759eebb14..3a12da0e66 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.2.0", "description": "A Backstage catalog backend module that helps integrate towards PuppetDB", "backstage": { "role": "backend-plugin-module", @@ -53,9 +53,7 @@ "test": "backstage-cli package test" }, "dependencies": { - "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", @@ -63,7 +61,7 @@ "@backstage/types": "workspace:^", "lodash": "^4.17.21", "luxon": "^3.0.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "uuid": "^9.0.0" }, "devDependencies": { diff --git a/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.test.ts b/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.test.ts index 4fee7e2cff..68f8b08a3b 100644 --- a/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.test.ts +++ b/plugins/catalog-backend-module-puppetdb/src/module/catalogModulePuppetDbEntityProvider.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { SchedulerServiceTaskScheduleDefinition } from '@backstage/backend-plugin-api'; import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node/alpha'; import { catalogModulePuppetDbEntityProvider } from './catalogModulePuppetDbEntityProvider'; @@ -23,7 +23,7 @@ import { PuppetDbEntityProvider } from '../providers/PuppetDbEntityProvider'; describe('catalogModulePuppetDbEntityProvider', () => { it('should register provider at the catalog extension point', async () => { let addedProviders: Array | undefined; - let usedSchedule: TaskScheduleDefinition | undefined; + let usedSchedule: SchedulerServiceTaskScheduleDefinition | undefined; const extensionPoint = { addEntityProvider: (providers: any) => { diff --git a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.test.ts b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.test.ts index bd76aa2048..bdeed6d2df 100644 --- a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.test.ts +++ b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.test.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { PuppetDbEntityProvider } from './PuppetDbEntityProvider'; import { @@ -39,14 +42,14 @@ jest.mock('../puppet/read', () => { const logger = mockServices.logger.mock(); -class PersistingTaskRunner implements TaskRunner { - private tasks: TaskInvocationDefinition[] = []; +class PersistingTaskRunner implements SchedulerServiceTaskRunner { + private tasks: SchedulerServiceTaskInvocationDefinition[] = []; getTasks() { return this.tasks; } - run(task: TaskInvocationDefinition): Promise { + run(task: SchedulerServiceTaskInvocationDefinition): Promise { this.tasks.push(task); return Promise.resolve(undefined); } diff --git a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.ts b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.ts index 0496cc033b..fc13f45c39 100644 --- a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.ts +++ b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProvider.ts @@ -23,7 +23,6 @@ import { readProviderConfigs, } from './PuppetDbEntityProviderConfig'; import { Config } from '@backstage/config'; -import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import * as uuid from 'uuid'; import { defaultResourceTransformer, ResourceTransformer } from '../puppet'; import { @@ -34,7 +33,11 @@ import { import { merge } from 'lodash'; import { readPuppetNodes } from '../puppet/read'; import { ENDPOINT_NODES } from '../puppet/constants'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { + SchedulerService, + SchedulerServiceTaskRunner, + LoggerService, +} from '@backstage/backend-plugin-api'; /** * Reads nodes from [PuppetDB](https://www.puppet.com/docs/puppet/6/puppetdb_overview.html) @@ -61,8 +64,8 @@ export class PuppetDbEntityProvider implements EntityProvider { config: Config, deps: { logger: LoggerService; - schedule?: TaskRunner; - scheduler?: PluginTaskScheduler; + schedule?: SchedulerServiceTaskRunner; + scheduler?: SchedulerService; transformer?: ResourceTransformer; }, ): PuppetDbEntityProvider[] { @@ -97,7 +100,7 @@ export class PuppetDbEntityProvider implements EntityProvider { * * @param config - Configuration of the provider. * @param logger - The instance of a {@link LoggerService}. - * @param taskRunner - The instance of {@link TaskRunner}. + * @param taskRunner - The instance of {@link SchedulerServiceTaskRunner}. * @param transformer - A {@link ResourceTransformer} function. * * @private @@ -105,7 +108,7 @@ export class PuppetDbEntityProvider implements EntityProvider { private constructor( config: PuppetDbEntityProviderConfig, logger: LoggerService, - taskRunner: TaskRunner, + taskRunner: SchedulerServiceTaskRunner, transformer: ResourceTransformer, ) { this.config = config; @@ -130,11 +133,13 @@ export class PuppetDbEntityProvider implements EntityProvider { /** * Creates a function that can be used to schedule a refresh of the catalog. * - * @param taskRunner - The instance of {@link TaskRunner}. + * @param taskRunner - The instance of {@link SchedulerServiceTaskRunner}. * * @private */ - private createScheduleFn(taskRunner: TaskRunner): () => Promise { + private createScheduleFn( + taskRunner: SchedulerServiceTaskRunner, + ): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; return taskRunner.run({ diff --git a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProviderConfig.ts b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProviderConfig.ts index 7b95b7f387..ed640b190e 100644 --- a/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-puppetdb/src/providers/PuppetDbEntityProviderConfig.ts @@ -15,9 +15,9 @@ */ import { - readTaskScheduleDefinitionFromConfig, - TaskScheduleDefinition, -} from '@backstage/backend-tasks'; + SchedulerServiceTaskScheduleDefinition, + readSchedulerServiceTaskScheduleDefinitionFromConfig, +} from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { DEFAULT_PROVIDER_ID } from './constants'; @@ -42,7 +42,7 @@ export type PuppetDbEntityProviderConfig = { /** * (Optional) Task schedule definition for the refresh. */ - schedule?: TaskScheduleDefinition; + schedule?: SchedulerServiceTaskScheduleDefinition; }; /** @@ -87,7 +87,9 @@ function readProviderConfig( const query = config.getOptionalString('query'); const schedule = config.has('schedule') - ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + ? readSchedulerServiceTaskScheduleDefinitionFromConfig( + config.getConfig('schedule'), + ) : undefined; return { 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..11075a73cf 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,82 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-scaffolder-common@1.5.5 + +## 0.1.21-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-scaffolder-common@1.5.5-next.2 + +## 0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.1 + - @backstage/catalog-model@1.5.0 + +## 0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.0 + - @backstage/catalog-model@1.5.0 + +## 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/api-report.md b/plugins/catalog-backend-module-scaffolder-entity-model/api-report.md index 1a0eabbec4..49542b0396 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/api-report.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/api-report.md @@ -3,14 +3,14 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node'; import { Entity } from '@backstage/catalog-model'; import { LocationSpec } from '@backstage/plugin-catalog-common'; // @public -const catalogModuleScaffolderEntityModel: BackendFeatureCompat; +const catalogModuleScaffolderEntityModel: BackendFeature; export default catalogModuleScaffolderEntityModel; // @public diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json index a2891d2d4d..fdb0027016 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", "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..0d7ab37983 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,103 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4 + +## 0.4.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4-next.1 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## 0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + +## 0.4.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.4-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 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/api-report.md b/plugins/catalog-backend-module-unprocessed/api-report.md index 044d6d3501..493082ac7c 100644 --- a/plugins/catalog-backend-module-unprocessed/api-report.md +++ b/plugins/catalog-backend-module-unprocessed/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { HttpRouterService } from '@backstage/backend-plugin-api'; @@ -11,7 +11,7 @@ import { Knex } from 'knex'; import { PermissionsService } from '@backstage/backend-plugin-api'; // @public -const catalogModuleUnprocessedEntities: BackendFeatureCompat; +const catalogModuleUnprocessedEntities: BackendFeature; export default catalogModuleUnprocessedEntities; // @public diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 582be2ffe8..ad91bd573d 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", "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..c6347c35d3 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,182 @@ # @backstage/plugin-catalog-backend +## 1.25.0 + +### Minor Changes + +- 163ba08: Deprecated `RouterOptions`, `CatalogBuilder`, and `CatalogEnvironment`. Please make sure to upgrade to the new backend system. +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### Patch Changes + +- 776eb56: `ProcessorOutputCollector` returns an error when receiving deferred entities that have an invalid `metadata.annotations` format. + + This allows to return an error on an actual validation issue instead of reporting that the location annotations are missing afterwards, which is misleading for the users. + +- 389f5a4: Update deprecated url-reader-related imports. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint +- 51240ee: Preserve default `allowedLocationTypes` when `setAllowedLocationTypes()` of `CatalogLocationsExtensionPoint` is not called. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-search-backend-module-catalog@0.2.0 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/backend-openapi-utils@0.1.16 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-events-node@0.3.9 + +## 1.24.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/backend-openapi-utils@0.1.16-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.3 + +## 1.24.1-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/backend-openapi-utils@0.1.16-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @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/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.24.1-next.1 + +### Patch Changes + +- 51240ee: Preserve default `allowedLocationTypes` when `setAllowedLocationTypes()` of `CatalogLocationsExtensionPoint` is not called. +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.29-next.1 + - @backstage/backend-openapi-utils@0.1.16-next.1 + - @backstage/backend-tasks@0.5.28-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-events-node@0.3.9-next.1 + +## 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/api-report-alpha.md b/plugins/catalog-backend/api-report-alpha.md index 260f59e70f..74f1e7d1b4 100644 --- a/plugins/catalog-backend/api-report-alpha.md +++ b/plugins/catalog-backend/api-report-alpha.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Conditions } from '@backstage/plugin-permission-node'; import { EntitiesSearchFilter } from '@backstage/plugin-catalog-node'; @@ -75,7 +75,7 @@ export type CatalogPermissionRule< > = PermissionRule; // @alpha -const catalogPlugin: BackendFeatureCompat; +const catalogPlugin: BackendFeature; export default catalogPlugin; // @alpha diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index f4c10d01a2..7717066d2a 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -26,6 +26,7 @@ import { CatalogProcessorRefreshKeysResult as CatalogProcessorRefreshKeysResult_ import { CatalogProcessorRelationResult as CatalogProcessorRelationResult_2 } from '@backstage/plugin-catalog-node'; import { CatalogProcessorResult as CatalogProcessorResult_2 } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; +import { DatabaseService } from '@backstage/backend-plugin-api'; import { DefaultCatalogCollatorFactory as DefaultCatalogCollatorFactory_2 } from '@backstage/plugin-search-backend-module-catalog'; import { DefaultCatalogCollatorFactoryOptions as DefaultCatalogCollatorFactoryOptions_2 } from '@backstage/plugin-search-backend-module-catalog'; import { DeferredEntity as DeferredEntity_2 } from '@backstage/plugin-catalog-node'; @@ -56,14 +57,14 @@ import { PlaceholderResolver as PlaceholderResolver_2 } from '@backstage/plugin- import { PlaceholderResolverParams as PlaceholderResolverParams_2 } from '@backstage/plugin-catalog-node'; import { PlaceholderResolverRead as PlaceholderResolverRead_2 } from '@backstage/plugin-catalog-node'; import { PlaceholderResolverResolveUrl as PlaceholderResolverResolveUrl_2 } from '@backstage/plugin-catalog-node'; -import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { RootConfigService } from '@backstage/backend-plugin-api'; import { Router } from 'express'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmLocationAnalyzer as ScmLocationAnalyzer_2 } from '@backstage/plugin-catalog-node'; import { TokenManager } from '@backstage/backend-common'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { Validators } from '@backstage/catalog-model'; // @public @deprecated @@ -137,7 +138,7 @@ export const CATALOG_CONFLICTS_TOPIC = 'experimental.catalog.conflict'; // @public (undocumented) export const CATALOG_ERRORS_TOPIC = 'experimental.catalog.errors'; -// @public +// @public @deprecated export class CatalogBuilder { addEntityPolicy( ...policies: Array> @@ -192,14 +193,14 @@ export class CatalogBuilder { export type CatalogCollatorEntityTransformer = CatalogCollatorEntityTransformer_2; -// @public (undocumented) +// @public @deprecated (undocumented) export type CatalogEnvironment = { logger: LoggerService; - database: PluginDatabaseManager; - config: Config; - reader: UrlReader; + database: DatabaseService; + config: RootConfigService; + reader: UrlReaderService; permissions: PermissionsService | PermissionAuthorizer; - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; discovery?: DiscoveryService; auth?: AuthService; httpAuth?: HttpAuthService; @@ -254,14 +255,14 @@ export class CodeOwnersProcessor implements CatalogProcessor_2 { constructor(options: { integrations: ScmIntegrationRegistry; logger: LoggerService; - reader: UrlReader; + reader: UrlReaderService; }); // (undocumented) static fromConfig( config: Config, options: { logger: LoggerService; - reader: UrlReader; + reader: UrlReaderService; }, ): CodeOwnersProcessor; // (undocumented) @@ -413,7 +414,7 @@ export class PlaceholderProcessor implements CatalogProcessor_2 { // @public (undocumented) export type PlaceholderProcessorOptions = { resolvers: Record; - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrationRegistry; }; @@ -465,7 +466,7 @@ export function transformLegacyPolicyToProcessor( // @public (undocumented) export class UrlReaderProcessor implements CatalogProcessor_2 { - constructor(options: { reader: UrlReader; logger: LoggerService }); + constructor(options: { reader: UrlReaderService; logger: LoggerService }); // (undocumented) getProcessorName(): string; // (undocumented) diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 596e51435e..4003fd2b6a 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.25.0", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin", @@ -64,7 +64,6 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-openapi-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", @@ -90,7 +89,7 @@ "lodash": "^4.17.21", "luxon": "^3.0.0", "minimatch": "^9.0.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "p-limit": "^3.0.2", "prom-client": "^15.0.0", "uuid": "^9.0.0", diff --git a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.ts b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.ts index b4ed33d162..9b8d2a3bbf 100644 --- a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.ts +++ b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { @@ -24,7 +23,7 @@ import { import { LocationSpec } from '@backstage/plugin-catalog-common'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; import { findCodeOwnerByTarget } from './lib'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; const ALLOWED_KINDS = ['API', 'Component', 'Domain', 'Resource', 'System']; const ALLOWED_LOCATION_TYPES = ['url']; @@ -33,11 +32,11 @@ const ALLOWED_LOCATION_TYPES = ['url']; export class CodeOwnersProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; private readonly logger: LoggerService; - private readonly reader: UrlReader; + private readonly reader: UrlReaderService; static fromConfig( config: Config, - options: { logger: LoggerService; reader: UrlReader }, + options: { logger: LoggerService; reader: UrlReaderService }, ) { const integrations = ScmIntegrations.fromConfig(config); @@ -50,7 +49,7 @@ export class CodeOwnersProcessor implements CatalogProcessor { constructor(options: { integrations: ScmIntegrationRegistry; logger: LoggerService; - reader: UrlReader; + reader: UrlReaderService; }) { this.integrations = options.integrations; this.logger = options.logger; diff --git a/plugins/catalog-backend/src/modules/codeowners/lib/read.ts b/plugins/catalog-backend/src/modules/codeowners/lib/read.ts index 6a5234c94f..155405ddf8 100644 --- a/plugins/catalog-backend/src/modules/codeowners/lib/read.ts +++ b/plugins/catalog-backend/src/modules/codeowners/lib/read.ts @@ -14,15 +14,15 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { NotFoundError } from '@backstage/errors'; import { ScmIntegration } from '@backstage/integration'; import 'core-js/features/promise'; // NOTE: This can be removed when ES2021 is implemented import { resolveCodeOwner } from './resolve'; import { scmCodeOwnersPaths } from './scm'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; export async function readCodeOwners( - reader: UrlReader, + reader: UrlReaderService, sourceUrl: string, codeownersPaths: string[], ): Promise { @@ -49,7 +49,7 @@ export async function readCodeOwners( } export async function findCodeOwnerByTarget( - reader: UrlReader, + reader: UrlReaderService, targetUrl: string, scmIntegration: ScmIntegration, ): Promise { diff --git a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts index 094f8f1df8..c2c49ea1cb 100644 --- a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; @@ -29,11 +28,12 @@ import { textPlaceholderResolver, yamlPlaceholderResolver, } from './PlaceholderProcessor'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); describe('PlaceholderProcessor', () => { - const reader: jest.Mocked = { + const reader: jest.Mocked = { readTree: jest.fn(), search: jest.fn(), readUrl: jest.fn(), diff --git a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts index f6431cc9f5..bdbb0433d7 100644 --- a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; @@ -27,11 +26,12 @@ import { PlaceholderResolverParams, processingResult, } from '@backstage/plugin-catalog-node'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; /** @public */ export type PlaceholderProcessorOptions = { resolvers: Record; - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrationRegistry; }; diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts index dd80f8f0c4..7e895c5344 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts @@ -14,10 +14,9 @@ * limitations under the License. */ -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'; @@ -31,6 +30,8 @@ import { } from '@backstage/plugin-catalog-node'; import { defaultEntityDataParser } from '../util/parse'; import { UrlReaderProcessor } from './UrlReaderProcessor'; +import { UrlReaders } from '@backstage/backend-defaults/urlReader'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('UrlReaderProcessor', () => { const mockApiOrigin = 'http://localhost'; @@ -39,7 +40,7 @@ describe('UrlReaderProcessor', () => { set: jest.fn(), }; const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); @@ -192,7 +193,7 @@ describe('UrlReaderProcessor', () => { it('uses search when there are globs', async () => { const logger = mockServices.logger.mock(); - const reader: jest.Mocked = { + const reader: jest.Mocked = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn().mockImplementation(async () => []), diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts index 89f9e74685..b623098171 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { assertError } from '@backstage/errors'; import limiterFactory from 'p-limit'; @@ -29,7 +28,7 @@ import { CatalogProcessorResult, processingResult, } from '@backstage/plugin-catalog-node'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { LoggerService, UrlReaderService } from '@backstage/backend-plugin-api'; const CACHE_KEY = 'v1'; @@ -47,7 +46,7 @@ type CacheItem = { export class UrlReaderProcessor implements CatalogProcessor { constructor( private readonly options: { - reader: UrlReader; + reader: UrlReaderService; logger: LoggerService; }, ) {} diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts index 6020811ab0..e42847bafe 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts @@ -29,7 +29,6 @@ import { createCounterMetric, createSummaryMetric } from '../util/metrics'; import { CatalogProcessingOrchestrator, EntityProcessingResult } from './types'; import { Stitcher, stitchingStrategyFromConfig } from '../stitching/types'; import { startTaskPipeline } from './TaskPipeline'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { addEntityAttributes, @@ -39,7 +38,7 @@ import { import { deleteOrphanedEntities } from '../database/operations/util/deleteOrphanedEntities'; import { EventBroker, EventsService } from '@backstage/plugin-events-node'; import { CATALOG_ERRORS_TOPIC } from '../constants'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { LoggerService, SchedulerService } from '@backstage/backend-plugin-api'; const CACHE_TTL = 5; @@ -55,7 +54,7 @@ export type ProgressTracker = ReturnType; // is just one. export class DefaultCatalogProcessingEngine { private readonly config: Config; - private readonly scheduler?: PluginTaskScheduler; + private readonly scheduler?: SchedulerService; private readonly logger: LoggerService; private readonly knex: Knex; private readonly processingDatabase: ProcessingDatabase; @@ -75,7 +74,7 @@ export class DefaultCatalogProcessingEngine { constructor(options: { config: Config; - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; logger: LoggerService; knex: Knex; processingDatabase: ProcessingDatabase; diff --git a/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts b/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts index eb8cbcc81a..0de6b12b90 100644 --- a/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts +++ b/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts @@ -115,24 +115,27 @@ export class ProcessorOutputCollector { // Note that at this point, we have only validated the envelope part of // the entity data. Annotations are not part of that, so we have to be - // defensive. If the annotations were malformed (e.g. were not a valid - // object), we just skip over this step and let the full entity - // validation at the next step of processing catch that. + // defensive and report an error if the annotations isn't a valid object, to avoid + // hiding errors when adding location annotations. const annotations = entity.metadata.annotations || {}; - if (typeof annotations === 'object' && !Array.isArray(annotations)) { - const originLocation = getEntityOriginLocationRef(this.parentEntity); - entity = { - ...entity, - metadata: { - ...entity.metadata, - annotations: { - ...annotations, - [ANNOTATION_ORIGIN_LOCATION]: originLocation, - [ANNOTATION_LOCATION]: location, - }, - }, - }; + if (typeof annotations !== 'object' || Array.isArray(annotations)) { + this.errors.push( + new Error('metadata.annotations must be a valid object'), + ); + return; } + const originLocation = getEntityOriginLocationRef(this.parentEntity); + entity = { + ...entity, + metadata: { + ...entity.metadata, + annotations: { + ...annotations, + [ANNOTATION_ORIGIN_LOCATION]: originLocation, + [ANNOTATION_LOCATION]: location, + }, + }, + }; this.deferredEntities.push({ entity, locationKey: location }); } else if (i.type === 'location') { 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/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index aabef51195..8f020c8c7c 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -17,10 +17,7 @@ import { createLegacyAuthAdapters, HostDiscovery, - PluginDatabaseManager, - UrlReader, } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { DefaultNamespaceEntityPolicy, Entity, @@ -108,10 +105,14 @@ import { EventBroker, EventsService } from '@backstage/plugin-events-node'; import { durationToMilliseconds } from '@backstage/types'; import { AuthService, + DatabaseService, DiscoveryService, HttpAuthService, LoggerService, PermissionsService, + RootConfigService, + UrlReaderService, + SchedulerService, } from '@backstage/backend-plugin-api'; /** @@ -123,14 +124,17 @@ export type CatalogPermissionRuleInput< TParams extends PermissionRuleParams = PermissionRuleParams, > = PermissionRule; -/** @public */ +/** + * @deprecated Please migrate to the new backend system as this will be removed in the future. + * @public + */ export type CatalogEnvironment = { logger: LoggerService; - database: PluginDatabaseManager; - config: Config; - reader: UrlReader; + database: DatabaseService; + config: RootConfigService; + reader: UrlReaderService; permissions: PermissionsService | PermissionAuthorizer; - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; discovery?: DiscoveryService; auth?: AuthService; httpAuth?: HttpAuthService; @@ -160,6 +164,7 @@ export type CatalogEnvironment = { * persisted in the catalog. * * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export class CatalogBuilder { private readonly env: CatalogEnvironment; 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-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 774ee61a6d..8207db879d 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -47,12 +47,12 @@ import { validateRequestBody, } from './util'; import { createOpenApiRouter } from '../schema/openapi.generated'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { parseEntityPaginationParams } from './request/parseEntityPaginationParams'; import { AuthService, HttpAuthService, LoggerService, + SchedulerService, } from '@backstage/backend-plugin-api'; import { LocationAnalyzer } from '@backstage/plugin-catalog-node'; @@ -60,6 +60,7 @@ import { LocationAnalyzer } from '@backstage/plugin-catalog-node'; * Options used by {@link createRouter}. * * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export interface RouterOptions { entitiesCatalog?: EntitiesCatalog; @@ -67,7 +68,7 @@ export interface RouterOptions { locationService: LocationService; orchestrator?: CatalogProcessingOrchestrator; refreshService?: RefreshService; - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; logger: LoggerService; config: Config; permissionIntegrationRouter?: express.Router; @@ -77,8 +78,6 @@ export interface RouterOptions { /** * Creates a catalog router. - * - * @public */ export async function createRouter( options: RouterOptions, diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index fd3c714f97..f15f53aef4 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-catalog-common +## 1.0.26 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-search-common@1.2.14 + - @backstage/catalog-model@1.6.0 + +## 1.0.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-search-common@1.2.14-next.1 + +## 1.0.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/catalog-model@1.5.0 + +## 1.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/catalog-model@1.5.0 + +## 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..b9917b9d35 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.26", "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..a1715bbbca 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,132 @@ # @backstage/plugin-catalog-graph +## 0.4.8 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- fba7537: Memoize entity graph nodes when applying an `entityFilter` to prevent repeated redraws +- 4a529c2: Use `entityPresentationApi` for the node title and the icon. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## 0.4.8-next.4 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## 0.4.8-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## 0.4.8-next.2 + +### Patch Changes + +- 4a529c2: Use `entityPresentationApi` for the node title and the icon. +- Updated dependencies + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-react@1.12.3-next.1 + +## 0.4.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/types@1.1.1 + +## 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/README-alpha.md b/plugins/catalog-graph/README-alpha.md index 52c09c4b50..784066c44f 100644 --- a/plugins/catalog-graph/README-alpha.md +++ b/plugins/catalog-graph/README-alpha.md @@ -126,7 +126,7 @@ The Catalog graphics plugin provides extensions for each of its features, see be #### Catalog Entity Relations Graph Card -An [entity card](https://backstage.io/docs/frontend-system/building-plugins/extension-types#entitycard---reference) extension that renders the relation graph for an entity on the Catalog entity page and has an action that redirects users to the more advanced [relations graph page](#catalog-entity-relations-graph-page). +An [entity card](https://backstage.io/docs/frontend-system/building-plugins/extension-blueprints#entitycard---reference) extension that renders the relation graph for an entity on the Catalog entity page and has an action that redirects users to the more advanced [relations graph page](#catalog-entity-relations-graph-page). | kind | namespace | name | id | Default enabled | | ----------- | ------------- | ---------------- | ------------------------------------- | --------------- | diff --git a/plugins/catalog-graph/api-report-alpha.md b/plugins/catalog-graph/api-report-alpha.md index 3895f12fcf..6ab438def3 100644 --- a/plugins/catalog-graph/api-report-alpha.md +++ b/plugins/catalog-graph/api-report-alpha.md @@ -3,8 +3,16 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { Direction } from '@backstage/plugin-catalog-graph'; +import { Entity } from '@backstage/catalog-model'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @public (undocumented) @@ -13,13 +21,140 @@ const _default: BackstagePlugin< catalogGraph: RouteRef; }, { - catalogEntity: ExternalRouteRef< + catalogEntity: ExternalRouteRef<{ + name: string; + kind: string; + namespace: string; + }>; + }, + { + 'entity-card:catalog-graph/relations': ExtensionDefinition< { - name: string; - kind: string; - namespace: string; + kinds: string[] | undefined; + relations: string[] | undefined; + maxDepth: number | undefined; + unidirectional: boolean | undefined; + mergeRelations: boolean | undefined; + direction: Direction | undefined; + relationPairs: [string, string][] | undefined; + zoom: 'disabled' | 'enabled' | 'enable-on-click' | undefined; + curve: 'curveStepBefore' | 'curveMonotoneX' | undefined; + title: string | undefined; + height: number | undefined; + } & { + filter: string | undefined; }, - true + { + height?: number | undefined; + curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; + direction?: Direction | undefined; + title?: string | undefined; + zoom?: 'disabled' | 'enabled' | 'enable-on-click' | undefined; + relations?: string[] | undefined; + maxDepth?: number | undefined; + kinds?: string[] | undefined; + unidirectional?: boolean | undefined; + mergeRelations?: boolean | undefined; + relationPairs?: [string, string][] | undefined; + } & { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + { + kind: 'entity-card'; + namespace: undefined; + name: 'relations'; + } + >; + 'page:catalog-graph': ExtensionDefinition< + { + selectedKinds: string[] | undefined; + selectedRelations: string[] | undefined; + rootEntityRefs: string[] | undefined; + maxDepth: number | undefined; + unidirectional: boolean | undefined; + mergeRelations: boolean | undefined; + direction: Direction | undefined; + showFilters: boolean | undefined; + curve: 'curveStepBefore' | 'curveMonotoneX' | undefined; + kinds: string[] | undefined; + relations: string[] | undefined; + relationPairs: [string, string][] | undefined; + zoom: 'disabled' | 'enabled' | 'enable-on-click' | undefined; + } & { + path: string | undefined; + }, + { + curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; + direction?: Direction | undefined; + zoom?: 'disabled' | 'enabled' | 'enable-on-click' | undefined; + relations?: string[] | undefined; + rootEntityRefs?: string[] | undefined; + maxDepth?: number | undefined; + kinds?: string[] | undefined; + unidirectional?: boolean | undefined; + mergeRelations?: boolean | undefined; + relationPairs?: [string, string][] | undefined; + selectedRelations?: string[] | undefined; + selectedKinds?: string[] | undefined; + showFilters?: boolean | undefined; + } & { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + { + kind: 'page'; + namespace: undefined; + name: undefined; + } >; } >; diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 82f3e5d171..326a80fdf2 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", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-graph", diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx index 6d8429332e..8186fd3b96 100644 --- a/plugins/catalog-graph/src/alpha.tsx +++ b/plugins/catalog-graph/src/alpha.tsx @@ -16,87 +16,77 @@ import React from 'react'; import { - createPageExtension, - createPlugin, - createSchemaFromZod, + createFrontendPlugin, + PageBlueprint, } from '@backstage/frontend-plugin-api'; import { compatWrapper, convertLegacyRouteRef, } from '@backstage/core-compat-api'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { catalogGraphRouteRef, catalogEntityRouteRef } from './routes'; -import { Direction } from './components'; +import { Direction } from '@backstage/plugin-catalog-graph'; -function getEntityGraphRelationsConfigSchema( - z: Parameters[0]>[0], -) { - // Mapping EntityRelationsGraphProps to config - // The classname and render functions are configurable only via extension overrides - return z.object({ - kinds: z.array(z.string()).optional(), - relations: z.array(z.string()).optional(), - maxDepth: z.number().optional(), - unidirectional: z.boolean().optional(), - mergeRelations: z.boolean().optional(), - direction: z.nativeEnum(Direction).optional(), - relationPairs: z.array(z.tuple([z.string(), z.string()])).optional(), - zoom: z.enum(['enabled', 'disabled', 'enable-on-click']).optional(), - curve: z.enum(['curveStepBefore', 'curveMonotoneX']).optional(), - }); -} - -const CatalogGraphEntityCard = createEntityCardExtension({ +const CatalogGraphEntityCard = EntityCardBlueprint.makeWithOverrides({ name: 'relations', - configSchema: createSchemaFromZod(z => - z - .object({ - // Filter is a config required to all entity cards - filter: z.string().optional(), - title: z.string().optional(), - height: z.number().optional(), - // Skipping a "variant" config for now, defaulting to "gridItem" in the component - // For more details, see this comment: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 - }) - .merge(getEntityGraphRelationsConfigSchema(z)), - ), - loader: async ({ config: { filter, ...props } }) => - import('./components/CatalogGraphCard').then(m => - compatWrapper(), - ), + config: { + schema: { + kinds: z => z.array(z.string()).optional(), + relations: z => z.array(z.string()).optional(), + maxDepth: z => z.number().optional(), + unidirectional: z => z.boolean().optional(), + mergeRelations: z => z.boolean().optional(), + direction: z => z.nativeEnum(Direction).optional(), + relationPairs: z => z.array(z.tuple([z.string(), z.string()])).optional(), + zoom: z => z.enum(['enabled', 'disabled', 'enable-on-click']).optional(), + curve: z => z.enum(['curveStepBefore', 'curveMonotoneX']).optional(), + // Skipping a "variant" config for now, defaulting to "gridItem" in the component + // For more details, see this comment: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252 + title: z => z.string().optional(), + height: z => z.number().optional(), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + loader: async () => + import('./components/CatalogGraphCard').then(m => + compatWrapper(), + ), + }); + }, }); -const CatalogGraphPage = createPageExtension({ - defaultPath: '/catalog-graph', - routeRef: convertLegacyRouteRef(catalogGraphRouteRef), - configSchema: createSchemaFromZod(z => - z.object({ - // Path is a default config required to all pages - path: z.string().default('/catalog-graph'), - // Mapping intialState prop to config, these are the initial filter values, as opposed to configuration of the available filter values - initialState: z - .object({ - selectedKinds: z.array(z.string()).optional(), - selectedRelations: z.array(z.string()).optional(), - rootEntityRefs: z.array(z.string()).optional(), - maxDepth: z.number().optional(), - unidirectional: z.boolean().optional(), - mergeRelations: z.boolean().optional(), - direction: z.nativeEnum(Direction).optional(), - showFilters: z.boolean().optional(), - curve: z.enum(['curveStepBefore', 'curveMonotoneX']).optional(), - }) - .merge(getEntityGraphRelationsConfigSchema(z)) - .optional(), - }), - ), - loader: ({ config: { path, ...props } }) => - import('./components/CatalogGraphPage').then(m => - compatWrapper(), - ), +const CatalogGraphPage = PageBlueprint.makeWithOverrides({ + config: { + schema: { + selectedKinds: z => z.array(z.string()).optional(), + selectedRelations: z => z.array(z.string()).optional(), + rootEntityRefs: z => z.array(z.string()).optional(), + maxDepth: z => z.number().optional(), + unidirectional: z => z.boolean().optional(), + mergeRelations: z => z.boolean().optional(), + direction: z => z.nativeEnum(Direction).optional(), + showFilters: z => z.boolean().optional(), + curve: z => z.enum(['curveStepBefore', 'curveMonotoneX']).optional(), + kinds: z => z.array(z.string()).optional(), + relations: z => z.array(z.string()).optional(), + relationPairs: z => z.array(z.tuple([z.string(), z.string()])).optional(), + zoom: z => z.enum(['enabled', 'disabled', 'enable-on-click']).optional(), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + defaultPath: '/catalog-graph', + routeRef: convertLegacyRouteRef(catalogGraphRouteRef), + loader: () => + import('./components/CatalogGraphPage').then(m => + compatWrapper(), + ), + }); + }, }); -export default createPlugin({ +export default createFrontendPlugin({ id: 'catalog-graph', routes: { catalogGraph: convertLegacyRouteRef(catalogGraphRouteRef), diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.test.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.test.tsx index 1f3d77e1ae..10aa62f946 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.test.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.test.tsx @@ -47,6 +47,7 @@ describe('', () => { ); expect(screen.getByText('kind:namespace/name')).toBeInTheDocument(); + expect(screen.getByText('namespace/name')).toBeInTheDocument(); }); test('renders node, skips default namespace', async () => { @@ -73,7 +74,7 @@ describe('', () => { , ); - expect(screen.getByText('kind:name')).toBeInTheDocument(); + expect(screen.getByText('name')).toBeInTheDocument(); }); test('renders node with onClick', async () => { @@ -102,8 +103,8 @@ describe('', () => { , ); - expect(screen.getByText('kind:namespace/name')).toBeInTheDocument(); - await userEvent.click(screen.getByText('kind:namespace/name')); + expect(screen.getByText('namespace/name')).toBeInTheDocument(); + await userEvent.click(screen.getByText('namespace/name')); expect(onClick).toHaveBeenCalledTimes(1); }); @@ -134,5 +135,6 @@ describe('', () => { ); expect(screen.getByText('Custom Title')).toBeInTheDocument(); + expect(screen.getByText('kind:namespace/name')).toBeInTheDocument(); }); }); diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.tsx index d55f4c015b..bc3311d33f 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/DefaultRenderNode.tsx @@ -14,12 +14,12 @@ * limitations under the License. */ import { DependencyGraphTypes } from '@backstage/core-components'; -import { useApp } from '@backstage/core-plugin-api'; -import { humanizeEntityRef } from '@backstage/plugin-catalog-react'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { useEntityPresentation } from '@backstage/plugin-catalog-react'; import { makeStyles } from '@material-ui/core/styles'; import classNames from 'classnames'; import React, { useLayoutEffect, useRef, useState } from 'react'; -import { EntityKindIcon } from './EntityKindIcon'; +import { EntityIcon } from './EntityIcon'; import { EntityNodeData } from './types'; import { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; @@ -64,8 +64,10 @@ export function DefaultRenderNode({ const classes = useStyles(); const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); - const app = useApp(); const idRef = useRef(null); + const entityRefPresentationSnapshot = useEntityPresentation(entity, { + defaultNamespace: DEFAULT_NAMESPACE, + }); useLayoutEffect(() => { // set the width to the length of the ID @@ -82,25 +84,14 @@ export function DefaultRenderNode({ } }, [width, height]); - const { - kind, - metadata: { name, namespace = DEFAULT_NAMESPACE, title }, - } = entity; - - const hasKindIcon = app.getSystemIcon( - `kind:${kind.toLocaleLowerCase('en-US')}`, - ); + const hasKindIcon = !!entityRefPresentationSnapshot.Icon; const padding = 10; const iconSize = height; const paddedIconWidth = hasKindIcon ? iconSize + padding : 0; const paddedWidth = paddedIconWidth + width + padding * 2; const paddedHeight = height + padding * 2; - const displayTitle = - title ?? - (kind && name && namespace - ? humanizeEntityRef({ kind, name, namespace }) - : id); + const displayTitle = entityRefPresentationSnapshot.primaryTitle ?? id; return ( @@ -115,8 +106,8 @@ export function DefaultRenderNode({ rx={10} /> {hasKindIcon && ( - {displayTitle} + {entityRefPresentationSnapshot.entityRef} ); } diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityKindIcon.test.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityIcon.test.tsx similarity index 81% rename from plugins/catalog-graph/src/components/EntityRelationsGraph/EntityKindIcon.test.tsx rename to plugins/catalog-graph/src/components/EntityRelationsGraph/EntityIcon.test.tsx index cc47c8850b..8683af9c1e 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityKindIcon.test.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityIcon.test.tsx @@ -16,12 +16,14 @@ import { renderInTestApp } from '@backstage/test-utils'; import React from 'react'; -import { EntityKindIcon } from './EntityKindIcon'; +import { EntityIcon } from './EntityIcon'; +import MuiMemoryIcon from '@material-ui/icons/Memory'; +import { IconComponent } from '@backstage/core-plugin-api'; describe('', () => { it('renders without exploding', async () => { const { baseElement } = await renderInTestApp( - , + , ); expect(baseElement.querySelector('svg')).toBeInTheDocument(); @@ -29,7 +31,7 @@ describe('', () => { it('renders without exploding for unknown kind', async () => { const { baseElement } = await renderInTestApp( - , + , ); expect(baseElement.querySelector('svg')).toBeInTheDocument(); diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityKindIcon.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityIcon.tsx similarity index 69% rename from plugins/catalog-graph/src/components/EntityRelationsGraph/EntityKindIcon.tsx rename to plugins/catalog-graph/src/components/EntityRelationsGraph/EntityIcon.tsx index 7263e25eae..a46a8bcc9d 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityKindIcon.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityIcon.tsx @@ -13,23 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useApp } from '@backstage/core-plugin-api'; import React from 'react'; import SvgIcon from '@material-ui/core/SvgIcon'; +import { OverridableComponent } from '@material-ui/core/OverridableComponent'; +import { SvgIconTypeMap } from '@material-ui/core/SvgIcon/SvgIcon'; +import { IconComponent } from '@backstage/core-plugin-api'; -export function EntityKindIcon({ - kind, +export function EntityIcon({ + icon, ...props }: { - kind: string; + icon: IconComponent | undefined; x?: number; y?: number; width?: number; height?: number; className?: string; }) { - const app = useApp(); - const Icon = - app.getSystemIcon(`kind:${kind.toLocaleLowerCase('en-US')}`) ?? SvgIcon; + const Icon = (icon as OverridableComponent) ?? SvgIcon; return ; } 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..1b8d14ff3f 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,143 @@ # @backstage/plugin-catalog-import +## 0.12.2 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/plugin-catalog-common@1.0.26 + +## 0.12.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/integration-react@1.1.30-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.2 + +## 0.12.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @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-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + +## 0.12.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30-next.0 + +## 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/api-report-alpha.md b/plugins/catalog-import/api-report-alpha.md index 63e2cda339..9f82f39266 100644 --- a/plugins/catalog-import/api-report-alpha.md +++ b/plugins/catalog-import/api-report-alpha.md @@ -3,7 +3,12 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyApiFactory } from '@backstage/core-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) @@ -11,7 +16,47 @@ const _default: BackstagePlugin< { importPage: RouteRef; }, - {} + {}, + { + 'api:catalog-import': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + { + kind: 'api'; + namespace: undefined; + name: undefined; + } + >; + 'page:catalog-import': ExtensionDefinition< + { + path: string | undefined; + }, + { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + {}, + { + kind: 'page'; + namespace: undefined; + name: undefined; + } + >; + } >; export default _default; diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 1082f08113..7909c33936 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", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 795e697874..cf63c92338 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -25,9 +25,9 @@ import { convertLegacyRouteRef, } from '@backstage/core-compat-api'; import { - createApiExtension, - createPageExtension, - createPlugin, + createFrontendPlugin, + PageBlueprint, + ApiBlueprint, } from '@backstage/frontend-plugin-api'; import { scmAuthApiRef, @@ -40,47 +40,51 @@ import { catalogApiRef } from '@backstage/plugin-catalog-react'; // TODO: It's currently possible to override the import page with a custom one. We need to decide // whether this type of override is typically done with an input or by overriding the entire extension. -const catalogImportPage = createPageExtension({ - defaultPath: '/catalog-import', - routeRef: convertLegacyRouteRef(rootRouteRef), - loader: () => - import('./components/ImportPage').then(m => - compatWrapper(), - ), +const catalogImportPage = PageBlueprint.make({ + params: { + defaultPath: '/catalog-import', + routeRef: convertLegacyRouteRef(rootRouteRef), + loader: () => + import('./components/ImportPage').then(m => + compatWrapper(), + ), + }, }); -const catalogImportApi = createApiExtension({ - factory: createApiFactory({ - api: catalogImportApiRef, - deps: { - discoveryApi: discoveryApiRef, - scmAuthApi: scmAuthApiRef, - fetchApi: fetchApiRef, - scmIntegrationsApi: scmIntegrationsApiRef, - catalogApi: catalogApiRef, - configApi: configApiRef, - }, - factory: ({ - discoveryApi, - scmAuthApi, - fetchApi, - scmIntegrationsApi, - catalogApi, - configApi, - }) => - new CatalogImportClient({ +const catalogImportApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: catalogImportApiRef, + deps: { + discoveryApi: discoveryApiRef, + scmAuthApi: scmAuthApiRef, + fetchApi: fetchApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + catalogApi: catalogApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, scmAuthApi, - scmIntegrationsApi, fetchApi, + scmIntegrationsApi, catalogApi, configApi, - }), - }), + }) => + new CatalogImportClient({ + discoveryApi, + scmAuthApi, + scmIntegrationsApi, + fetchApi, + catalogApi, + configApi, + }), + }), + }, }); /** @alpha */ -export default createPlugin({ +export default createFrontendPlugin({ id: 'catalog-import', extensions: [catalogImportApi, catalogImportPage], routes: { 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..1c77be04e9 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,107 @@ # @backstage/plugin-catalog-node +## 1.12.5 + +### Patch Changes + +- a629fb2: Added setAllowedLocationTypes while introducing a new extension point called CatalogLocationsExtensionPoint +- 7c5f3b0: Explicit declare if the service ref accepts `single` or `multiple` implementations. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + +## 1.12.5-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## 1.12.5-next.2 + +### Patch Changes + +- 7c5f3b0: Explicit declare if the service ref accepts `single` or `multiple` implementations. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.12.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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..ba02079ceb 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; @@ -88,7 +96,7 @@ export interface CatalogProcessingExtensionPoint { export const catalogProcessingExtensionPoint: ExtensionPoint; // @alpha -export const catalogServiceRef: ServiceRef; +export const catalogServiceRef: ServiceRef; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 293b915eea..ff4c0d17a8 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", "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..8cb8985542 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,159 @@ # @backstage/plugin-catalog-react +## 1.12.3 + +### Patch Changes + +- 7bd27e1: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. +- 31bfc44: Updated alpha definitions of extension data references. +- 7ca331c: Correct `EntityDisplayName`'s icon alignment with the text. +- 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 +- 519b8e0: Added utilities for converting existing entity card and content extensions to the new frontend system. This is in particular useful when used in combination with the new `convertLegacyPlugin` utility from `@backstage/core-compat-api`. +- d001a42: Fix label related accessibility issues with `FavorityEntity` +- 012e3eb: Entity page extensions created for the new frontend system via the `/alpha` exports will now be enabled by default. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + +## 1.12.3-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/core-components@0.14.10-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/version-bridge@1.0.8 + - @backstage/plugin-catalog-common@1.0.26-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + +## 1.12.3-next.2 + +### Patch Changes + +- 012e3eb: Entity page extensions created for the new frontend system via the `/alpha` exports will now be enabled by default. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-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/version-bridge@1.0.8 + +## 1.12.3-next.1 + +### Patch Changes + +- 7ca331c: Correct `EntityDisplayName`'s icon alignment with the text. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-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/version-bridge@1.0.8 + +## 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 b82c7ae3e2..b22d76cd15 100644 --- a/plugins/catalog-react/api-report-alpha.md +++ b/plugins/catalog-react/api-report-alpha.md @@ -5,26 +5,17 @@ ```ts /// -import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; +import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; -import { PortableSchema } from '@backstage/frontend-plugin-api'; -import { ResolvedExtensionInputs } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; 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; - entityFilterFunction: ConfigurableExtensionDataRef< - (entity: Entity) => boolean, - {} - >; - entityFilterExpression: ConfigurableExtensionDataRef; -}; - // @alpha (undocumented) export const catalogReactTranslationRef: TranslationRef< 'catalog-react', @@ -84,56 +75,143 @@ export const catalogReactTranslationRef: TranslationRef< >; // @alpha (undocumented) -export function createEntityCardExtension< - TConfig extends { - filter?: string; +export function convertLegacyEntityCardExtension( + LegacyExtension: ComponentType<{}>, + overrides?: { + name?: string; + filter?: + | typeof EntityCardBlueprint.dataRefs.filterFunction.T + | typeof EntityCardBlueprint.dataRefs.filterExpression.T; }, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - disabled?: boolean; - inputs?: TInputs; - configSchema?: PortableSchema; - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; -}): ExtensionDefinition; +): ExtensionDefinition; // @alpha (undocumented) -export function createEntityContentExtension< - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { - id: string; - input: string; - }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - defaultPath: string; - defaultTitle: string; - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: (options: { - inputs: Expand>; - }) => Promise; -}): ExtensionDefinition<{ - title: string; - path: string; - filter?: string | undefined; -}>; +export function convertLegacyEntityContentExtension( + LegacyExtension: ComponentType<{}>, + overrides?: { + name?: string; + filter?: + | typeof EntityContentBlueprint.dataRefs.filterFunction.T + | typeof EntityContentBlueprint.dataRefs.filterExpression.T; + defaultPath?: string; + defaultTitle?: string; + }, +): ExtensionDefinition; + +// @alpha +export const EntityCardBlueprint: ExtensionBlueprint< + { + kind: 'entity-card'; + namespace: undefined; + name: undefined; + }, + { + loader: () => Promise; + filter?: string | ((entity: Entity) => boolean) | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + { + filterFunction: ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + {} + >; + filterExpression: ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + {} + >; + } +>; + +// @alpha +export const EntityContentBlueprint: ExtensionBlueprint< + { + kind: 'entity-content'; + namespace: undefined; + name: undefined; + }, + { + loader: () => Promise; + defaultPath: string; + defaultTitle: string; + routeRef?: RouteRef | undefined; + filter?: string | ((entity: Entity) => boolean) | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }, + { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }, + { + title: ConfigurableExtensionDataRef< + string, + 'catalog.entity-content-title', + {} + >; + filterFunction: ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + {} + >; + filterExpression: ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + {} + >; + } +>; // @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..7cf7464a88 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", "description": "A frontend library that helps other Backstage plugins interact with the catalog", "backstage": { "role": "web-library", @@ -29,7 +29,7 @@ "sideEffects": false, "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.tsx", + "./alpha": "./src/alpha/index.ts", "./package.json": "./package.json" }, "main": "src/index.ts", @@ -37,7 +37,7 @@ "typesVersions": { "*": { "alpha": [ - "src/alpha.tsx" + "src/alpha/index.ts" ], "package.json": [ "package.json" @@ -59,6 +59,7 @@ "dependencies": { "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", + "@backstage/core-compat-api": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", @@ -85,6 +86,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/test-utils": "workspace:^", diff --git a/plugins/catalog-react/src/alpha.tsx b/plugins/catalog-react/src/alpha.tsx deleted file mode 100644 index b1650d1f5b..0000000000 --- a/plugins/catalog-react/src/alpha.tsx +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - AnyExtensionInputMap, - ExtensionBoundary, - PortableSchema, - ResolvedExtensionInputs, - RouteRef, - coreExtensionData, - createExtension, - createExtensionDataRef, - createSchemaFromZod, -} from '@backstage/frontend-plugin-api'; -import React, { lazy } from 'react'; -import { Entity } from '@backstage/catalog-model'; -// eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { Expand } from '../../../packages/frontend-plugin-api/src/types'; - -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', - ), -}; - -// TODO: Figure out how to merge with provided config schema -/** @alpha */ -export function createEntityCardExtension< - TConfig extends { filter?: string }, - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - configSchema?: PortableSchema; - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: (options: { - config: TConfig; - inputs: Expand>; - }) => Promise; -}) { - const configSchema = - 'configSchema' in options - ? options.configSchema - : (createSchemaFromZod(z => - z.object({ - filter: z.string().optional(), - }), - ) as PortableSchema); - return createExtension({ - kind: 'entity-card', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { - id: 'entity-content:catalog/overview', - input: 'cards', - }, - disabled: options.disabled ?? true, - output: { - element: coreExtensionData.reactElement, - filterFunction: catalogExtensionData.entityFilterFunction.optional(), - filterExpression: catalogExtensionData.entityFilterExpression.optional(), - }, - inputs: options.inputs, - configSchema, - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ inputs, config }) - .then(element => ({ default: () => element })), - ); - - return { - element: ( - - - - ), - ...mergeFilters({ config, options }), - }; - }, - }); -} - -/** @alpha */ -export function createEntityContentExtension< - TInputs extends AnyExtensionInputMap, ->(options: { - namespace?: string; - name?: string; - attachTo?: { id: string; input: string }; - disabled?: boolean; - inputs?: TInputs; - routeRef?: RouteRef; - defaultPath: string; - defaultTitle: string; - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - loader: (options: { - inputs: Expand>; - }) => Promise; -}) { - return createExtension({ - kind: 'entity-content', - namespace: options.namespace, - name: options.name, - attachTo: options.attachTo ?? { - id: 'page:catalog/entity', - input: 'contents', - }, - disabled: options.disabled ?? true, - output: { - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath, - routeRef: coreExtensionData.routeRef.optional(), - title: catalogExtensionData.entityContentTitle, - filterFunction: catalogExtensionData.entityFilterFunction.optional(), - filterExpression: catalogExtensionData.entityFilterExpression.optional(), - }, - inputs: options.inputs, - configSchema: createSchemaFromZod(z => - z.object({ - path: z.string().default(options.defaultPath), - title: z.string().default(options.defaultTitle), - filter: z.string().optional(), - }), - ), - factory({ config, inputs, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ inputs }) - .then(element => ({ default: () => element })), - ); - - return { - path: config.path, - title: config.title, - routeRef: options.routeRef, - element: ( - - - - ), - ...mergeFilters({ config, options }), - }; - }, - }); -} - -/** - * Decides what filter outputs to produce, given some options and config - */ -function mergeFilters(inputs: { - options: { - filter?: - | typeof catalogExtensionData.entityFilterFunction.T - | typeof catalogExtensionData.entityFilterExpression.T; - }; - config: { - filter?: string; - }; -}): { - filterFunction?: typeof catalogExtensionData.entityFilterFunction.T; - filterExpression?: typeof catalogExtensionData.entityFilterExpression.T; -} { - const { options, config } = inputs; - if (config.filter) { - return { filterExpression: config.filter }; - } else if (typeof options.filter === 'string') { - return { filterExpression: options.filter }; - } else if (typeof options.filter === 'function') { - return { filterFunction: options.filter }; - } - return {}; -} diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx new file mode 100644 index 0000000000..0cb7cdead8 --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx @@ -0,0 +1,178 @@ +/* + * 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 { EntityCardBlueprint } from './EntityCardBlueprint'; +import { + coreExtensionData, + createExtension, + createExtensionInput, +} from '@backstage/frontend-plugin-api'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { waitFor, screen } from '@testing-library/react'; +import { Entity } from '@backstage/catalog-model'; + +describe('EntityCardBlueprint', () => { + it('should return an extension with sensible defaults', () => { + const extension = EntityCardBlueprint.make({ + name: 'test', + params: { + filter: 'has:labels', + loader: async () =>
im a card
, + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "entity-content:catalog/overview", + "input": "cards", + }, + "configSchema": { + "parse": [Function], + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "filter": { + "type": "string", + }, + }, + "type": "object", + }, + }, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "entity-card", + "name": "test", + "namespace": undefined, + "output": [ + [Function], + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "catalog.entity-filter-function", + "optional": [Function], + "toString": [Function], + }, + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "catalog.entity-filter-expression", + "optional": [Function], + "toString": [Function], + }, + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should output the correct filter output', () => { + const mockFilter = (_entity: Entity) => true; + + expect( + createExtensionTester( + EntityCardBlueprint.make({ + name: 'test', + params: { + loader: async () =>
Test!
, + filter: 'test', + }, + }), + ).get(EntityCardBlueprint.dataRefs.filterExpression), + ).toBe('test'); + + expect( + createExtensionTester( + EntityCardBlueprint.make({ + name: 'test', + params: { + loader: async () =>
Test!
, + }, + }), + { config: { filter: 'test' } }, + ).get(EntityCardBlueprint.dataRefs.filterExpression), + ).toBe('test'); + + expect( + createExtensionTester( + EntityCardBlueprint.make({ + name: 'test', + params: { + filter: mockFilter, + loader: async () =>
Test!
, + }, + }), + ).get(EntityCardBlueprint.dataRefs.filterFunction), + ).toBe(mockFilter); + }); + + it('should allow overriding config and inputs', async () => { + const extension = EntityCardBlueprint.makeWithOverrides({ + name: 'test', + inputs: { + mock: createExtensionInput([coreExtensionData.reactElement]), + }, + config: { + schema: { + mock: z => z.string(), + }, + }, + factory(originalFactory, { inputs, config }) { + return originalFactory({ + loader: async () => ( +
+ config: {config.mock} +
+ {inputs.mock.map((i, k) => ( +
{i.get(coreExtensionData.reactElement)}
+ ))} +
+
+ ), + }); + }, + }); + + const mockExtension = createExtension({ + attachTo: { id: 'entity-card:test', input: 'mock' }, + output: [coreExtensionData.reactElement], + factory() { + return [coreExtensionData.reactElement(
im a mock
)]; + }, + }); + + createExtensionTester(extension, { config: { mock: 'mock test config' } }) + .add(mockExtension) + .render(); + + await waitFor(() => { + expect(screen.getByTestId('test')).toBeInTheDocument(); + expect(screen.getByTestId('test')).toHaveTextContent( + 'config: mock test config', + ); + expect(screen.getByTestId('contents')).toHaveTextContent('im a mock'); + }); + }); +}); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts new file mode 100644 index 0000000000..82ff2df44a --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts @@ -0,0 +1,70 @@ +/* + * 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 { + ExtensionBoundary, + coreExtensionData, + createExtensionBlueprint, +} from '@backstage/frontend-plugin-api'; +import { + entityFilterFunctionDataRef, + entityFilterExpressionDataRef, +} from './extensionData'; + +/** + * @alpha + * A blueprint for creating cards for the entity pages in the catalog. + */ +export const EntityCardBlueprint = createExtensionBlueprint({ + kind: 'entity-card', + attachTo: { id: 'entity-content:catalog/overview', input: 'cards' }, + output: [ + coreExtensionData.reactElement, + entityFilterFunctionDataRef.optional(), + entityFilterExpressionDataRef.optional(), + ], + dataRefs: { + filterFunction: entityFilterFunctionDataRef, + filterExpression: entityFilterExpressionDataRef, + }, + config: { + schema: { + filter: z => z.string().optional(), + }, + }, + *factory( + { + loader, + filter, + }: { + loader: () => Promise; + filter?: + | typeof entityFilterFunctionDataRef.T + | typeof entityFilterExpressionDataRef.T; + }, + { node, config }, + ) { + yield coreExtensionData.reactElement(ExtensionBoundary.lazy(node, loader)); + + if (config.filter) { + yield entityFilterExpressionDataRef(config.filter); + } else if (typeof filter === 'string') { + yield entityFilterExpressionDataRef(filter); + } else if (typeof filter === 'function') { + yield entityFilterFunctionDataRef(filter); + } + }, +}); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx new file mode 100644 index 0000000000..5d91f402de --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.test.tsx @@ -0,0 +1,226 @@ +/* + * 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 { EntityContentBlueprint } from './EntityContentBlueprint'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { + coreExtensionData, + createExtension, + createExtensionInput, + createRouteRef, +} from '@backstage/frontend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { waitFor, screen } from '@testing-library/react'; + +describe('EntityContentBlueprint', () => { + it('should return an extension with sane defaults', () => { + const extension = EntityContentBlueprint.make({ + name: 'test', + params: { + defaultPath: '/test', + defaultTitle: 'Test', + loader: async () =>
Test!
, + }, + }); + + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "page:catalog/entity", + "input": "contents", + }, + "configSchema": { + "parse": [Function], + "schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "additionalProperties": false, + "properties": { + "filter": { + "type": "string", + }, + "path": { + "type": "string", + }, + "title": { + "type": "string", + }, + }, + "type": "object", + }, + }, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "entity-content", + "name": "test", + "namespace": undefined, + "output": [ + [Function], + [Function], + [Function], + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "core.routing.ref", + "optional": [Function], + "toString": [Function], + }, + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "catalog.entity-filter-function", + "optional": [Function], + "toString": [Function], + }, + { + "$$type": "@backstage/ExtensionDataRef", + "config": { + "optional": true, + }, + "id": "catalog.entity-filter-expression", + "optional": [Function], + "toString": [Function], + }, + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should emit the correct defaults', () => { + const mockRouteRef = createRouteRef(); + const extension = EntityContentBlueprint.make({ + name: 'test', + params: { + defaultPath: '/test', + defaultTitle: 'Test', + routeRef: mockRouteRef, + loader: async () =>
Test!
, + }, + }); + + const tester = createExtensionTester(extension); + + // todo(blam): route paths are always set to / in the createExtensionTester. This will work eventually. + // expect(tester.get(coreExtensionData.routePath)).toBe('/test'); + + expect(tester.get(coreExtensionData.routeRef)).toBe(mockRouteRef); + expect(tester.get(EntityContentBlueprint.dataRefs.title)).toBe('Test'); + }); + + it('should emit the correct filter output', () => { + const mockFilter = (_entity: Entity) => true; + + expect( + createExtensionTester( + EntityContentBlueprint.make({ + name: 'test', + params: { + defaultPath: '/test', + defaultTitle: 'Test', + loader: async () =>
Test!
, + filter: 'test', + }, + }), + ).get(EntityContentBlueprint.dataRefs.filterExpression), + ).toBe('test'); + + expect( + createExtensionTester( + EntityContentBlueprint.make({ + name: 'test', + params: { + defaultPath: '/test', + defaultTitle: 'Test', + loader: async () =>
Test!
, + }, + }), + { config: { filter: 'test' } }, + ).get(EntityContentBlueprint.dataRefs.filterExpression), + ).toBe('test'); + + expect( + createExtensionTester( + EntityContentBlueprint.make({ + name: 'test', + params: { + defaultPath: '/test', + defaultTitle: 'Test', + filter: mockFilter, + loader: async () =>
Test!
, + }, + }), + ).get(EntityContentBlueprint.dataRefs.filterFunction), + ).toBe(mockFilter); + }); + + it('should allow overriding config and inputs', async () => { + const extension = EntityContentBlueprint.makeWithOverrides({ + name: 'test', + inputs: { + mock: createExtensionInput([coreExtensionData.reactElement]), + }, + config: { + schema: { + mock: z => z.string(), + }, + }, + factory(originalFactory, { inputs, config }) { + return originalFactory({ + defaultPath: '/test', + defaultTitle: 'Test', + loader: async () => ( +
+ config: {config.mock} +
+ {inputs.mock.map((i, k) => ( +
{i.get(coreExtensionData.reactElement)}
+ ))} +
+
+ ), + }); + }, + }); + + const mockExtension = createExtension({ + attachTo: { id: 'entity-content:test', input: 'mock' }, + output: [coreExtensionData.reactElement], + factory() { + return [coreExtensionData.reactElement(
im a mock
)]; + }, + }); + + createExtensionTester(extension, { config: { mock: 'mock test config' } }) + .add(mockExtension) + .render(); + + await waitFor(() => { + expect(screen.getByTestId('test')).toBeInTheDocument(); + expect(screen.getByTestId('test')).toHaveTextContent( + 'config: mock test config', + ); + expect(screen.getByTestId('contents')).toHaveTextContent('im a mock'); + }); + }); +}); diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts new file mode 100644 index 0000000000..54cd476a90 --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/EntityContentBlueprint.ts @@ -0,0 +1,95 @@ +/* + * 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 { + coreExtensionData, + createExtensionBlueprint, + ExtensionBoundary, + RouteRef, +} from '@backstage/frontend-plugin-api'; +import { + entityContentTitleDataRef, + entityFilterFunctionDataRef, + entityFilterExpressionDataRef, +} from './extensionData'; + +/** + * @alpha + * Creates an EntityContent extension. + */ +export const EntityContentBlueprint = createExtensionBlueprint({ + kind: 'entity-content', + attachTo: { id: 'page:catalog/entity', input: 'contents' }, + output: [ + coreExtensionData.reactElement, + coreExtensionData.routePath, + entityContentTitleDataRef, + coreExtensionData.routeRef.optional(), + entityFilterFunctionDataRef.optional(), + entityFilterExpressionDataRef.optional(), + ], + dataRefs: { + title: entityContentTitleDataRef, + filterFunction: entityFilterFunctionDataRef, + filterExpression: entityFilterExpressionDataRef, + }, + config: { + schema: { + path: z => z.string().optional(), + title: z => z.string().optional(), + filter: z => z.string().optional(), + }, + }, + *factory( + { + loader, + defaultPath, + defaultTitle, + filter, + routeRef, + }: { + loader: () => Promise; + defaultPath: string; + defaultTitle: string; + routeRef?: RouteRef; + filter?: + | typeof entityFilterFunctionDataRef.T + | typeof entityFilterExpressionDataRef.T; + }, + { node, config }, + ) { + const path = config.path ?? defaultPath; + const title = config.title ?? defaultTitle; + + yield coreExtensionData.reactElement(ExtensionBoundary.lazy(node, loader)); + + yield coreExtensionData.routePath(path); + + yield entityContentTitleDataRef(title); + + if (routeRef) { + yield coreExtensionData.routeRef(routeRef); + } + + if (config.filter) { + yield entityFilterExpressionDataRef(config.filter); + } else if (typeof filter === 'string') { + yield entityFilterExpressionDataRef(filter); + } else if (typeof filter === 'function') { + yield entityFilterFunctionDataRef(filter); + } + }, +}); diff --git a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts b/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx similarity index 51% rename from packages/frontend-plugin-api/src/extensions/createThemeExtension.ts rename to plugins/catalog-react/src/alpha/blueprints/extensionData.tsx index cc6dbdabe9..09aac809cb 100644 --- a/packages/frontend-plugin-api/src/extensions/createThemeExtension.ts +++ b/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx @@ -14,25 +14,21 @@ * limitations under the License. */ -import { createExtension, createExtensionDataRef } from '../wiring'; -import { AppTheme } from '@backstage/core-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { createExtensionDataRef } from '@backstage/frontend-plugin-api'; -/** @public */ -export function createThemeExtension(theme: AppTheme) { - return createExtension({ - kind: 'theme', - namespace: 'app', - name: theme.id, - attachTo: { id: 'app', input: 'themes' }, - output: { - theme: createThemeExtension.themeDataRef, - }, - factory: () => ({ theme }), +/** @internal */ +export const entityContentTitleDataRef = createExtensionDataRef().with({ + id: 'catalog.entity-content-title', +}); + +/** @internal */ +export const entityFilterFunctionDataRef = createExtensionDataRef< + (entity: Entity) => boolean +>().with({ id: 'catalog.entity-filter-function' }); + +/** @internal */ +export const entityFilterExpressionDataRef = + createExtensionDataRef().with({ + id: 'catalog.entity-filter-expression', }); -} - -/** @public */ -export namespace createThemeExtension { - export const themeDataRef = - createExtensionDataRef('core.theme.theme'); -} diff --git a/plugins/catalog-react/src/alpha/blueprints/index.ts b/plugins/catalog-react/src/alpha/blueprints/index.ts new file mode 100644 index 0000000000..dccaf28cd9 --- /dev/null +++ b/plugins/catalog-react/src/alpha/blueprints/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { EntityCardBlueprint } from './EntityCardBlueprint'; +export { EntityContentBlueprint } from './EntityContentBlueprint'; diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx new file mode 100644 index 0000000000..7dacd9c3e8 --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx @@ -0,0 +1,129 @@ +/* + * 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 { + createPlugin as createLegacyPlugin, + createRouteRef as createLegacyRouteRef, + createRoutableExtension, +} from '@backstage/core-plugin-api'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { convertLegacyEntityCardExtension } from './convertLegacyEntityCardExtension'; +import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { EntityContentBlueprint } from '../blueprints'; + +const routeRef = createLegacyRouteRef({ id: 'test' }); +const legacyPlugin = createLegacyPlugin({ + id: 'test', + routes: { + test: routeRef, + }, +}); + +describe('convertLegacyEntityCardExtension', () => { + it('should convert an entity card extension', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleCard', + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ); + + const converted = convertLegacyEntityCardExtension(LegacyExtension); + expect(converted.kind).toBe('entity-card'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('example'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + undefined, + ); + expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + undefined, + ); + }); + + it('should convert an entity card extension with overrides', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleCard', + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ); + + const converted = convertLegacyEntityCardExtension(LegacyExtension, { + name: 'other', + filter: 'my-filter', + }); + expect(converted.kind).toBe('entity-card'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('other'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + 'my-filter', + ); + expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + undefined, + ); + }); + + it('should support various naming patterns for entity card extensions', async () => { + const withName = (name: string) => { + const converted = convertLegacyEntityCardExtension( + legacyPlugin.provide( + createRoutableExtension({ + name, + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ), + ); + return converted.name; + }; + + expect(withName('EntityTestCard')).toBe(undefined); + expect(withName('EntityTestTrimCard')).toBe('trim'); + expect(withName('EntityTeStTrimCard')).toBe('trim'); + expect(withName('EntityExampleCard')).toBe('example'); + expect(withName('EntityExAmpleCard')).toBe('ex-ample'); + expect(withName('ExampleCard')).toBe('example-card'); + }); +}); diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx new file mode 100644 index 0000000000..76442f526d --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx @@ -0,0 +1,68 @@ +/* + * 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 { compatWrapper } from '@backstage/core-compat-api'; +import { BackstagePlugin, getComponentData } from '@backstage/core-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import React, { ComponentType } from 'react'; +import { EntityCardBlueprint } from '../blueprints'; +import kebabCase from 'lodash/kebabCase'; + +/** @alpha */ +export function convertLegacyEntityCardExtension( + LegacyExtension: ComponentType<{}>, + overrides?: { + name?: string; + filter?: + | typeof EntityCardBlueprint.dataRefs.filterFunction.T + | typeof EntityCardBlueprint.dataRefs.filterExpression.T; + }, +): ExtensionDefinition { + const element = ; + + const extName = getComponentData(element, 'core.extensionName'); + if (!extName) { + throw new Error('Extension has no name'); + } + + const plugin = getComponentData(element, 'core.plugin'); + const pluginId = plugin?.getId(); + + const match = extName.match(/^Entity(.*)Card$/); + const infix = match?.[1] ?? extName; + + let name: string | undefined = infix; + if ( + pluginId && + name + .toLocaleLowerCase('en-US') + .startsWith(pluginId.toLocaleLowerCase('en-US')) + ) { + name = name.slice(pluginId.length); + if (!name) { + name = undefined; + } + } + name = name && kebabCase(name); + + return EntityCardBlueprint.make({ + name: overrides?.name ?? name, + params: { + filter: overrides?.filter, + loader: async () => compatWrapper(element), + }, + }); +} diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx new file mode 100644 index 0000000000..6d7c8f0420 --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx @@ -0,0 +1,136 @@ +/* + * 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 { + createPlugin as createLegacyPlugin, + createRouteRef as createLegacyRouteRef, + createRoutableExtension, +} from '@backstage/core-plugin-api'; +import { coreExtensionData } from '@backstage/frontend-plugin-api'; +import { + createExtensionTester, + renderInTestApp, +} from '@backstage/frontend-test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { convertLegacyEntityContentExtension } from './convertLegacyEntityContentExtension'; +import { convertLegacyRouteRef } from '@backstage/core-compat-api'; +import { EntityContentBlueprint } from '../blueprints'; + +const routeRef = createLegacyRouteRef({ id: 'test' }); +const legacyPlugin = createLegacyPlugin({ + id: 'test', + routes: { + test: routeRef, + }, +}); + +describe('convertLegacyEntityContentExtension', () => { + it('should convert an entity content extension', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleContent', + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ); + + const converted = convertLegacyEntityContentExtension(LegacyExtension); + expect(converted.kind).toBe('entity-content'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('example'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(coreExtensionData.routePath)).toBe('/example'); + expect(tester.get(coreExtensionData.routeRef)).toBe(routeRef); + expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + undefined, + ); + expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + undefined, + ); + }); + + it('should convert an entity content extension with overrides', async () => { + const LegacyExtension = legacyPlugin.provide( + createRoutableExtension({ + name: 'EntityExampleContent', + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ); + + const converted = convertLegacyEntityContentExtension(LegacyExtension, { + name: 'other', + defaultPath: '/other', + defaultTitle: 'Other', + filter: 'my-filter', + }); + expect(converted.kind).toBe('entity-content'); + expect(converted.namespace).toBe(undefined); + expect(converted.name).toBe('other'); + + const tester = createExtensionTester(converted); + + await renderInTestApp(tester.reactElement(), { + mountedRoutes: { + '/': convertLegacyRouteRef(routeRef), + }, + }); + + await expect(screen.findByText('Hello')).resolves.toBeInTheDocument(); + + expect(tester.get(coreExtensionData.routePath)).toBe('/other'); + expect(tester.get(coreExtensionData.routeRef)).toBe(routeRef); + expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe( + 'my-filter', + ); + expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe( + undefined, + ); + }); + + it('should support various naming patterns for entity content extensions', async () => { + const withName = (name: string) => { + const converted = convertLegacyEntityContentExtension( + legacyPlugin.provide( + createRoutableExtension({ + name, + mountPoint: routeRef, + component: async () => () =>
Hello
, + }), + ), + ); + return converted.name; + }; + + expect(withName('EntityTestContent')).toBe(undefined); + expect(withName('EntityTestTrimContent')).toBe('trim'); + expect(withName('EntityTeStTrimContent')).toBe('trim'); + expect(withName('EntityExampleContent')).toBe('example'); + expect(withName('EntityExAmpleContent')).toBe('ex-ample'); + expect(withName('ExampleContent')).toBe('example-content'); + }); +}); diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx new file mode 100644 index 0000000000..e44b5671fc --- /dev/null +++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx @@ -0,0 +1,86 @@ +/* + * 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 { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; +import { + BackstagePlugin, + getComponentData, + RouteRef as LegacyRouteRef, +} from '@backstage/core-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import kebabCase from 'lodash/kebabCase'; +import startCase from 'lodash/startCase'; +import React, { ComponentType } from 'react'; +import { EntityContentBlueprint } from '../blueprints'; + +/** @alpha */ +export function convertLegacyEntityContentExtension( + LegacyExtension: ComponentType<{}>, + overrides?: { + name?: string; + filter?: + | typeof EntityContentBlueprint.dataRefs.filterFunction.T + | typeof EntityContentBlueprint.dataRefs.filterExpression.T; + defaultPath?: string; + defaultTitle?: string; + }, +): ExtensionDefinition { + const element = ; + + const extName = getComponentData(element, 'core.extensionName'); + if (!extName) { + throw new Error('Extension has no name'); + } + + const mountPoint = getComponentData( + element, + 'core.mountPoint', + ); + + const plugin = getComponentData(element, 'core.plugin'); + const pluginId = plugin?.getId(); + + const match = extName.match(/^Entity(.*)Content$/); + const infix = match?.[1] ?? extName; + + let name: string | undefined = infix; + if ( + pluginId && + name + .toLocaleLowerCase('en-US') + .startsWith(pluginId.toLocaleLowerCase('en-US')) + ) { + name = name.slice(pluginId.length); + if (!name) { + name = undefined; + } + } + name = name && kebabCase(name); + + return EntityContentBlueprint.make({ + name: overrides?.name ?? name, + params: { + filter: overrides?.filter, + defaultPath: overrides?.defaultPath ?? `/${kebabCase(infix)}`, + defaultTitle: overrides?.defaultTitle ?? startCase(infix), + routeRef: mountPoint && convertLegacyRouteRef(mountPoint), + loader: async () => compatWrapper(element), + }, + }); +} diff --git a/packages/backend-app-api/src/services/implementations/deprecated.ts b/plugins/catalog-react/src/alpha/converters/index.ts similarity index 72% rename from packages/backend-app-api/src/services/implementations/deprecated.ts rename to plugins/catalog-react/src/alpha/converters/index.ts index 347a968bce..731ab846c0 100644 --- a/packages/backend-app-api/src/services/implementations/deprecated.ts +++ b/plugins/catalog-react/src/alpha/converters/index.ts @@ -14,11 +14,5 @@ * limitations under the License. */ -export * from './auth'; -export * from './httpAuth'; -export * from './httpRouter'; -export * from './logger'; -export * from './rootHttpRouter'; -export * from './rootLogger'; -export * from './scheduler'; -export * from './userInfo'; +export { convertLegacyEntityCardExtension } from './convertLegacyEntityCardExtension'; +export { convertLegacyEntityContentExtension } from './convertLegacyEntityContentExtension'; diff --git a/plugins/catalog-react/src/alpha/index.ts b/plugins/catalog-react/src/alpha/index.ts new file mode 100644 index 0000000000..a46fec0615 --- /dev/null +++ b/plugins/catalog-react/src/alpha/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './blueprints'; +export * from './converters'; +export { catalogReactTranslationRef } from '../translation'; +export { isOwnerOf } from '../utils/isOwnerOf'; +export { useEntityPermission } from '../hooks/useEntityPermission'; 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/EntityOwnerPicker/useFacetsEntities.test.ts b/plugins/catalog-react/src/components/EntityOwnerPicker/useFacetsEntities.test.ts index 742efaab9f..1d9eaa8af7 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/useFacetsEntities.test.ts +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/useFacetsEntities.test.ts @@ -70,6 +70,21 @@ describe('useFacetsEntities', () => { expect(result.current[0]).toEqual({ value: { items: [] }, loading: true }); }); + it(`should return empty response when facet is not present`, async () => { + mockedGetEntityFacets.mockResolvedValueOnce({ + facets: { 'metadata.tags': [{ value: 'tag', count: 1 }] }, + }); + mockedGetEntitiesByRefs.mockResolvedValueOnce({ items: [] }); + const { result } = renderHook(() => useFacetsEntities({ enabled: true })); + result.current[1]({ text: '' }); + await waitFor(() => { + expect(result.current[0]).toEqual({ + value: { items: [] }, + loading: false, + }); + }); + }); + it(`should return the owners`, async () => { const entityRefs = ['component:default/e1', 'component:default/e2']; mockedGetEntityFacets.mockResolvedValue(facetsFromEntityRefs(entityRefs)); diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/useFacetsEntities.ts b/plugins/catalog-react/src/components/EntityOwnerPicker/useFacetsEntities.ts index 10a5c5a8c2..9bb27eab98 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/useFacetsEntities.ts +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/useFacetsEntities.ts @@ -52,7 +52,7 @@ export function useFacetsEntities({ enabled }: { enabled: boolean }) { const facetsResponse = await catalogApi.getEntityFacets({ facets: [facet], }); - const entityRefs = facetsResponse.facets[facet].map(e => e.value); + const entityRefs = facetsResponse.facets[facet]?.map(e => e.value) ?? []; return catalogApi .getEntitiesByRefs({ entityRefs }) diff --git a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.test.tsx b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.test.tsx new file mode 100644 index 0000000000..d36700452a --- /dev/null +++ b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.test.tsx @@ -0,0 +1,106 @@ +/* + * 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 { storageApiRef } from '@backstage/core-plugin-api'; + +import { MockStarredEntitiesApi, starredEntitiesApiRef } from '../../apis'; +import { FavoriteEntity } from './FavoriteEntity'; +import { ComponentEntity } from '@backstage/catalog-model'; +import { + MockStorageApi, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +const entity: ComponentEntity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'example', + }, + spec: { + type: 'service', + lifecycle: 'experimental', + owner: 'user:default/john.doe_example.com', + }, +}; + +const mockStorage = MockStorageApi.create(); + +describe('', () => { + it('should add to favorites', async () => { + await renderInTestApp( + + + , + ); + + const addToFavorite = screen.getByRole('button', { + name: 'Add to favorites', + }); + + // Should keep the label when hovering + await userEvent.hover(addToFavorite); + expect(addToFavorite).toBeInTheDocument(); + + await userEvent.click(addToFavorite); + expect( + screen.getByRole('button', { + name: 'Remove from favorites', + }), + ).toBeInTheDocument(); + }); + + it('should remove from favorites', async () => { + const starredEntities = new MockStarredEntitiesApi(); + await starredEntities.toggleStarred('component:default/example'); + + await renderInTestApp( + + + , + ); + + const removeFromFavorites = screen.getByRole('button', { + name: 'Remove from favorites', + }); + + // Should keep the label when hovering + await userEvent.hover(removeFromFavorites); + expect(removeFromFavorites).toBeInTheDocument(); + + await userEvent.click(removeFromFavorites); + + expect( + screen.getByRole('button', { + name: 'Add to favorites', + }), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx index 8848857976..ab674ff98e 100644 --- a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx +++ b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import IconButton from '@material-ui/core/IconButton'; import Tooltip from '@material-ui/core/Tooltip'; import { withStyles } from '@material-ui/core/styles'; @@ -46,20 +46,24 @@ export const FavoriteEntity = (props: FavoriteEntityProps) => { props.entity, ); const { t } = useTranslationRef(catalogReactTranslationRef); + const title = isStarredEntity + ? t('favoriteEntity.removeFromFavorites') + : t('favoriteEntity.addToFavorites'); + + const id = `favorite-${stringifyEntityRef(props.entity).replace( + /[^a-zA-Z0-9-_]/g, + '-', + )}`; + return ( toggleStarredEntity()} > - + {isStarredEntity ? : } diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index 7f0ff8be74..c6b515aa67 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -247,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/setupTests.ts b/plugins/catalog-react/src/setupTests.ts index 963c0f188b..dae4360fd3 100644 --- a/plugins/catalog-react/src/setupTests.ts +++ b/plugins/catalog-react/src/setupTests.ts @@ -15,3 +15,18 @@ */ import '@testing-library/jest-dom'; + +// eslint-disable-next-line no-console +const originalConsoleWarn = console.warn; +// eslint-disable-next-line no-console +console.warn = (...args: any[]) => { + const message = args[0]; + if ( + typeof message === 'string' && + (message.includes('CSSOM.parse is not a function') || + message.includes('[JSS]')) + ) { + return; + } + originalConsoleWarn(...args); +}; diff --git a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md index f2f489b6a0..6e254a9591 100644 --- a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-catalog-unprocessed-entities-common +## 0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + +## 0.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + +## 0.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + +## 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..75a76166bd 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.4", "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..5b43a660d5 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## 0.2.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## 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..1c028e05a6 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", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 9c6031799c..2185791a40 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,184 @@ # @backstage/plugin-catalog +## 1.22.0 + +### Minor Changes + +- 6925dcb: Introduces the HasSubdomainsCard component that displays the subdomains of a given domain + +### Patch Changes + +- 496b8a9: Export `RelatedEntitiesCard` presets to be reused. +- 604a504: The entity relation cards available for the new frontend system via `/alpha` now have more accurate and granular default filters. +- 7bd27e1: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead. +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- 6582799: Add `tableOptions` to all tables and additionally `title` to API tables. +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-search-react@1.7.14 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/plugin-search-common@1.2.14 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/integration-react@1.1.30 + - @backstage/types@1.1.1 + - @backstage/plugin-catalog-common@1.0.26 + - @backstage/plugin-permission-react@0.4.25 + - @backstage/plugin-scaffolder-common@1.5.5 + +## 1.22.0-next.3 + +### Patch Changes + +- 6582799: Add `tableOptions` to all tables and additionally `title` to API tables. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/plugin-search-react@1.7.14-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/core-components@0.14.10-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.26-next.2 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + +## 1.22.0-next.2 + +### Minor Changes + +- 6925dcb: Introduces the HasSubdomainsCard component that displays the subdomains of a given domain + +### Patch Changes + +- 604a504: The entity relation cards available for the new frontend system via `/alpha` now have more accurate and granular default filters. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-search-common@1.2.14-next.1 + - @backstage/plugin-search-react@1.7.14-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-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 + +## 1.21.2-next.1 + +### Patch Changes + +- 6349099: Added config input type to the extensions +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-search-react@1.7.14-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-scaffolder-common@1.5.5-next.0 + - @backstage/plugin-search-common@1.2.14-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-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 + +## 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 102e2a7aa3..42f4355921 100644 --- a/plugins/catalog/api-report-alpha.md +++ b/plugins/catalog/api-report-alpha.md @@ -5,14 +5,40 @@ ```ts /// -import { AnyExtensionInputMap } from '@backstage/frontend-plugin-api'; +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; -import { PortableSchema } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { JSX as JSX_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; +import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; +import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +// @alpha +export const CatalogFilterBlueprint: ExtensionBlueprint< + { + kind: 'catalog-filter'; + namespace: undefined; + name: undefined; + }, + { + loader: () => Promise; + }, + ConfigurableExtensionDataRef, + {}, + {}, + {}, + never +>; + // @alpha (undocumented) export const catalogTranslationRef: TranslationRef< 'catalog', @@ -85,6 +111,8 @@ export const catalogTranslationRef: TranslationRef< 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 'hasSubdomainsCard.title': 'Has subdomains'; + readonly 'hasSubdomainsCard.emptyMessage': 'No subdomain is part of this domain'; readonly 'hasSystemsCard.title': 'Has systems'; readonly 'hasSystemsCard.emptyMessage': 'No system is part of this domain'; readonly 'relatedEntitiesCard.emptyHelpLinkTitle': 'Learn how to change this.'; @@ -96,18 +124,6 @@ export const catalogTranslationRef: TranslationRef< } >; -// @alpha (undocumented) -export function createCatalogFilterExtension< - TInputs extends AnyExtensionInputMap, - TConfig = never, ->(options: { - namespace?: string; - name?: string; - inputs?: TInputs; - configSchema?: PortableSchema; - loader: (options: { config: TConfig }) => Promise; -}): ExtensionDefinition; - // @alpha (undocumented) const _default: BackstagePlugin< { @@ -119,23 +135,661 @@ const _default: BackstagePlugin< }>; }, { - viewTechDoc: ExternalRouteRef< + viewTechDoc: ExternalRouteRef<{ + name: string; + kind: string; + namespace: string; + }>; + createComponent: ExternalRouteRef; + createFromTemplate: ExternalRouteRef<{ + namespace: string; + templateName: string; + }>; + unregisterRedirect: ExternalRouteRef; + }, + { + 'nav-item:catalog': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, { - name: string; - kind: string; - namespace: string; - }, - true + kind: 'nav-item'; + namespace: undefined; + name: undefined; + } >; - createComponent: ExternalRouteRef; - createFromTemplate: ExternalRouteRef< + 'api:catalog/starred-entities': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, { - namespace: string; - templateName: string; - }, - true + kind: 'api'; + namespace: undefined; + name: 'starred-entities'; + } + >; + 'api:catalog/entity-presentation': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + { + kind: 'api'; + namespace: undefined; + name: 'entity-presentation'; + } + >; + 'entity-card:catalog/about': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'about'; + } + >; + 'entity-card:catalog/links': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'links'; + } + >; + 'entity-card:catalog/labels': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'labels'; + } + >; + 'entity-card:catalog/depends-on-components': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'depends-on-components'; + } + >; + 'entity-card:catalog/depends-on-resources': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'depends-on-resources'; + } + >; + 'entity-card:catalog/has-components': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'has-components'; + } + >; + 'entity-card:catalog/has-resources': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'has-resources'; + } + >; + 'entity-card:catalog/has-subcomponents': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'has-subcomponents'; + } + >; + 'entity-card:catalog/has-subdomains': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'has-subdomains'; + } + >; + 'entity-card:catalog/has-systems': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'has-systems'; + } + >; + 'entity-content:catalog/overview': ExtensionDefinition< + { + [x: string]: any; + } & { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }, + { + [x: string]: any; + } & { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + { + cards: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + } + >; + }, + { + kind: 'entity-content'; + namespace: undefined; + name: 'overview'; + } + >; + 'catalog-filter:catalog/tag': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'tag'; + } + >; + 'catalog-filter:catalog/kind': ExtensionDefinition< + { + initialFilter: string; + }, + { + initialFilter?: string | undefined; + }, + ConfigurableExtensionDataRef, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'kind'; + } + >; + 'catalog-filter:catalog/type': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'type'; + } + >; + 'catalog-filter:catalog/mode': ExtensionDefinition< + { + mode: 'all' | 'owners-only' | undefined; + }, + { + mode?: 'all' | 'owners-only' | undefined; + }, + ConfigurableExtensionDataRef, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'mode'; + } + >; + 'catalog-filter:catalog/namespace': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'namespace'; + } + >; + 'catalog-filter:catalog/lifecycle': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'lifecycle'; + } + >; + 'catalog-filter:catalog/processing-status': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'processing-status'; + } + >; + 'catalog-filter:catalog/list': ExtensionDefinition< + { + initialFilter: 'all' | 'owned' | 'starred'; + }, + { + initialFilter?: 'all' | 'owned' | 'starred' | undefined; + }, + ConfigurableExtensionDataRef, + { + [x: string]: ExtensionInput< + AnyExtensionDataRef, + { + optional: boolean; + singleton: boolean; + } + >; + }, + { + kind: 'catalog-filter'; + namespace: undefined; + name: 'list'; + } + >; + 'page:catalog': ExtensionDefinition< + { + [x: string]: any; + } & { + path: string | undefined; + }, + { + [x: string]: any; + } & { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + filters: ExtensionInput< + ConfigurableExtensionDataRef, + { + singleton: false; + optional: false; + } + >; + }, + { + kind: 'page'; + namespace: undefined; + name: undefined; + } + >; + 'page:catalog/entity': ExtensionDefinition< + { + [x: string]: any; + } & { + path: string | undefined; + }, + { + [x: string]: any; + } & { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + contents: ExtensionInput< + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-content-title', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + { + singleton: false; + optional: false; + } + >; + }, + { + kind: 'page'; + namespace: undefined; + name: 'entity'; + } + >; + 'search-result-list-item:catalog': ExtensionDefinition< + { + noTrack: boolean; + }, + { + noTrack?: boolean | undefined; + }, + ConfigurableExtensionDataRef< + { + predicate?: SearchResultItemExtensionPredicate | undefined; + component: SearchResultItemExtensionComponent; + }, + 'search.search-result-list-item.item', + {} + >, + {}, + { + kind: 'search-result-list-item'; + namespace: undefined; + name: undefined; + } >; - unregisterRedirect: ExternalRouteRef; } >; export default _default; diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index c1fb52fb94..541cb08887 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -33,6 +33,7 @@ import { SearchResultListItemExtensionProps } from '@backstage/plugin-search-rea import { StarredEntitiesApi } from '@backstage/plugin-catalog-react'; import { StorageApi } from '@backstage/core-plugin-api'; import { StyleRules } from '@material-ui/core/styles/withStyles'; +import { SystemEntity } from '@backstage/catalog-model'; import { TableColumn } from '@backstage/core-components'; import { TableOptions } from '@backstage/core-components'; import { TableProps } from '@backstage/core-components'; @@ -304,6 +305,10 @@ export class DefaultStarredEntitiesApi implements StarredEntitiesApi { // @public (undocumented) export interface DependencyOfComponentsCardProps { + // (undocumented) + columns?: TableColumn[]; + // (undocumented) + tableOptions?: TableOptions; // (undocumented) title?: string; // (undocumented) @@ -370,6 +375,11 @@ export const EntityHasSubcomponentsCard: ( props: HasSubcomponentsCardProps, ) => JSX.Element; +// @public (undocumented) +export const EntityHasSubdomainsCard: ( + props: HasSubdomainsCardProps, +) => JSX.Element; + // @public (undocumented) export const EntityHasSystemsCard: (props: HasSystemsCardProps) => JSX.Element; @@ -516,6 +526,10 @@ export function hasCatalogProcessingErrors( // @public (undocumented) export interface HasComponentsCardProps { + // (undocumented) + columns?: TableColumn[]; + // (undocumented) + tableOptions?: TableOptions; // (undocumented) title?: string; // (undocumented) @@ -535,6 +549,10 @@ export function hasRelationWarnings( // @public (undocumented) export interface HasResourcesCardProps { + // (undocumented) + columns?: TableColumn[]; + // (undocumented) + tableOptions?: TableOptions; // (undocumented) title?: string; // (undocumented) @@ -543,6 +561,18 @@ export interface HasResourcesCardProps { // @public (undocumented) export interface HasSubcomponentsCardProps { + // (undocumented) + columns?: TableColumn[]; + // (undocumented) + tableOptions?: TableOptions; + // (undocumented) + title?: string; + // (undocumented) + variant?: InfoCardVariants; +} + +// @public (undocumented) +export interface HasSubdomainsCardProps { // (undocumented) tableOptions?: TableOptions; // (undocumented) @@ -553,6 +583,10 @@ export interface HasSubcomponentsCardProps { // @public (undocumented) export interface HasSystemsCardProps { + // (undocumented) + columns?: TableColumn[]; + // (undocumented) + tableOptions?: TableOptions; // (undocumented) title?: string; // (undocumented) diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 7cafc9cf92..a592ae3fd5 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.22.0", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin", @@ -29,7 +29,7 @@ "sideEffects": false, "exports": { ".": "./src/index.ts", - "./alpha": "./src/alpha.ts", + "./alpha": "./src/alpha/index.ts", "./package.json": "./package.json" }, "main": "src/index.ts", @@ -37,7 +37,7 @@ "typesVersions": { "*": { "alpha": [ - "src/alpha.ts" + "src/alpha/index.ts" ], "package.json": [ "package.json" @@ -90,6 +90,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/frontend-test-utils": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/dom": "^10.0.0", diff --git a/plugins/catalog/src/alpha/apis.tsx b/plugins/catalog/src/alpha/apis.tsx index 92880ba728..d0f80c3aec 100644 --- a/plugins/catalog/src/alpha/apis.tsx +++ b/plugins/catalog/src/alpha/apis.tsx @@ -21,7 +21,7 @@ import { storageApiRef, } from '@backstage/core-plugin-api'; import { CatalogClient } from '@backstage/catalog-client'; -import { createApiExtension } from '@backstage/frontend-plugin-api'; +import { ApiBlueprint } from '@backstage/frontend-plugin-api'; import { catalogApiRef, entityPresentationApiRef, @@ -32,33 +32,42 @@ import { DefaultStarredEntitiesApi, } from '../apis'; -export const catalogApi = createApiExtension({ - factory: createApiFactory({ - api: catalogApiRef, - deps: { - discoveryApi: discoveryApiRef, - fetchApi: fetchApiRef, - }, - factory: ({ discoveryApi, fetchApi }) => - new CatalogClient({ discoveryApi, fetchApi }), - }), +export const catalogApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: catalogApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ discoveryApi, fetchApi }) => + new CatalogClient({ discoveryApi, fetchApi }), + }), + }, }); -export const catalogStarredEntitiesApi = createApiExtension({ - factory: createApiFactory({ - api: starredEntitiesApiRef, - deps: { storageApi: storageApiRef }, - factory: ({ storageApi }) => new DefaultStarredEntitiesApi({ storageApi }), - }), +export const catalogStarredEntitiesApi = ApiBlueprint.make({ + name: 'starred-entities', + params: { + factory: createApiFactory({ + api: starredEntitiesApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => + new DefaultStarredEntitiesApi({ storageApi }), + }), + }, }); -export const entityPresentationApi = createApiExtension({ - factory: createApiFactory({ - api: entityPresentationApiRef, - deps: { catalogApiImp: catalogApiRef }, - factory: ({ catalogApiImp }) => - DefaultEntityPresentationApi.create({ catalogApi: catalogApiImp }), - }), +export const entityPresentationApi = ApiBlueprint.make({ + name: 'entity-presentation', + params: { + factory: createApiFactory({ + api: entityPresentationApiRef, + deps: { catalogApiImp: catalogApiRef }, + factory: ({ catalogApiImp }) => + DefaultEntityPresentationApi.create({ catalogApi: catalogApiImp }), + }), + }, }); export default [catalogApi, catalogStarredEntitiesApi, entityPresentationApi]; diff --git a/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.test.tsx b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.test.tsx new file mode 100644 index 0000000000..9cf8b65957 --- /dev/null +++ b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.test.tsx @@ -0,0 +1,104 @@ +/* + * 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 { CatalogFilterBlueprint } from './CatalogFilterBlueprint'; +import { + coreExtensionData, + createExtension, + createExtensionInput, +} from '@backstage/frontend-plugin-api'; +import { createExtensionTester } from '@backstage/frontend-test-utils'; +import { waitFor, screen } from '@testing-library/react'; + +describe('CatalogFilterBlueprint', () => { + it('should create an extension with sane defaults', () => { + const extension = CatalogFilterBlueprint.make({ + params: { + loader: async () =>
, + }, + }); + expect(extension).toMatchInlineSnapshot(` + { + "$$type": "@backstage/ExtensionDefinition", + "attachTo": { + "id": "page:catalog", + "input": "filters", + }, + "configSchema": undefined, + "disabled": false, + "factory": [Function], + "inputs": {}, + "kind": "catalog-filter", + "name": undefined, + "namespace": undefined, + "output": [ + [Function], + ], + "override": [Function], + "toString": [Function], + "version": "v2", + } + `); + }); + + it('should allow overrding of inputs and config', async () => { + const extension = CatalogFilterBlueprint.makeWithOverrides({ + name: 'test-name', + inputs: { + mock: createExtensionInput([coreExtensionData.reactElement]), + }, + config: { + schema: { + test: z => z.string(), + }, + }, + factory(originalFactory, { config, inputs }) { + return originalFactory({ + loader: async () => ( +
+ config: {config.test} +
+ {inputs.mock.map((i, k) => ( +
{i.get(coreExtensionData.reactElement)}
+ ))} +
+
+ ), + }); + }, + }); + + const mockExtension = createExtension({ + attachTo: { id: 'catalog-filter:test-name', input: 'mock' }, + output: [coreExtensionData.reactElement], + factory() { + return [coreExtensionData.reactElement(
im a mock
)]; + }, + }); + + createExtensionTester(extension, { config: { test: 'mock test config' } }) + .add(mockExtension) + .render(); + + await waitFor(() => { + expect(screen.getByTestId('test')).toBeInTheDocument(); + expect(screen.getByTestId('test')).toHaveTextContent( + 'config: mock test config', + ); + expect(screen.getByTestId('contents')).toHaveTextContent('im a mock'); + }); + }); +}); diff --git a/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.ts b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.ts new file mode 100644 index 0000000000..a1063b2526 --- /dev/null +++ b/plugins/catalog/src/alpha/blueprints/CatalogFilterBlueprint.ts @@ -0,0 +1,38 @@ +/* + * 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 { + ExtensionBoundary, + coreExtensionData, + createExtensionBlueprint, +} from '@backstage/frontend-plugin-api'; + +/** + * Creates Catalog Filter Extensions + * @alpha + */ +export const CatalogFilterBlueprint = createExtensionBlueprint({ + kind: 'catalog-filter', + attachTo: { id: 'page:catalog', input: 'filters' }, + output: [coreExtensionData.reactElement], + factory(params: { loader: () => Promise }, { node }) { + return [ + coreExtensionData.reactElement( + ExtensionBoundary.lazy(node, params.loader), + ), + ]; + }, +}); diff --git a/packages/backend-app-api/src/services/implementations/httpAuth/index.ts b/plugins/catalog/src/alpha/blueprints/index.ts similarity index 90% rename from packages/backend-app-api/src/services/implementations/httpAuth/index.ts rename to plugins/catalog/src/alpha/blueprints/index.ts index edd7e53026..a3a7bcb33f 100644 --- a/packages/backend-app-api/src/services/implementations/httpAuth/index.ts +++ b/plugins/catalog/src/alpha/blueprints/index.ts @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export { httpAuthServiceFactory } from './httpAuthServiceFactory'; +export { CatalogFilterBlueprint } from './CatalogFilterBlueprint'; diff --git a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx b/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx deleted file mode 100644 index 48ee647b81..0000000000 --- a/plugins/catalog/src/alpha/createCatalogFilterExtension.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { lazy } from 'react'; -import { - AnyExtensionInputMap, - ExtensionBoundary, - PortableSchema, - coreExtensionData, - createExtension, -} from '@backstage/frontend-plugin-api'; - -/** @alpha */ -export function createCatalogFilterExtension< - TInputs extends AnyExtensionInputMap, - TConfig = never, ->(options: { - namespace?: string; - name?: string; - inputs?: TInputs; - configSchema?: PortableSchema; - loader: (options: { config: TConfig }) => Promise; -}) { - return createExtension({ - kind: 'catalog-filter', - namespace: options.namespace, - name: options.name, - attachTo: { id: 'page:catalog', input: 'filters' }, - inputs: options.inputs ?? {}, - configSchema: options.configSchema, - output: { - element: coreExtensionData.reactElement, - }, - factory({ config, node }) { - const ExtensionComponent = lazy(() => - options - .loader({ config }) - .then(element => ({ default: () => element })), - ); - - return { - element: ( - - - - ), - }; - }, - }); -} diff --git a/plugins/catalog/src/alpha/entityCards.tsx b/plugins/catalog/src/alpha/entityCards.tsx index da6a6a669e..5f8ee16f87 100644 --- a/plugins/catalog/src/alpha/entityCards.tsx +++ b/plugins/catalog/src/alpha/entityCards.tsx @@ -15,81 +15,116 @@ */ import React from 'react'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { compatWrapper } from '@backstage/core-compat-api'; -export const catalogAboutEntityCard = createEntityCardExtension({ +export const catalogAboutEntityCard = EntityCardBlueprint.make({ name: 'about', - loader: async () => - import('../components/AboutCard').then(m => - compatWrapper(), - ), + params: { + loader: async () => + import('../components/AboutCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogLinksEntityCard = createEntityCardExtension({ +export const catalogLinksEntityCard = EntityCardBlueprint.make({ name: 'links', - filter: 'has:links', - loader: async () => - import('../components/EntityLinksCard').then(m => - compatWrapper(), - ), + params: { + filter: 'has:links', + loader: async () => + import('../components/EntityLinksCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogLabelsEntityCard = createEntityCardExtension({ +export const catalogLabelsEntityCard = EntityCardBlueprint.make({ name: 'labels', - filter: 'has:labels', - loader: async () => - import('../components/EntityLabelsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'has:labels', + loader: async () => + import('../components/EntityLabelsCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogDependsOnComponentsEntityCard = createEntityCardExtension({ +export const catalogDependsOnComponentsEntityCard = EntityCardBlueprint.make({ name: 'depends-on-components', - loader: async () => - import('../components/DependsOnComponentsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:component', + loader: async () => + import('../components/DependsOnComponentsCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogDependsOnResourcesEntityCard = createEntityCardExtension({ +export const catalogDependsOnResourcesEntityCard = EntityCardBlueprint.make({ name: 'depends-on-resources', - loader: async () => - import('../components/DependsOnResourcesCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:component', + loader: async () => + import('../components/DependsOnResourcesCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogHasComponentsEntityCard = createEntityCardExtension({ +export const catalogHasComponentsEntityCard = EntityCardBlueprint.make({ name: 'has-components', - loader: async () => - import('../components/HasComponentsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:system', + loader: async () => + import('../components/HasComponentsCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogHasResourcesEntityCard = createEntityCardExtension({ +export const catalogHasResourcesEntityCard = EntityCardBlueprint.make({ name: 'has-resources', - loader: async () => - import('../components/HasResourcesCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:system', + loader: async () => + import('../components/HasResourcesCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogHasSubcomponentsEntityCard = createEntityCardExtension({ +export const catalogHasSubcomponentsEntityCard = EntityCardBlueprint.make({ name: 'has-subcomponents', - loader: async () => - import('../components/HasSubcomponentsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:component', + loader: async () => + import('../components/HasSubcomponentsCard').then(m => + compatWrapper(), + ), + }, }); -export const catalogHasSystemsEntityCard = createEntityCardExtension({ +export const catalogHasSubdomainsEntityCard = EntityCardBlueprint.make({ + name: 'has-subdomains', + params: { + filter: 'kind:domain', + loader: async () => + import('../components/HasSubdomainsCard').then(m => + compatWrapper(), + ), + }, +}); + +export const catalogHasSystemsEntityCard = EntityCardBlueprint.make({ name: 'has-systems', - loader: async () => - import('../components/HasSystemsCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:domain', + loader: async () => + import('../components/HasSystemsCard').then(m => + compatWrapper(), + ), + }, }); export default [ @@ -101,5 +136,6 @@ export default [ catalogHasComponentsEntityCard, catalogHasResourcesEntityCard, catalogHasSubcomponentsEntityCard, + catalogHasSubdomainsEntityCard, catalogHasSystemsEntityCard, ]; diff --git a/plugins/catalog/src/alpha/entityContents.tsx b/plugins/catalog/src/alpha/entityContents.tsx index 75c3fef49f..259b92492c 100644 --- a/plugins/catalog/src/alpha/entityContents.tsx +++ b/plugins/catalog/src/alpha/entityContents.tsx @@ -19,27 +19,38 @@ import { coreExtensionData, createExtensionInput, } from '@backstage/frontend-plugin-api'; -import { - createEntityContentExtension, - catalogExtensionData, -} from '@backstage/plugin-catalog-react/alpha'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; -export const catalogOverviewEntityContent = createEntityContentExtension({ - name: 'overview', - defaultPath: '/', - defaultTitle: 'Overview', - disabled: false, - inputs: { - cards: createExtensionInput({ - element: coreExtensionData.reactElement, - filterFunction: catalogExtensionData.entityFilterFunction.optional(), - filterExpression: catalogExtensionData.entityFilterExpression.optional(), - }), - }, - loader: async ({ inputs }) => - import('./EntityOverviewPage').then(m => ( - c.output)} /> - )), -}); +export const catalogOverviewEntityContent = + EntityContentBlueprint.makeWithOverrides({ + name: 'overview', + inputs: { + cards: createExtensionInput([ + coreExtensionData.reactElement, + EntityContentBlueprint.dataRefs.filterFunction.optional(), + EntityContentBlueprint.dataRefs.filterExpression.optional(), + ]), + }, + factory: (originalFactory, { inputs }) => { + return originalFactory({ + defaultPath: '/', + defaultTitle: 'Overview', + loader: async () => + import('./EntityOverviewPage').then(m => ( + ({ + element: c.get(coreExtensionData.reactElement), + filterFunction: c.get( + EntityContentBlueprint.dataRefs.filterFunction, + ), + filterExpression: c.get( + EntityContentBlueprint.dataRefs.filterExpression, + ), + }))} + /> + )), + }); + }, + }); export default [catalogOverviewEntityContent]; diff --git a/plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.test.ts b/plugins/catalog/src/alpha/filter/matchers/createHasMatcher.test.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.test.ts rename to plugins/catalog/src/alpha/filter/matchers/createHasMatcher.test.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.ts b/plugins/catalog/src/alpha/filter/matchers/createHasMatcher.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createHasMatcher.ts rename to plugins/catalog/src/alpha/filter/matchers/createHasMatcher.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.test.ts b/plugins/catalog/src/alpha/filter/matchers/createIsMatcher.test.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.test.ts rename to plugins/catalog/src/alpha/filter/matchers/createIsMatcher.test.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.ts b/plugins/catalog/src/alpha/filter/matchers/createIsMatcher.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createIsMatcher.ts rename to plugins/catalog/src/alpha/filter/matchers/createIsMatcher.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.test.ts b/plugins/catalog/src/alpha/filter/matchers/createKindMatcher.test.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.test.ts rename to plugins/catalog/src/alpha/filter/matchers/createKindMatcher.test.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.ts b/plugins/catalog/src/alpha/filter/matchers/createKindMatcher.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createKindMatcher.ts rename to plugins/catalog/src/alpha/filter/matchers/createKindMatcher.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.test.ts b/plugins/catalog/src/alpha/filter/matchers/createTypeMatcher.test.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.test.ts rename to plugins/catalog/src/alpha/filter/matchers/createTypeMatcher.test.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.ts b/plugins/catalog/src/alpha/filter/matchers/createTypeMatcher.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/createTypeMatcher.ts rename to plugins/catalog/src/alpha/filter/matchers/createTypeMatcher.ts diff --git a/plugins/catalog/src/alpha/filter/matrchers/types.ts b/plugins/catalog/src/alpha/filter/matchers/types.ts similarity index 100% rename from plugins/catalog/src/alpha/filter/matrchers/types.ts rename to plugins/catalog/src/alpha/filter/matchers/types.ts diff --git a/plugins/catalog/src/alpha/filter/parseFilterExpression.ts b/plugins/catalog/src/alpha/filter/parseFilterExpression.ts index a4c17cb8da..4f809690f7 100644 --- a/plugins/catalog/src/alpha/filter/parseFilterExpression.ts +++ b/plugins/catalog/src/alpha/filter/parseFilterExpression.ts @@ -16,11 +16,11 @@ import { Entity } from '@backstage/catalog-model'; import { InputError } from '@backstage/errors'; -import { EntityMatcherFn } from './matrchers/types'; -import { createKindMatcher } from './matrchers/createKindMatcher'; -import { createTypeMatcher } from './matrchers/createTypeMatcher'; -import { createIsMatcher } from './matrchers/createIsMatcher'; -import { createHasMatcher } from './matrchers/createHasMatcher'; +import { EntityMatcherFn } from './matchers/types'; +import { createKindMatcher } from './matchers/createKindMatcher'; +import { createTypeMatcher } from './matchers/createTypeMatcher'; +import { createIsMatcher } from './matchers/createIsMatcher'; +import { createHasMatcher } from './matchers/createHasMatcher'; const rootMatcherFactories: Record< string, diff --git a/plugins/catalog/src/alpha/filters.tsx b/plugins/catalog/src/alpha/filters.tsx index 62cf9a1353..3507658731 100644 --- a/plugins/catalog/src/alpha/filters.tsx +++ b/plugins/catalog/src/alpha/filters.tsx @@ -15,97 +15,122 @@ */ import React from 'react'; -import { createCatalogFilterExtension } from './createCatalogFilterExtension'; -import { createSchemaFromZod } from '@backstage/frontend-plugin-api'; +import { CatalogFilterBlueprint } from './blueprints'; -const catalogTagCatalogFilter = createCatalogFilterExtension({ +const catalogTagCatalogFilter = CatalogFilterBlueprint.make({ name: 'tag', - loader: async () => { - const { EntityTagPicker } = await import('@backstage/plugin-catalog-react'); - return ; + params: { + loader: async () => { + const { EntityTagPicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, }, }); -const catalogKindCatalogFilter = createCatalogFilterExtension({ +const catalogKindCatalogFilter = CatalogFilterBlueprint.makeWithOverrides({ name: 'kind', - configSchema: createSchemaFromZod(z => - z.object({ - initialFilter: z.string().default('component'), - }), - ), - loader: async ({ config }) => { - const { EntityKindPicker } = await import( - '@backstage/plugin-catalog-react' - ); - return ; + config: { + schema: { + initialFilter: z => z.string().default('component'), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + loader: async () => { + const { EntityKindPicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, + }); }, }); -const catalogTypeCatalogFilter = createCatalogFilterExtension({ +const catalogTypeCatalogFilter = CatalogFilterBlueprint.make({ name: 'type', - loader: async () => { - const { EntityTypePicker } = await import( - '@backstage/plugin-catalog-react' - ); - return ; + params: { + loader: async () => { + const { EntityTypePicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, }, }); -const catalogModeCatalogFilter = createCatalogFilterExtension({ +const catalogModeCatalogFilter = CatalogFilterBlueprint.makeWithOverrides({ name: 'mode', - configSchema: createSchemaFromZod(z => - z.object({ - mode: z.enum(['owners-only', 'all']).optional(), - }), - ), - loader: async ({ config }) => { - const { EntityOwnerPicker } = await import( - '@backstage/plugin-catalog-react' - ); - return ; + config: { + schema: { + mode: z => z.enum(['owners-only', 'all']).optional(), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + loader: async () => { + const { EntityOwnerPicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, + }); }, }); -const catalogNamespaceCatalogFilter = createCatalogFilterExtension({ +const catalogNamespaceCatalogFilter = CatalogFilterBlueprint.make({ name: 'namespace', - loader: async () => { - const { EntityNamespacePicker } = await import( - '@backstage/plugin-catalog-react' - ); - return ; + params: { + loader: async () => { + const { EntityNamespacePicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, }, }); -const catalogLifecycleCatalogFilter = createCatalogFilterExtension({ +const catalogLifecycleCatalogFilter = CatalogFilterBlueprint.make({ name: 'lifecycle', - loader: async () => { - const { EntityLifecyclePicker } = await import( - '@backstage/plugin-catalog-react' - ); - return ; + params: { + loader: async () => { + const { EntityLifecyclePicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, }, }); -const catalogProcessingStatusCatalogFilter = createCatalogFilterExtension({ +const catalogProcessingStatusCatalogFilter = CatalogFilterBlueprint.make({ name: 'processing-status', - loader: async () => { - const { EntityProcessingStatusPicker } = await import( - '@backstage/plugin-catalog-react' - ); - return ; + params: { + loader: async () => { + const { EntityProcessingStatusPicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, }, }); -const catalogListCatalogFilter = createCatalogFilterExtension({ +const catalogListCatalogFilter = CatalogFilterBlueprint.makeWithOverrides({ name: 'list', - configSchema: createSchemaFromZod(z => - z.object({ - initialFilter: z.enum(['owned', 'starred', 'all']).default('owned'), - }), - ), - loader: async ({ config }) => { - const { UserListPicker } = await import('@backstage/plugin-catalog-react'); - return ; + config: { + schema: { + initialFilter: z => z.enum(['owned', 'starred', 'all']).default('owned'), + }, + }, + factory(originalFactory, { config }) { + return originalFactory({ + loader: async () => { + const { UserListPicker } = await import( + '@backstage/plugin-catalog-react' + ); + return ; + }, + }); }, }); diff --git a/plugins/catalog/src/alpha/index.ts b/plugins/catalog/src/alpha/index.ts index 06a78ec6ea..3357024fd9 100644 --- a/plugins/catalog/src/alpha/index.ts +++ b/plugins/catalog/src/alpha/index.ts @@ -15,4 +15,6 @@ */ export { default } from './plugin'; -export { createCatalogFilterExtension } from './createCatalogFilterExtension'; + +export * from './blueprints'; +export * from './translation'; diff --git a/plugins/catalog/src/alpha/navItems.tsx b/plugins/catalog/src/alpha/navItems.tsx index c1360cc29f..0eb4e6827e 100644 --- a/plugins/catalog/src/alpha/navItems.tsx +++ b/plugins/catalog/src/alpha/navItems.tsx @@ -16,13 +16,15 @@ import HomeIcon from '@material-ui/icons/Home'; import { convertLegacyRouteRef } from '@backstage/core-compat-api'; -import { createNavItemExtension } from '@backstage/frontend-plugin-api'; +import { NavItemBlueprint } from '@backstage/frontend-plugin-api'; import { rootRouteRef } from '../routes'; -export const catalogNavItem = createNavItemExtension({ - routeRef: convertLegacyRouteRef(rootRouteRef), - title: 'Catalog', - icon: HomeIcon, +export const catalogNavItem = NavItemBlueprint.make({ + params: { + routeRef: convertLegacyRouteRef(rootRouteRef), + title: 'Catalog', + icon: HomeIcon, + }, }); export default [catalogNavItem]; diff --git a/plugins/catalog/src/alpha/pages.tsx b/plugins/catalog/src/alpha/pages.tsx index 5c583e6a19..764acd1fe5 100644 --- a/plugins/catalog/src/alpha/pages.tsx +++ b/plugins/catalog/src/alpha/pages.tsx @@ -20,72 +20,86 @@ import { convertLegacyRouteRef, } from '@backstage/core-compat-api'; import { - createPageExtension, coreExtensionData, createExtensionInput, + PageBlueprint, } from '@backstage/frontend-plugin-api'; import { AsyncEntityProvider, entityRouteRef, } from '@backstage/plugin-catalog-react'; -import { catalogExtensionData } from '@backstage/plugin-catalog-react/alpha'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; import { rootRouteRef } from '../routes'; import { useEntityFromUrl } from '../components/CatalogEntityPage/useEntityFromUrl'; import { buildFilterFn } from './filter/FilterWrapper'; -export const catalogPage = createPageExtension({ - defaultPath: '/catalog', - routeRef: convertLegacyRouteRef(rootRouteRef), +export const catalogPage = PageBlueprint.makeWithOverrides({ inputs: { - filters: createExtensionInput({ - element: coreExtensionData.reactElement, - }), + filters: createExtensionInput([coreExtensionData.reactElement]), }, - loader: async ({ inputs }) => { - const { BaseCatalogPage } = await import('../components/CatalogPage'); - const filters = inputs.filters.map(filter => filter.output.element); - return compatWrapper({filters}} />); + factory(originalFactory, { inputs }) { + return originalFactory({ + defaultPath: '/catalog', + routeRef: convertLegacyRouteRef(rootRouteRef), + loader: async () => { + const { BaseCatalogPage } = await import('../components/CatalogPage'); + const filters = inputs.filters.map(filter => + filter.get(coreExtensionData.reactElement), + ); + return compatWrapper({filters}} />); + }, + }); }, }); -export const catalogEntityPage = createPageExtension({ +export const catalogEntityPage = PageBlueprint.makeWithOverrides({ name: 'entity', - defaultPath: '/catalog/:namespace/:kind/:name', - routeRef: convertLegacyRouteRef(entityRouteRef), inputs: { - contents: createExtensionInput({ - element: coreExtensionData.reactElement, - path: coreExtensionData.routePath, - routeRef: coreExtensionData.routeRef.optional(), - title: catalogExtensionData.entityContentTitle, - filterFunction: catalogExtensionData.entityFilterFunction.optional(), - filterExpression: catalogExtensionData.entityFilterExpression.optional(), - }), + contents: createExtensionInput([ + coreExtensionData.reactElement, + coreExtensionData.routePath, + coreExtensionData.routeRef.optional(), + EntityContentBlueprint.dataRefs.title, + EntityContentBlueprint.dataRefs.filterFunction.optional(), + EntityContentBlueprint.dataRefs.filterExpression.optional(), + ]), }, - loader: async ({ inputs }) => { - const { EntityLayout } = await import('../components/EntityLayout'); - const Component = () => { - return ( - - - {inputs.contents.map(({ output }) => ( - - {output.element} - - ))} - - - ); - }; - return compatWrapper(); + factory(originalFactory, { inputs }) { + return originalFactory({ + defaultPath: '/catalog/:namespace/:kind/:name', + routeRef: convertLegacyRouteRef(entityRouteRef), + loader: async () => { + const { EntityLayout } = await import('../components/EntityLayout'); + const Component = () => { + return ( + + + {inputs.contents.map(output => { + return ( + + {output.get(coreExtensionData.reactElement)} + + ); + })} + + + ); + }; + return compatWrapper(); + }, + }); }, }); diff --git a/plugins/catalog/src/alpha/plugin.tsx b/plugins/catalog/src/alpha/plugin.tsx index 9a964c4e46..8a18500043 100644 --- a/plugins/catalog/src/alpha/plugin.tsx +++ b/plugins/catalog/src/alpha/plugin.tsx @@ -15,7 +15,7 @@ */ import { convertLegacyRouteRefs } from '@backstage/core-compat-api'; -import { createPlugin } from '@backstage/frontend-plugin-api'; +import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; import { entityRouteRef } from '@backstage/plugin-catalog-react'; @@ -36,7 +36,7 @@ import entityContents from './entityContents'; import searchResultItems from './searchResultItems'; /** @alpha */ -export default createPlugin({ +export default createFrontendPlugin({ id: 'catalog', routes: convertLegacyRouteRefs({ catalogIndex: rootRouteRef, diff --git a/plugins/catalog/src/alpha/searchResultItems.tsx b/plugins/catalog/src/alpha/searchResultItems.tsx index 5233144173..8e140de637 100644 --- a/plugins/catalog/src/alpha/searchResultItems.tsx +++ b/plugins/catalog/src/alpha/searchResultItems.tsx @@ -14,14 +14,16 @@ * limitations under the License. */ -import { createSearchResultListItemExtension } from '@backstage/plugin-search-react/alpha'; +import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; -export const catalogSearchResultListItem = createSearchResultListItemExtension({ - predicate: result => result.type === 'software-catalog', - component: () => - import('../components/CatalogSearchResultListItem').then( - m => m.CatalogSearchResultListItem, - ), +export const catalogSearchResultListItem = SearchResultListItemBlueprint.make({ + params: { + predicate: result => result.type === 'software-catalog', + component: () => + import('../components/CatalogSearchResultListItem').then( + m => m.CatalogSearchResultListItem, + ), + }, }); export default [catalogSearchResultListItem]; diff --git a/plugins/catalog/src/translation.ts b/plugins/catalog/src/alpha/translation.ts similarity index 97% rename from plugins/catalog/src/translation.ts rename to plugins/catalog/src/alpha/translation.ts index 2699b58542..f38a34be84 100644 --- a/plugins/catalog/src/translation.ts +++ b/plugins/catalog/src/alpha/translation.ts @@ -143,6 +143,10 @@ export const catalogTranslationRef = createTranslationRef({ title: 'Has subcomponents', emptyMessage: 'No subcomponent is part of this component', }, + hasSubdomainsCard: { + title: 'Has subdomains', + emptyMessage: 'No subdomain is part of this domain', + }, hasSystemsCard: { title: 'Has systems', emptyMessage: 'No system is part of this domain', diff --git a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts index 4cc4d79f52..2c3fb9352b 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts +++ b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.test.ts @@ -161,6 +161,15 @@ describe('DefaultEntityPresentationApi', () => { expect(catalogApi.getEntitiesByRefs).toHaveBeenCalledWith( expect.objectContaining({ entityRefs: ['component:default/test'], + fields: [ + 'kind', + 'metadata.name', + 'metadata.namespace', + 'metadata.title', + 'metadata.description', + 'spec.profile.displayName', + 'spec.type', + ], }), ); }); diff --git a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts index 1955763d3d..2832792951 100644 --- a/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts +++ b/plugins/catalog/src/apis/EntityPresentationApi/DefaultEntityPresentationApi.ts @@ -26,15 +26,15 @@ import { EntityRefPresentation, EntityRefPresentationSnapshot, } from '@backstage/plugin-catalog-react'; -import { HumanDuration, durationToMilliseconds } from '@backstage/types'; +import { durationToMilliseconds, HumanDuration } from '@backstage/types'; import DataLoader from 'dataloader'; import ExpiryMap from 'expiry-map'; import ObservableImpl from 'zen-observable'; import { + createDefaultRenderer, DEFAULT_BATCH_DELAY, DEFAULT_CACHE_TTL, DEFAULT_ICONS, - createDefaultRenderer, } from './defaults'; /** @@ -371,6 +371,15 @@ export class DefaultEntityPresentationApi implements EntityPresentationApi { async (entityRefs: readonly string[]) => { const { items } = await options.catalogApi!.getEntitiesByRefs({ entityRefs: entityRefs as string[], + fields: [ + 'kind', + 'metadata.name', + 'metadata.namespace', + 'metadata.title', + 'metadata.description', + 'spec.profile.displayName', + 'spec.type', + ], }); const now = Date.now(); diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index ff5d7fc801..cf4a3c2d43 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -64,7 +64,7 @@ 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 { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index 3f781f4759..f6d2d80264 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -33,7 +33,7 @@ import React from 'react'; import { AboutField } from './AboutField'; import { LinksGridList } from '../EntityLinksCard/LinksGridList'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; const useStyles = makeStyles({ description: { diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx index db765b9145..a46a552b8d 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx @@ -33,7 +33,7 @@ import { import React, { ReactNode } from 'react'; import { createComponentRouteRef } from '../../routes'; import { CatalogTable, CatalogTableRow } from '../CatalogTable'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { CatalogTableColumnsFunc } from '../CatalogTable/types'; diff --git a/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx index 1a596f8b9b..f4501bd795 100644 --- a/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx +++ b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx @@ -27,7 +27,7 @@ import { ResultHighlight, } from '@backstage/plugin-search-common'; import { HighlightedSearchResultText } from '@backstage/plugin-search-react'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; const useStyles = makeStyles( diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index e18467249a..1026dc5d26 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -49,7 +49,7 @@ 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'; +import { catalogTranslationRef } from '../../alpha/translation'; /** * Props for {@link CatalogTable}. diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx index 617d009d4f..6c6f811cc1 100644 --- a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx +++ b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx @@ -14,8 +14,15 @@ * limitations under the License. */ -import { RELATION_DEPENDENCY_OF } from '@backstage/catalog-model'; -import { InfoCardVariants } from '@backstage/core-components'; +import { + ComponentEntity, + RELATION_DEPENDENCY_OF, +} from '@backstage/catalog-model'; +import { + InfoCardVariants, + TableColumn, + TableOptions, +} from '@backstage/core-components'; import React from 'react'; import { asComponentEntities, @@ -23,13 +30,15 @@ import { componentEntityHelpLink, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface DependencyOfComponentsCardProps { variant?: InfoCardVariants; title?: string; + columns?: TableColumn[]; + tableOptions?: TableOptions; } export function DependencyOfComponentsCard( @@ -39,6 +48,8 @@ export function DependencyOfComponentsCard( const { variant = 'gridItem', title = t('dependencyOfComponentsCard.title'), + columns = componentEntityColumns, + tableOptions = {}, } = props; return ( ); } diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx index fe56ea582b..eafa33346d 100644 --- a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx +++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx @@ -27,7 +27,7 @@ import { componentEntityHelpLink, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx index 281bfd8766..723b87cc29 100644 --- a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx +++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx @@ -27,7 +27,7 @@ import { RelatedEntitiesCard, resourceEntityColumns, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx index 5b740038a1..77f934e7de 100644 --- a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx +++ b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx @@ -33,7 +33,7 @@ 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 { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx b/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx index 08496d4819..63247d0c62 100644 --- a/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx +++ b/plugins/catalog/src/components/EntityContextMenu/UnregisterEntity.tsx @@ -19,7 +19,7 @@ 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 { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; type VisibleType = 'visible' | 'hidden' | 'disable'; diff --git a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx index d48b45f054..c4557b6774 100644 --- a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx +++ b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsCard.tsx @@ -25,7 +25,7 @@ import { import { EntityLabelsEmptyState } from './EntityLabelsEmptyState'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx index 16d0a3a897..d78be2d2ce 100644 --- a/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx +++ b/plugins/catalog/src/components/EntityLabelsCard/EntityLabelsEmptyState.tsx @@ -19,7 +19,7 @@ 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 { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const ENTITY_YAML = `metadata: diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx index bbc7792acb..e8b957497d 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx @@ -53,7 +53,7 @@ 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 { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx index b5d8269a0b..59e1f6d252 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx @@ -23,7 +23,7 @@ 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'; +import { catalogTranslationRef } from '../../alpha/translation'; /** @public */ export interface EntityLinksCardProps { diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx index 09362385f9..513183677d 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx @@ -20,7 +20,7 @@ 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'; +import { catalogTranslationRef } from '../../alpha/translation'; const ENTITY_YAML = `metadata: name: example diff --git a/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx b/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx index 252005dff5..a033199ffd 100644 --- a/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx +++ b/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx @@ -20,7 +20,7 @@ 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 { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const useStyles = makeStyles(theme => ({ diff --git a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx index c9c24a14a1..b1a7afbb50 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx @@ -23,7 +23,7 @@ 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 { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; interface DeleteEntityDialogProps { diff --git a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx index 10a10e54fd..eb561f9c19 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx @@ -22,7 +22,7 @@ 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 { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** diff --git a/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.tsx b/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.tsx index bd49785abe..3c7c5bb2e3 100644 --- a/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.tsx +++ b/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.tsx @@ -31,7 +31,7 @@ import { import { useApi, ApiHolder } from '@backstage/core-plugin-api'; import useAsync from 'react-use/esm/useAsync'; import { SerializedError } from '@backstage/errors'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; const errorFilter = (i: EntityStatusItem) => diff --git a/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx b/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx index 2ce9120bf0..ed816bfc75 100644 --- a/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx +++ b/plugins/catalog/src/components/EntityRelationWarning/EntityRelationWarning.tsx @@ -26,7 +26,7 @@ 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 { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; async function getRelationWarnings(entity: Entity, catalogApi: CatalogApi) { diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx index 917543dd12..2fc6cde81b 100644 --- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx @@ -14,8 +14,12 @@ * limitations under the License. */ -import { RELATION_HAS_PART } from '@backstage/catalog-model'; -import { InfoCardVariants } from '@backstage/core-components'; +import { ComponentEntity, RELATION_HAS_PART } from '@backstage/catalog-model'; +import { + InfoCardVariants, + TableColumn, + TableOptions, +} from '@backstage/core-components'; import React from 'react'; import { asComponentEntities, @@ -23,28 +27,36 @@ import { componentEntityHelpLink, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasComponentsCardProps { variant?: InfoCardVariants; title?: string; + columns?: TableColumn[]; + tableOptions?: TableOptions; } export function HasComponentsCard(props: HasComponentsCardProps) { const { t } = useTranslationRef(catalogTranslationRef); - const { variant = 'gridItem', title = t('hasComponentsCard.title') } = props; + const { + variant = 'gridItem', + title = t('hasComponentsCard.title'), + columns = componentEntityColumns, + tableOptions = {}, + } = props; return ( ); } diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx index d11f10c882..6b90ce0443 100644 --- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx +++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx @@ -14,8 +14,12 @@ * limitations under the License. */ -import { RELATION_HAS_PART } from '@backstage/catalog-model'; -import { InfoCardVariants } from '@backstage/core-components'; +import { RELATION_HAS_PART, ResourceEntity } from '@backstage/catalog-model'; +import { + InfoCardVariants, + TableColumn, + TableOptions, +} from '@backstage/core-components'; import React from 'react'; import { asResourceEntities, @@ -23,28 +27,36 @@ import { resourceEntityColumns, resourceEntityHelpLink, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasResourcesCardProps { variant?: InfoCardVariants; title?: string; + columns?: TableColumn[]; + tableOptions?: TableOptions; } export function HasResourcesCard(props: HasResourcesCardProps) { const { t } = useTranslationRef(catalogTranslationRef); - const { variant = 'gridItem', title = t('hasResourcesCard.title') } = props; + const { + variant = 'gridItem', + title = t('hasResourcesCard.title'), + columns = resourceEntityColumns, + tableOptions = {}, + } = props; return ( ); } diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index d81349794a..ceab037a45 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -14,30 +14,36 @@ * limitations under the License. */ -import { RELATION_HAS_PART } from '@backstage/catalog-model'; -import { InfoCardVariants, TableOptions } from '@backstage/core-components'; +import { ComponentEntity, RELATION_HAS_PART } from '@backstage/catalog-model'; +import { + InfoCardVariants, + TableColumn, + TableOptions, +} from '@backstage/core-components'; import React from 'react'; import { asComponentEntities, componentEntityColumns, RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasSubcomponentsCardProps { variant?: InfoCardVariants; - tableOptions?: TableOptions; title?: string; + columns?: TableColumn[]; + tableOptions?: TableOptions; } export function HasSubcomponentsCard(props: HasSubcomponentsCardProps) { const { t } = useTranslationRef(catalogTranslationRef); const { variant = 'gridItem', - tableOptions = {}, title = t('hasSubcomponentsCard.title'), + columns = componentEntityColumns, + tableOptions = {}, } = props; return ( ', () => { + const getEntitiesByRefs: jest.MockedFunction< + CatalogApi['getEntitiesByRefs'] + > = jest.fn(); + let Wrapper: React.ComponentType>; + + beforeEach(() => { + Wrapper = ({ children }: { children?: React.ReactNode }) => ( + + {children} + + ); + }); + + afterEach(() => jest.resetAllMocks()); + + it('shows empty list if no relations', async () => { + const entity: Entity = { + apiVersion: 'v1', + kind: 'Domain', + metadata: { + name: 'my-domain', + namespace: 'my-namespace', + }, + relations: [], + }; + + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + expect(screen.getByText('Has subdomains')).toBeInTheDocument(); + expect( + screen.getByText(/No subdomain is part of this domain/i), + ).toBeInTheDocument(); + }); + + it('shows related subdomains', async () => { + const entity: Entity = { + apiVersion: 'v1', + kind: 'Domain', + metadata: { + name: 'my-domain', + namespace: 'my-namespace', + }, + relations: [ + { + targetRef: 'domain:my-namespace/target-name', + type: RELATION_HAS_PART, + }, + ], + }; + getEntitiesByRefs.mockResolvedValue({ + items: [ + { + apiVersion: 'v1', + kind: 'Domain', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }, + ], + }); + + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + await waitFor(() => { + expect(screen.getByText('Has subdomains')).toBeInTheDocument(); + expect(screen.getByText(/target-name/i)).toBeInTheDocument(); + }); + }); +}); diff --git a/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx b/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx new file mode 100644 index 0000000000..71705ea90f --- /dev/null +++ b/plugins/catalog/src/components/HasSubdomainsCard/HasSubdomainsCard.tsx @@ -0,0 +1,55 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RELATION_HAS_PART } from '@backstage/catalog-model'; +import { InfoCardVariants, TableOptions } from '@backstage/core-components'; +import React from 'react'; +import { + asComponentEntities, + componentEntityColumns, + RelatedEntitiesCard, +} from '../RelatedEntitiesCard'; +import { catalogTranslationRef } from '../../alpha/translation'; +import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; + +/** @public */ +export interface HasSubdomainsCardProps { + variant?: InfoCardVariants; + tableOptions?: TableOptions; + title?: string; +} + +export function HasSubdomainsCard(props: HasSubdomainsCardProps) { + const { t } = useTranslationRef(catalogTranslationRef); + const { + variant = 'gridItem', + tableOptions = {}, + title = t('hasSubdomainsCard.title'), + } = props; + return ( + + ); +} diff --git a/packages/backend-app-api/src/services/implementations/database/index.ts b/plugins/catalog/src/components/HasSubdomainsCard/index.ts similarity index 77% rename from packages/backend-app-api/src/services/implementations/database/index.ts rename to plugins/catalog/src/components/HasSubdomainsCard/index.ts index d676c8013e..a740abbcc2 100644 --- a/packages/backend-app-api/src/services/implementations/database/index.ts +++ b/plugins/catalog/src/components/HasSubdomainsCard/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * 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. @@ -14,4 +14,5 @@ * limitations under the License. */ -export { databaseServiceFactory } from './databaseServiceFactory'; +export { HasSubdomainsCard } from './HasSubdomainsCard'; +export type { HasSubdomainsCardProps } from './HasSubdomainsCard'; diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx index cee45a95c5..48742f9d28 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -14,8 +14,12 @@ * limitations under the License. */ -import { RELATION_HAS_PART } from '@backstage/catalog-model'; -import { InfoCardVariants } from '@backstage/core-components'; +import { RELATION_HAS_PART, SystemEntity } from '@backstage/catalog-model'; +import { + InfoCardVariants, + TableColumn, + TableOptions, +} from '@backstage/core-components'; import React from 'react'; import { asSystemEntities, @@ -23,28 +27,36 @@ import { systemEntityColumns, systemEntityHelpLink, } from '../RelatedEntitiesCard'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ export interface HasSystemsCardProps { variant?: InfoCardVariants; title?: string; + columns?: TableColumn[]; + tableOptions?: TableOptions; } export function HasSystemsCard(props: HasSystemsCardProps) { const { t } = useTranslationRef(catalogTranslationRef); - const { variant = 'gridItem', title = t('hasSystemsCard.title') } = props; + const { + variant = 'gridItem', + title = t('hasSystemsCard.title'), + columns = systemEntityColumns, + tableOptions = {}, + } = props; return ( ); } diff --git a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx index dd1359482d..a2814527cc 100644 --- a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx +++ b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx @@ -31,7 +31,18 @@ import { TableColumn, TableOptions, } from '@backstage/core-components'; -import { catalogTranslationRef } from '../../translation'; +import { + asComponentEntities, + asResourceEntities, + asSystemEntities, + componentEntityColumns, + componentEntityHelpLink, + resourceEntityColumns, + resourceEntityHelpLink, + systemEntityColumns, + systemEntityHelpLink, +} from './presets'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ @@ -59,9 +70,9 @@ export type RelatedEntitiesCardProps = { * * @public */ -export function RelatedEntitiesCard( +export const RelatedEntitiesCard = ( props: RelatedEntitiesCardProps, -) { +) => { const { variant = 'gridItem', title, @@ -116,4 +127,14 @@ export function RelatedEntitiesCard( tableOptions={tableOptions} /> ); -} +}; + +RelatedEntitiesCard.componentEntityColumns = componentEntityColumns; +RelatedEntitiesCard.componentEntityHelpLink = componentEntityHelpLink; +RelatedEntitiesCard.asComponentEntities = asComponentEntities; +RelatedEntitiesCard.resourceEntityColumns = resourceEntityColumns; +RelatedEntitiesCard.resourceEntityHelpLink = resourceEntityHelpLink; +RelatedEntitiesCard.asResourceEntities = asResourceEntities; +RelatedEntitiesCard.systemEntityColumns = systemEntityColumns; +RelatedEntitiesCard.systemEntityHelpLink = systemEntityHelpLink; +RelatedEntitiesCard.asSystemEntities = asSystemEntities; diff --git a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx index 5e485d672b..879a5076b1 100644 --- a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx +++ b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.tsx @@ -46,7 +46,7 @@ import { } from '@backstage/core-components'; import { useApi, useRouteRef } from '@backstage/core-plugin-api'; -import { catalogTranslationRef } from '../../translation'; +import { catalogTranslationRef } from '../../alpha/translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; /** @public */ diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index a691011c96..806ea40c66 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -49,6 +49,7 @@ export { EntityHasComponentsCard, EntityHasResourcesCard, EntityHasSubcomponentsCard, + EntityHasSubdomainsCard, EntityHasSystemsCard, EntityLinksCard, EntityLabelsCard, @@ -71,6 +72,7 @@ export type { EntityContextMenuClassKey } from './components/EntityContextMenu'; export type { HasComponentsCardProps } from './components/HasComponentsCard'; export type { HasResourcesCardProps } from './components/HasResourcesCard'; export type { HasSubcomponentsCardProps } from './components/HasSubcomponentsCard'; +export type { HasSubdomainsCardProps } from './components/HasSubdomainsCard'; export type { HasSystemsCardProps } from './components/HasSystemsCard'; export type { RelatedEntitiesCardProps } from './components/RelatedEntitiesCard'; export type { CatalogSearchResultListItemProps } from './components/CatalogSearchResultListItem'; diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts index 33ceaef632..a339ff31a8 100644 --- a/plugins/catalog/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -50,6 +50,7 @@ import { DependsOnResourcesCardProps } from './components/DependsOnResourcesCard import { HasComponentsCardProps } from './components/HasComponentsCard'; import { HasResourcesCardProps } from './components/HasResourcesCard'; import { HasSubcomponentsCardProps } from './components/HasSubcomponentsCard'; +import { HasSubdomainsCardProps } from './components/HasSubdomainsCard'; import { HasSystemsCardProps } from './components/HasSystemsCard'; import { RelatedEntitiesCardProps } from './components/RelatedEntitiesCard'; import { CatalogSearchResultListItemProps } from './components/CatalogSearchResultListItem'; @@ -199,6 +200,19 @@ export const EntityHasSubcomponentsCard: ( }), ); +/** @public */ +export const EntityHasSubdomainsCard: ( + props: HasSubdomainsCardProps, +) => JSX.Element = catalogPlugin.provide( + createComponentExtension({ + name: 'EntityHasSubdomainsCard', + component: { + lazy: () => + import('./components/HasSubdomainsCard').then(m => m.HasSubdomainsCard), + }, + }), +); + /** @public */ export const EntityHasResourcesCard: ( props: HasResourcesCardProps, diff --git a/plugins/catalog/src/setupTests.ts b/plugins/catalog/src/setupTests.ts index 963c0f188b..dae4360fd3 100644 --- a/plugins/catalog/src/setupTests.ts +++ b/plugins/catalog/src/setupTests.ts @@ -15,3 +15,18 @@ */ import '@testing-library/jest-dom'; + +// eslint-disable-next-line no-console +const originalConsoleWarn = console.warn; +// eslint-disable-next-line no-console +console.warn = (...args: any[]) => { + const message = args[0]; + if ( + typeof message === 'string' && + (message.includes('CSSOM.parse is not a function') || + message.includes('[JSS]')) + ) { + return; + } + originalConsoleWarn(...args); +}; diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 76472b69b4..28d63f63d7 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-config-schema +## 0.1.58 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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..7fd576a85f 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", "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..ae25e3b15c 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,122 @@ # @backstage/plugin-devtools-backend +## 0.3.9 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 32a38e1: Removed unused code for lockfile analysis. +- 2886ef7: Deprecated `createRouter` and its router options in favour of the new backend system. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/config-loader@1.9.0 + - @backstage/plugin-permission-node@0.8.1 + - @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.12 + +## 0.3.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.12-next.1 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## 0.3.9-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/config-loader@1.9.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-devtools-common@0.1.12-next.1 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.9.0-next.1 + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-devtools-common@0.1.12-next.0 + - @backstage/cli-common@0.1.14 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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/api-report.md b/plugins/devtools-backend/api-report.md index 482422e66f..5fceda3d46 100644 --- a/plugins/devtools-backend/api-report.md +++ b/plugins/devtools-backend/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { ConfigInfo } from '@backstage/plugin-devtools-common'; import { DevToolsInfo } from '@backstage/plugin-devtools-common'; @@ -13,8 +13,9 @@ import { ExternalDependency } from '@backstage/plugin-devtools-common'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; import { PermissionsService } from '@backstage/backend-plugin-api'; +import { RootConfigService } from '@backstage/backend-plugin-api'; -// @public (undocumented) +// @public @deprecated (undocumented) export function createRouter(options: RouterOptions): Promise; // @public (undocumented) @@ -29,13 +30,13 @@ export class DevToolsBackendApi { } // @public -const devtoolsPlugin: BackendFeatureCompat; +const devtoolsPlugin: BackendFeature; export default devtoolsPlugin; -// @public (undocumented) +// @public @deprecated (undocumented) export interface RouterOptions { // (undocumented) - config: Config; + config: RootConfigService; // (undocumented) devToolsBackendApi?: DevToolsBackendApi; // (undocumented) diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index f15e205bca..03b3df0c00 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", "backstage": { "role": "backend-plugin", "pluginId": "devtools", @@ -56,7 +56,7 @@ "express-promise-router": "^4.1.0", "fs-extra": "^11.0.0", "lodash": "^4.17.21", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "ping": "^0.4.1", "semver": "^7.5.3", "yn": "^4.0.0" diff --git a/plugins/devtools-backend/src/service/router.ts b/plugins/devtools-backend/src/service/router.ts index cec3d87a5a..e3eb72c293 100644 --- a/plugins/devtools-backend/src/service/router.ts +++ b/plugins/devtools-backend/src/service/router.ts @@ -21,7 +21,6 @@ import { devToolsPermissions, } from '@backstage/plugin-devtools-common'; -import { Config } from '@backstage/config'; import { DevToolsBackendApi } from '../api'; import { NotAllowedError } from '@backstage/errors'; import Router from 'express-promise-router'; @@ -36,19 +35,26 @@ import { HttpAuthService, LoggerService, PermissionsService, + RootConfigService, } from '@backstage/backend-plugin-api'; -/** @public */ +/** + * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. + */ export interface RouterOptions { devToolsBackendApi?: DevToolsBackendApi; logger: LoggerService; - config: Config; + config: RootConfigService; permissions: PermissionsService; discovery: DiscoveryService; httpAuth?: HttpAuthService; } -/** @public */ +/** + * @deprecated Please migrate to the new backend system as this will be removed in the future. + * @public + * */ export async function createRouter( options: RouterOptions, ): Promise { 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..5cc72c62ae 100644 --- a/plugins/devtools-common/CHANGELOG.md +++ b/plugins/devtools-common/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-devtools-common +## 0.1.12 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/types@1.1.1 + +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/types@1.1.1 + +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/types@1.1.1 + +## 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..5a14202649 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.12", "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..e1e3b11cf6 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,93 @@ # @backstage/plugin-devtools +## 0.1.17 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-devtools-common@0.1.12 + - @backstage/plugin-permission-react@0.4.25 + +## 0.1.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-devtools-common@0.1.12-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + +## 0.1.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-devtools-common@0.1.12-next.1 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-devtools-common@0.1.12-next.0 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + +## 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/README.md b/plugins/devtools/README.md index ca9b436acc..fef51626ad 100644 --- a/plugins/devtools/README.md +++ b/plugins/devtools/README.md @@ -20,7 +20,7 @@ You may need to modify your Dockerfile to ensure `backstage.json` is copied into ```sh WORKDIR /app # This switches many Node.js dependencies to production mode. -ENV NODE_ENV production +ENV NODE_ENV=production # Then copy the rest of the backend bundle, along with any other files we might want (including backstage.json). COPY --chown=node:node ... backstage.json ./ diff --git a/plugins/devtools/api-report-alpha.md b/plugins/devtools/api-report-alpha.md index 59aefd3698..888faa29c7 100644 --- a/plugins/devtools/api-report-alpha.md +++ b/plugins/devtools/api-report-alpha.md @@ -3,7 +3,13 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) @@ -11,7 +17,66 @@ const _default: BackstagePlugin< { root: RouteRef; }, - {} + {}, + { + 'api:devtools': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + { + kind: 'api'; + namespace: undefined; + name: undefined; + } + >; + 'page:devtools': ExtensionDefinition< + { + path: string | undefined; + }, + { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + {}, + { + kind: 'page'; + namespace: undefined; + name: undefined; + } + >; + 'nav-item:devtools': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >, + {}, + { + kind: 'nav-item'; + namespace: undefined; + name: undefined; + } + >; + } >; export default _default; diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 2749bbeef2..db6236f327 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", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx index 0420556504..4c62dd3155 100644 --- a/plugins/devtools/src/alpha/plugin.tsx +++ b/plugins/devtools/src/alpha/plugin.tsx @@ -16,13 +16,13 @@ import React from 'react'; import { - createApiExtension, createApiFactory, - createNavItemExtension, - createPageExtension, - createPlugin, + createFrontendPlugin, discoveryApiRef, fetchApiRef, + ApiBlueprint, + PageBlueprint, + NavItemBlueprint, } from '@backstage/frontend-plugin-api'; import { devToolsApiRef, DevToolsClient } from '../api'; @@ -34,37 +34,43 @@ import BuildIcon from '@material-ui/icons/Build'; import { rootRouteRef } from '../routes'; /** @alpha */ -export const devToolsApi = createApiExtension({ - factory: createApiFactory({ - api: devToolsApiRef, - deps: { - discoveryApi: discoveryApiRef, - fetchApi: fetchApiRef, - }, - factory: ({ discoveryApi, fetchApi }) => - new DevToolsClient({ discoveryApi, fetchApi }), - }), +export const devToolsApi = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: devToolsApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ discoveryApi, fetchApi }) => + new DevToolsClient({ discoveryApi, fetchApi }), + }), + }, }); /** @alpha */ -export const devToolsPage = createPageExtension({ - defaultPath: '/devtools', - routeRef: convertLegacyRouteRef(rootRouteRef), - loader: () => - import('../components/DevToolsPage').then(m => - compatWrapper(), - ), +export const devToolsPage = PageBlueprint.make({ + params: { + defaultPath: '/devtools', + routeRef: convertLegacyRouteRef(rootRouteRef), + loader: () => + import('../components/DevToolsPage').then(m => + compatWrapper(), + ), + }, }); /** @alpha */ -export const devToolsNavItem = createNavItemExtension({ - title: 'DevTools', - routeRef: convertLegacyRouteRef(rootRouteRef), - icon: BuildIcon, +export const devToolsNavItem = NavItemBlueprint.make({ + params: { + title: 'DevTools', + routeRef: convertLegacyRouteRef(rootRouteRef), + icon: BuildIcon, + }, }); /** @alpha */ -export default createPlugin({ +export default createFrontendPlugin({ id: 'devtools', routes: { root: convertLegacyRouteRef(rootRouteRef), diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 677cdddcfc..64db006eea 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,94 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.4.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. + +### 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-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9 + +## 0.3.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/backend-tasks@0.5.28-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report-alpha.md b/plugins/events-backend-module-aws-sqs/api-report-alpha.md index ee14cc5b5b..1e8ae5c690 100644 --- a/plugins/events-backend-module-aws-sqs/api-report-alpha.md +++ b/plugins/events-backend-module-aws-sqs/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const eventsModuleAwsSqsConsumingEventPublisher: BackendFeatureCompat; +const eventsModuleAwsSqsConsumingEventPublisher: BackendFeature; export default eventsModuleAwsSqsConsumingEventPublisher; // (No @packageDocumentation comment for this package) diff --git a/plugins/events-backend-module-aws-sqs/api-report.md b/plugins/events-backend-module-aws-sqs/api-report.md index ff863e4d8d..ac806bf01b 100644 --- a/plugins/events-backend-module-aws-sqs/api-report.md +++ b/plugins/events-backend-module-aws-sqs/api-report.md @@ -6,7 +6,7 @@ import { Config } from '@backstage/config'; import { EventsService } from '@backstage/plugin-events-node'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; // @public export class AwsSqsConsumingEventPublisher { @@ -15,7 +15,7 @@ export class AwsSqsConsumingEventPublisher { config: Config; events: EventsService; logger: LoggerService; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; }): AwsSqsConsumingEventPublisher[]; // (undocumented) start(): Promise; diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index 9cba8e76e3..89d8c17c85 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.4.0", "backstage": { "role": "backend-plugin-module", "pluginId": "events", @@ -50,7 +50,6 @@ "@aws-sdk/client-sqs": "^3.350.0", "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-events-node": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts index db4f4ffa32..6b1f9ec54c 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.test.ts @@ -19,7 +19,7 @@ import { ReceiveMessageCommand, SQSClient, } from '@aws-sdk/client-sqs'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { TestEventsService } from '@backstage/plugin-events-backend-test-utils'; import { mockClient } from 'aws-sdk-client-mock'; @@ -56,7 +56,7 @@ describe('AwsSqsConsumingEventPublisher', () => { const events = new TestEventsService(); const scheduler = { scheduleTask: jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const publishers = AwsSqsConsumingEventPublisher.fromConfig({ config, @@ -90,7 +90,7 @@ describe('AwsSqsConsumingEventPublisher', () => { const events = new TestEventsService(); const scheduler = { scheduleTask: jest.fn(), - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; const publishers = AwsSqsConsumingEventPublisher.fromConfig({ config, @@ -141,7 +141,7 @@ describe('AwsSqsConsumingEventPublisher', () => { scheduleTask: (spec: { fn: () => Promise }) => { taskFn = spec.fn; }, - } as unknown as PluginTaskScheduler; + } as unknown as SchedulerService; // on the first attempt, we will return 1 message and 0 messages afterwards const sqsMock = mockClient(SQSClient); 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..c7628ffb20 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/AwsSqsConsumingEventPublisher.ts @@ -22,7 +22,7 @@ import { SQSClient, } from '@aws-sdk/client-sqs'; import { LoggerService } from '@backstage/backend-plugin-api'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { EventsService } from '@backstage/plugin-events-node'; import { AwsSqsEventSourceConfig, readConfig } from './config'; @@ -46,7 +46,7 @@ export class AwsSqsConsumingEventPublisher { config: Config; events: EventsService; logger: LoggerService; - scheduler: PluginTaskScheduler; + scheduler: SchedulerService; }): AwsSqsConsumingEventPublisher[] { return readConfig(env.config).map( config => @@ -62,7 +62,7 @@ export class AwsSqsConsumingEventPublisher { private constructor( private readonly logger: LoggerService, private readonly events: EventsService, - private readonly scheduler: PluginTaskScheduler, + private readonly scheduler: SchedulerService, config: AwsSqsEventSourceConfig, ) { this.topic = config.topic; @@ -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-aws-sqs/src/publisher/config.ts b/plugins/events-backend-module-aws-sqs/src/publisher/config.ts index eeb650ff6c..7bad1e38d1 100644 --- a/plugins/events-backend-module-aws-sqs/src/publisher/config.ts +++ b/plugins/events-backend-module-aws-sqs/src/publisher/config.ts @@ -34,7 +34,7 @@ export interface AwsSqsEventSourceConfig { endpoint?: string; } -// TODO(pjungermann): validation could be improved similar to `convertToHumanDuration` at @backstage/backend-tasks +// TODO(pjungermann): validation could be improved similar to `convertToHumanDuration` at @backstage/backend-plugin-api function readOptionalHumanDuration( config: Config, key: string, diff --git a/plugins/events-backend-module-aws-sqs/src/service/eventsModuleAwsSqsConsumingEventPublisher.test.ts b/plugins/events-backend-module-aws-sqs/src/service/eventsModuleAwsSqsConsumingEventPublisher.test.ts index a25d2ef947..13bc3d2272 100644 --- a/plugins/events-backend-module-aws-sqs/src/service/eventsModuleAwsSqsConsumingEventPublisher.test.ts +++ b/plugins/events-backend-module-aws-sqs/src/service/eventsModuleAwsSqsConsumingEventPublisher.test.ts @@ -35,7 +35,7 @@ describe('eventsModuleAwsSqsConsumingEventPublisher', () => { await startTestBackend({ features: [ - eventsServiceFactory(), + eventsServiceFactory, eventsModuleAwsSqsConsumingEventPublisher, mockServices.rootConfig.factory({ data: { diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index cea3fab2b6..efe13729b2 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-events-node@0.3.9 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report-alpha.md b/plugins/events-backend-module-azure/api-report-alpha.md index 03e807242f..861ff95bcd 100644 --- a/plugins/events-backend-module-azure/api-report-alpha.md +++ b/plugins/events-backend-module-azure/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const eventsModuleAzureDevOpsEventRouter: BackendFeatureCompat; +const eventsModuleAzureDevOpsEventRouter: BackendFeature; export default eventsModuleAzureDevOpsEventRouter; export { eventsModuleAzureDevOpsEventRouter }; diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 74573116d7..7a49aff69c 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", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-azure/src/service/eventsModuleAzureDevOpsEventRouter.test.ts b/plugins/events-backend-module-azure/src/service/eventsModuleAzureDevOpsEventRouter.test.ts index b9271ece10..9ca2ed819d 100644 --- a/plugins/events-backend-module-azure/src/service/eventsModuleAzureDevOpsEventRouter.test.ts +++ b/plugins/events-backend-module-azure/src/service/eventsModuleAzureDevOpsEventRouter.test.ts @@ -32,7 +32,7 @@ describe('eventsModuleAzureDevOpsEventRouter', () => { }); await startTestBackend({ - features: [eventsServiceFactory(), eventsModuleAzureDevOpsEventRouter], + features: [eventsServiceFactory, eventsModuleAzureDevOpsEventRouter], }); expect(events.subscribed).toHaveLength(1); diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index eac1919f41..f84238c5b9 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-events-node@0.3.9 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report-alpha.md b/plugins/events-backend-module-bitbucket-cloud/api-report-alpha.md index 71c106ef01..6ee4fab225 100644 --- a/plugins/events-backend-module-bitbucket-cloud/api-report-alpha.md +++ b/plugins/events-backend-module-bitbucket-cloud/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const eventsModuleBitbucketCloudEventRouter: BackendFeatureCompat; +const eventsModuleBitbucketCloudEventRouter: BackendFeature; export default eventsModuleBitbucketCloudEventRouter; export { eventsModuleBitbucketCloudEventRouter }; diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 471ecb4ae2..959639fdd9 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", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-cloud/src/service/eventsModuleBitbucketCloudEventRouter.test.ts b/plugins/events-backend-module-bitbucket-cloud/src/service/eventsModuleBitbucketCloudEventRouter.test.ts index 799a71de8f..aa15120418 100644 --- a/plugins/events-backend-module-bitbucket-cloud/src/service/eventsModuleBitbucketCloudEventRouter.test.ts +++ b/plugins/events-backend-module-bitbucket-cloud/src/service/eventsModuleBitbucketCloudEventRouter.test.ts @@ -32,7 +32,7 @@ describe('eventsModuleBitbucketCloudEventRouter', () => { }); await startTestBackend({ - features: [eventsServiceFactory(), eventsModuleBitbucketCloudEventRouter], + features: [eventsServiceFactory, eventsModuleBitbucketCloudEventRouter], }); expect(events.subscribed).toHaveLength(1); diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index cb5ac5662f..88c2ac91d8 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-events-node@0.3.9 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report-alpha.md b/plugins/events-backend-module-gerrit/api-report-alpha.md index 8922f317e9..83b2fdff98 100644 --- a/plugins/events-backend-module-gerrit/api-report-alpha.md +++ b/plugins/events-backend-module-gerrit/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const eventsModuleGerritEventRouter: BackendFeatureCompat; +const eventsModuleGerritEventRouter: BackendFeature; export default eventsModuleGerritEventRouter; export { eventsModuleGerritEventRouter }; diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index dbff36987a..10abf94085 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", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gerrit/src/service/eventsModuleGerritEventRouter.test.ts b/plugins/events-backend-module-gerrit/src/service/eventsModuleGerritEventRouter.test.ts index ca661215de..cf46a9a7ee 100644 --- a/plugins/events-backend-module-gerrit/src/service/eventsModuleGerritEventRouter.test.ts +++ b/plugins/events-backend-module-gerrit/src/service/eventsModuleGerritEventRouter.test.ts @@ -32,7 +32,7 @@ describe('eventsModuleGerritEventRouter', () => { }); await startTestBackend({ - features: [eventsServiceFactory(), eventsModuleGerritEventRouter], + features: [eventsServiceFactory, eventsModuleGerritEventRouter], }); expect(events.subscribed).toHaveLength(1); diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index e2511f76ba..f08743d3b3 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/plugin-events-backend-module-github +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report-alpha.md b/plugins/events-backend-module-github/api-report-alpha.md index 50d899f71d..63e51f1dae 100644 --- a/plugins/events-backend-module-github/api-report-alpha.md +++ b/plugins/events-backend-module-github/api-report-alpha.md @@ -3,13 +3,13 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -export const eventsModuleGithubEventRouter: BackendFeatureCompat; +export const eventsModuleGithubEventRouter: BackendFeature; // @alpha -export const eventsModuleGithubWebhook: BackendFeatureCompat; +export const eventsModuleGithubWebhook: BackendFeature; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 126da9f29c..16238b7b93 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", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-github/src/service/eventsModuleGithubEventRouter.test.ts b/plugins/events-backend-module-github/src/service/eventsModuleGithubEventRouter.test.ts index ba0c71f158..d69a128c4c 100644 --- a/plugins/events-backend-module-github/src/service/eventsModuleGithubEventRouter.test.ts +++ b/plugins/events-backend-module-github/src/service/eventsModuleGithubEventRouter.test.ts @@ -32,7 +32,7 @@ describe('eventsModuleGithubEventRouter', () => { }); await startTestBackend({ - features: [eventsServiceFactory(), eventsModuleGithubEventRouter], + features: [eventsServiceFactory, eventsModuleGithubEventRouter], }); expect(events.subscribed).toHaveLength(1); diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md index 89d2f47c10..85b62d9adf 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + +## 0.2.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + +## 0.2.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report-alpha.md b/plugins/events-backend-module-gitlab/api-report-alpha.md index 4e81eb4269..1406b3ef64 100644 --- a/plugins/events-backend-module-gitlab/api-report-alpha.md +++ b/plugins/events-backend-module-gitlab/api-report-alpha.md @@ -3,13 +3,13 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -export const eventsModuleGitlabEventRouter: BackendFeatureCompat; +export const eventsModuleGitlabEventRouter: BackendFeature; // @alpha -export const eventsModuleGitlabWebhook: BackendFeatureCompat; +export const eventsModuleGitlabWebhook: BackendFeature; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 45a9637a24..f0183783ec 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", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gitlab/src/service/eventsModuleGitlabEventRouter.test.ts b/plugins/events-backend-module-gitlab/src/service/eventsModuleGitlabEventRouter.test.ts index 44e90d1b7e..48ed7ef4d3 100644 --- a/plugins/events-backend-module-gitlab/src/service/eventsModuleGitlabEventRouter.test.ts +++ b/plugins/events-backend-module-gitlab/src/service/eventsModuleGitlabEventRouter.test.ts @@ -32,7 +32,7 @@ describe('eventsModuleGitlabEventRouter', () => { }); await startTestBackend({ - features: [eventsServiceFactory(), eventsModuleGitlabEventRouter], + features: [eventsServiceFactory, eventsModuleGitlabEventRouter], }); expect(events.subscribed).toHaveLength(1); diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index e0f42a6f24..3047079949 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.33 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.9 + +## 0.1.33-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.1.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.9-next.2 + +## 0.1.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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..0fb0092bf0 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", "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..763699805b 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,75 @@ # @backstage/plugin-events-backend +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9 + +## 0.3.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.3 + +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/config@1.2.0 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.9-next.1 + +## 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/api-report-alpha.md b/plugins/events-backend/api-report-alpha.md index a3cc7148ce..ef881006e4 100644 --- a/plugins/events-backend/api-report-alpha.md +++ b/plugins/events-backend/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const eventsPlugin: BackendFeatureCompat; +const eventsPlugin: BackendFeature; export default eventsPlugin; // (No @packageDocumentation comment for this package) diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index b176044bf9..a0b2b17ba1 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", "backstage": { "role": "backend-plugin", "pluginId": "events", diff --git a/plugins/events-backend/src/service/EventsPlugin.test.ts b/plugins/events-backend/src/service/EventsPlugin.test.ts index bb923bbed6..48ce1090fb 100644 --- a/plugins/events-backend/src/service/EventsPlugin.test.ts +++ b/plugins/events-backend/src/service/EventsPlugin.test.ts @@ -56,7 +56,7 @@ describe('eventsPlugin', () => { const { server } = await startTestBackend({ extensionPoints: [], features: [ - eventsServiceFactory(), + eventsServiceFactory, eventsPlugin, testModule, mockServices.logger.factory(), diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 1c5025b788..5762c94e9e 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-events-node +## 0.3.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + +## 0.3.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + +## 0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + +## 0.3.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + +## 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..d8a2281faa 100644 --- a/plugins/events-node/api-report.md +++ b/plugins/events-node/api-report.md @@ -63,13 +63,14 @@ export interface EventsService { export type EventsServiceEventHandler = (params: EventParams) => Promise; // @public (undocumented) -export const eventsServiceFactory: () => ServiceFactory< +export const eventsServiceFactory: ServiceFactory< EventsService, - 'plugin' + 'plugin', + 'singleton' >; // @public -export const eventsServiceRef: ServiceRef; +export const eventsServiceRef: ServiceRef; // @public (undocumented) export type EventsServiceSubscribeOptions = { diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 58790c4e02..2b51efd6af 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", "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..7c555c4754 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,75 @@ # @internal/plugin-todo-list-backend +## 1.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/errors@1.2.4 + +## 1.0.30-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + +## 1.0.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/errors@1.2.4 + +## 1.0.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 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/api-report.md b/plugins/example-todo-list-backend/api-report.md index 69c6bdf239..8fa39f0cec 100644 --- a/plugins/example-todo-list-backend/api-report.md +++ b/plugins/example-todo-list-backend/api-report.md @@ -3,22 +3,22 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import express from 'express'; -import { IdentityApi } from '@backstage/plugin-auth-node'; +import { HttpAuthService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; // @public export function createRouter(options: RouterOptions): Promise; // @public -const exampleTodoListPlugin: BackendFeatureCompat; +const exampleTodoListPlugin: BackendFeature; export default exampleTodoListPlugin; // @public export interface RouterOptions { // (undocumented) - identity: IdentityApi; + httpAuth: HttpAuthService; // (undocumented) logger: LoggerService; } diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index ec37ce2843..8c378a814e 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", "backstage": { "role": "backend-plugin", "pluginId": "todo-list", diff --git a/plugins/example-todo-list-backend/src/plugin.ts b/plugins/example-todo-list-backend/src/plugin.ts index ccff192d4c..1c7bba929a 100644 --- a/plugins/example-todo-list-backend/src/plugin.ts +++ b/plugins/example-todo-list-backend/src/plugin.ts @@ -30,14 +30,14 @@ export const exampleTodoListPlugin = createBackendPlugin({ register(env) { env.registerInit({ deps: { - identity: coreServices.identity, + httpAuth: coreServices.httpAuth, logger: coreServices.logger, httpRouter: coreServices.httpRouter, }, - async init({ identity, logger, httpRouter }) { + async init({ httpAuth, logger, httpRouter }) { httpRouter.use( await createRouter({ - identity, + httpAuth, logger, }), ); diff --git a/plugins/example-todo-list-backend/src/service/router.test.ts b/plugins/example-todo-list-backend/src/service/router.test.ts index e9abb5a76c..50bacac203 100644 --- a/plugins/example-todo-list-backend/src/service/router.test.ts +++ b/plugins/example-todo-list-backend/src/service/router.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; import express from 'express'; import request from 'supertest'; @@ -27,7 +26,7 @@ describe('createRouter', () => { beforeAll(async () => { const router = await createRouter({ logger: mockServices.logger.mock(), - identity: {} as DefaultIdentityClient, + httpAuth: mockServices.httpAuth.mock(), }); app = express().use(router); }); diff --git a/plugins/example-todo-list-backend/src/service/router.ts b/plugins/example-todo-list-backend/src/service/router.ts index 09b8669507..5f2d9fd37c 100644 --- a/plugins/example-todo-list-backend/src/service/router.ts +++ b/plugins/example-todo-list-backend/src/service/router.ts @@ -19,8 +19,7 @@ import express from 'express'; import Router from 'express-promise-router'; import { add, getAll, update } from './todos'; import { InputError } from '@backstage/errors'; -import { IdentityApi } from '@backstage/plugin-auth-node'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { HttpAuthService, LoggerService } from '@backstage/backend-plugin-api'; /** * Dependencies of the todo-list router @@ -29,7 +28,7 @@ import { LoggerService } from '@backstage/backend-plugin-api'; */ export interface RouterOptions { logger: LoggerService; - identity: IdentityApi; + httpAuth: HttpAuthService; } /** @@ -44,7 +43,7 @@ export interface RouterOptions { export async function createRouter( options: RouterOptions, ): Promise { - const { logger, identity } = options; + const { logger, httpAuth } = options; const router = Router(); router.use(express.json()); @@ -59,16 +58,16 @@ export async function createRouter( }); router.post('/todos', async (req, res) => { - let author: string | undefined = undefined; - - const user = await identity.getIdentity({ request: req }); - author = user?.identity.userEntityRef; + const credentials = await httpAuth.credentials(req, { allow: ['user'] }); if (!isTodoCreateRequest(req.body)) { throw new InputError('Invalid payload'); } - const todo = add({ title: req.body.title, author }); + const todo = add({ + title: req.body.title, + author: credentials.principal.userEntityRef, + }); res.json(todo); }); diff --git a/plugins/example-todo-list-common/CHANGELOG.md b/plugins/example-todo-list-common/CHANGELOG.md index 3761ead6a3..4b1ba3f9ce 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,33 @@ # @internal/plugin-todo-list-common +## 1.0.21 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + +## 1.0.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + +## 1.0.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + +## 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..9bb25b7054 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.21", "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..c5ccfbcd54 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,36 @@ # @internal/plugin-todo-list +## 1.0.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + +## 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..eb13f52c6b 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", "backstage": { "role": "frontend-plugin", "pluginId": "todo-list", diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index 62f3d98623..87ff61daf0 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/plugin-home-react +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/core-plugin-api@1.9.3 + +## 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/catalog-info.yaml b/plugins/home-react/catalog-info.yaml index 28109b9ddc..7022206fb9 100644 --- a/plugins/home-react/catalog-info.yaml +++ b/plugins/home-react/catalog-info.yaml @@ -9,4 +9,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: discoverability-maintainers + owner: home-maintainers diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 9c7667b4d0..e720402766 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", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index b6ff8061f2..b2435eb5db 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,148 @@ # @backstage/plugin-home +## 0.7.9 + +### Patch Changes + +- 31bfc44: Updated alpha definitions of extension data references. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- 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.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/core-app-api@1.14.2 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/theme@0.5.6 + - @backstage/plugin-home-react@0.1.16 + +## 0.7.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-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.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-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.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.14.2-next.0 + - @backstage/core-components@0.14.10-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.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..e32412168b 100644 --- a/plugins/home/api-report-alpha.md +++ b/plugins/home/api-report-alpha.md @@ -3,15 +3,81 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { BackstagePlugin } from '@backstage/frontend-plugin-api'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; +import { RouteRef } from '@backstage/frontend-plugin-api'; // @alpha (undocumented) -const _default: BackstagePlugin<{}, {}>; +const _default: BackstagePlugin< + {}, + {}, + { + 'page:home': ExtensionDefinition< + { + [x: string]: any; + } & { + path: string | undefined; + }, + { + [x: string]: any; + } & { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + { + props: ExtensionInput< + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'title', + { + optional: true; + } + >, + { + singleton: true; + optional: true; + } + >; + }, + { + kind: 'page'; + namespace: undefined; + name: undefined; + } + >; + } +>; export default _default; // @alpha (undocumented) -export const titleExtensionDataRef: ConfigurableExtensionDataRef; +export const titleExtensionDataRef: ConfigurableExtensionDataRef< + string, + 'title', + {} +>; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/home/catalog-info.yaml b/plugins/home/catalog-info.yaml index 1b23d4b83b..eefc114313 100644 --- a/plugins/home/catalog-info.yaml +++ b/plugins/home/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend-plugin - owner: discoverability-maintainers + owner: home-maintainers diff --git a/plugins/home/package.json b/plugins/home/package.json index 2bc646b417..97d7597b99 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.9", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin", diff --git a/plugins/home/src/alpha.tsx b/plugins/home/src/alpha.tsx index e9ff26c9e1..ce96b18943 100644 --- a/plugins/home/src/alpha.tsx +++ b/plugins/home/src/alpha.tsx @@ -20,8 +20,8 @@ import { coreExtensionData, createExtensionDataRef, createExtensionInput, - createPageExtension, - createPlugin, + PageBlueprint, + createFrontendPlugin, createRouteRef, } from '@backstage/frontend-plugin-api'; import { compatWrapper } from '@backstage/core-compat-api'; @@ -31,39 +31,44 @@ const rootRouteRef = createRouteRef(); /** * @alpha */ -export const titleExtensionDataRef = createExtensionDataRef('title'); +export const titleExtensionDataRef = createExtensionDataRef().with({ + id: 'title', +}); -const homePage = createPageExtension({ - defaultPath: '/home', - routeRef: rootRouteRef, +const homePage = PageBlueprint.makeWithOverrides({ inputs: { props: createExtensionInput( - { - children: coreExtensionData.reactElement.optional(), - title: titleExtensionDataRef.optional(), - }, - + [ + coreExtensionData.reactElement.optional(), + titleExtensionDataRef.optional(), + ], { singleton: true, optional: true, }, ), }, - loader: ({ inputs }) => - import('./components/').then(m => - compatWrapper( - , - ), - ), + factory: (originalFactory, { inputs }) => { + return originalFactory({ + defaultPath: '/home', + routeRef: rootRouteRef, + loader: () => + import('./components/').then(m => + compatWrapper( + , + ), + ), + }); + }, }); /** * @alpha */ -export default createPlugin({ +export default createFrontendPlugin({ id: 'home', extensions: [homePage], }); 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/home/src/plugin.test.ts b/plugins/home/src/plugin.test.ts index 1526bd0c61..904481c98e 100644 --- a/plugins/home/src/plugin.test.ts +++ b/plugins/home/src/plugin.test.ts @@ -24,7 +24,7 @@ describe('home', () => { // Temporarily ensure we are installing a working version of the react-grid-layout library // For more details, see: https://github.com/react-grid-layout/react-grid-layout/issues/1959 - // TODO(@backstage/discoverability-maintainers): Delete this once the sub-dependency issue has been resolved. + // TODO(@backstage/home-maintainers): Delete this once the sub-dependency issue has been resolved. it('should pin react-grid-layout version to 1.3.4', async () => { expect(packageJson.dependencies['react-grid-layout']).toBe('1.3.4'); }); diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index ea2049d73c..e944ce6255 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,152 @@ # @backstage/plugin-kubernetes-backend +## 0.18.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b63d378: Update internal imports +- 8c1aa06: Add `kubernetes.clusterLocatorMethods[].clusters[].customResources` to the configuration schema. + This was already documented and supported by the plugin. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-kubernetes-node@0.1.17 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @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-common@0.8.2 + +## 0.18.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.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.5.0-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-kubernetes-common@0.8.2-next.2 + - @backstage/plugin-kubernetes-node@0.1.17-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## 0.18.4-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 8c1aa06: Add `kubernetes.clusterLocatorMethods[].clusters[].customResources` to the configuration schema. + This was already documented and supported by the plugin. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-kubernetes-node@0.1.17-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/integration-aws-node@0.1.12 + - @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 + +## 0.18.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-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.1 + - @backstage/plugin-kubernetes-node@0.1.17-next.1 + +## 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/api-report-alpha.md b/plugins/kubernetes-backend/api-report-alpha.md index c3a86779e9..f6da905f30 100644 --- a/plugins/kubernetes-backend/api-report-alpha.md +++ b/plugins/kubernetes-backend/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const kubernetesPlugin: BackendFeatureCompat; +const kubernetesPlugin: BackendFeature; export default kubernetesPlugin; // (No @packageDocumentation comment for this package) diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index c31af334d7..2711085b81 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -28,8 +28,8 @@ import { LoggerService } from '@backstage/backend-plugin-api'; import { ObjectToFetch as ObjectToFetch_2 } from '@backstage/plugin-kubernetes-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PermissionsService } from '@backstage/backend-plugin-api'; -import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { RequestHandler } from 'http-proxy-middleware'; +import { RootConfigService } from '@backstage/backend-plugin-api'; import { TokenCredential } from '@azure/identity'; // @public (undocumented) @@ -153,7 +153,7 @@ export const HEADER_KUBERNETES_AUTH: string; // @public export const HEADER_KUBERNETES_CLUSTER: string; -// @public (undocumented) +// @public @deprecated (undocumented) export class KubernetesBuilder { constructor(env: KubernetesEnvironment); // (undocumented) @@ -268,7 +268,7 @@ export class KubernetesBuilder { setServiceLocator(serviceLocator?: KubernetesServiceLocator_2): this; } -// @public +// @public @deprecated export type KubernetesBuilderReturn = Promise<{ router: express.Router; clusterSupplier: KubernetesClustersSupplier_2; @@ -289,7 +289,7 @@ export type KubernetesClustersSupplier = // @public @deprecated (undocumented) export type KubernetesCredential = k8sAuthTypes.KubernetesCredential; -// @public (undocumented) +// @public @deprecated (undocumented) export interface KubernetesEnvironment { // (undocumented) auth?: AuthService; @@ -380,16 +380,16 @@ export class OidcStrategy implements AuthenticationStrategy_2 { validateCluster(authMetadata: AuthMetadata_2): Error[]; } -// @public (undocumented) +// @public @deprecated (undocumented) export interface RouterOptions { // (undocumented) catalogApi: CatalogApi; // (undocumented) clusterSupplier?: KubernetesClustersSupplier; // (undocumented) - config: Config; + config: RootConfigService; // (undocumented) - discovery: PluginEndpointDiscovery; + discovery: DiscoveryService; // (undocumented) logger: Logger; // (undocumented) diff --git a/plugins/kubernetes-backend/config.d.ts b/plugins/kubernetes-backend/config.d.ts index 777a3392db..d6c5f1d32b 100644 --- a/plugins/kubernetes-backend/config.d.ts +++ b/plugins/kubernetes-backend/config.d.ts @@ -68,6 +68,11 @@ export interface Config { caData?: string; /** @visibility secret */ caFile?: string; + customResources?: Array<{ + group: string; + apiVersion: string; + plural: string; + }>; }>; } | { diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index d0de031d9d..978171e2ea 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", "description": "A Backstage backend plugin that integrates towards Kubernetes", "backstage": { "role": "backend-plugin", @@ -92,13 +92,14 @@ "lodash": "^4.17.21", "luxon": "^3.0.0", "morgan": "^1.10.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "stream-buffers": "^3.0.2", "winston": "^3.2.1", "yn": "^4.0.0" }, "devDependencies": { "@backstage/backend-app-api": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-permission-backend": "workspace:^", diff --git a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts index cfa89a6bfd..5264e762b6 100644 --- a/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts +++ b/plugins/kubernetes-backend/src/routes/resourceRoutes.test.ts @@ -20,10 +20,10 @@ import { mockServices, startTestBackend, } from '@backstage/backend-test-utils'; -import { ExtendedHttpServer } from '@backstage/backend-app-api'; import { kubernetesObjectsProviderExtensionPoint } from '@backstage/plugin-kubernetes-node'; import { createBackendModule } from '@backstage/backend-plugin-api'; import { Entity } from '@backstage/catalog-model'; +import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter'; describe('resourcesRoutes', () => { let app: ExtendedHttpServer; diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index 1ac653bb58..60683a74f3 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'; @@ -56,7 +56,7 @@ import { kubernetesFetcherExtensionPoint, kubernetesServiceLocatorExtensionPoint, } from '@backstage/plugin-kubernetes-node'; -import { ExtendedHttpServer } from '@backstage/backend-app-api'; +import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter'; describe('API integration tests', () => { let app: ExtendedHttpServer; @@ -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/KubernetesBuilder.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts index 8f006cdc86..62577c4fb0 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts @@ -74,7 +74,7 @@ import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; import { KubernetesProxy } from './KubernetesProxy'; /** - * + * @deprecated Please migrate to the new backend system as this will be removed in the future. * @public */ export interface KubernetesEnvironment { @@ -89,7 +89,7 @@ export interface KubernetesEnvironment { /** * The return type of the `KubernetesBuilder.build` method - * + * @deprecated Please migrate to the new backend system as this will be removed in the future. * @public */ export type KubernetesBuilderReturn = Promise<{ @@ -104,9 +104,9 @@ export type KubernetesBuilderReturn = Promise<{ }>; /** - * + * @deprecated Please migrate to the new backend system as this will be removed in the future. * @public - */ + * */ export class KubernetesBuilder { private clusterSupplier?: KubernetesClustersSupplier; private defaultClusterRefreshInterval: Duration = Duration.fromObject({ 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-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index f7e58f535c..d1b54c5927 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -14,25 +14,27 @@ * limitations under the License. */ -import { Config } from '@backstage/config'; import { Logger } from 'winston'; import { KubernetesClustersSupplier } from '../types/types'; import express from 'express'; import { KubernetesBuilder } from './KubernetesBuilder'; -import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import { + DiscoveryService, + RootConfigService, +} from '@backstage/backend-plugin-api'; /** - * + * @deprecated Please migrate to the new backend system as this will be removed in the future. * @public */ export interface RouterOptions { logger: Logger; - config: Config; + config: RootConfigService; catalogApi: CatalogApi; clusterSupplier?: KubernetesClustersSupplier; - discovery: PluginEndpointDiscovery; + discovery: DiscoveryService; permissions: PermissionEvaluator; } @@ -40,7 +42,7 @@ export interface RouterOptions { * creates and configure a new router for handling the kubernetes backend APIs * @param options - specifies the options required by this plugin * @returns a new router - * @deprecated Please use the new KubernetesBuilder instead like this + * @deprecated Please migrate to the new backend system as this will be removed in the future. * ``` * import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend'; * const { router } = await KubernetesBuilder.createBuilder({ diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index cef2c059d1..a35b71719a 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,98 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.14 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-kubernetes-react@0.4.2 + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.0.14-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2-next.2 + - @backstage/plugin-kubernetes-react@0.4.2-next.3 + +## 0.0.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-react@0.4.2-next.2 + +## 0.0.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-react@0.4.2-next.1 + +## 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..c7eb6658b0 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", "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..6b3606335c 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-kubernetes-common +## 0.8.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + +## 0.8.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## 0.8.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## 0.8.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## 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..e902879f9f 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.2", "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..6d81f07df2 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,78 @@ # @backstage/plugin-kubernetes-node +## 0.1.17 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b63d378: Update internal imports +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.1.17-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.2 + +## 0.1.17-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## 0.1.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## 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..5f9c9efa55 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", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library", @@ -43,12 +43,13 @@ "@backstage/plugin-kubernetes-common": "workspace:^", "@backstage/types": "workspace:^", "@kubernetes/client-node": "^0.20.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "winston": "^3.2.1" }, "devDependencies": { "@backstage/backend-app-api": "workspace:^", "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-kubernetes-backend": "workspace:^", diff --git a/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts b/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts index fdfea030a5..65e48affc0 100644 --- a/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts +++ b/plugins/kubernetes-node/src/auth/PinnipedHelper.test.ts @@ -14,11 +14,10 @@ * limitations under the License. */ -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'; @@ -37,13 +36,14 @@ import { JsonObject } from '@backstage/types'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; import { loggerToWinstonLogger } from '@backstage/backend-common'; +import { ExtendedHttpServer } from '@backstage/backend-defaults/rootHttpRouter'; describe('Pinniped - tokenCredentialRequest', () => { let app: ExtendedHttpServer; 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..d76b454a4b 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,88 @@ # @backstage/plugin-kubernetes-react +## 0.4.2 + +### Patch Changes + +- 954a593: `Liveness Probe` added in ContainerCard Component of PodDrawer +- Updated dependencies + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.4.2-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @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-kubernetes-common@0.8.2-next.2 + +## 0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/catalog-model@1.5.0 + - @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.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes-common@0.8.2-next.0 + - @backstage/catalog-model@1.5.0 + - @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.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..936231da7c 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", "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-react/src/components/Pods/PodDrawer/ContainerCard.test.tsx b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.test.tsx index 6d64b8ad5b..3b131a617c 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.test.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.test.tsx @@ -40,6 +40,7 @@ describe('ContainerCard', () => { }, containerSpec: { readinessProbe: {}, + livenessProbe: {}, }, containerStatus: { name: 'some-name', diff --git a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx index cabda69b2c..1919bc112d 100644 --- a/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx +++ b/plugins/kubernetes-react/src/components/Pods/PodDrawer/ContainerCard.tsx @@ -35,20 +35,25 @@ const getContainerHealthChecks = ( containerSpec: IContainer, containerStatus: IContainerStatus, ): { [key: string]: boolean } => { - if (containerStatus.state?.terminated?.reason === 'Completed') { - return { - 'not waiting to start': containerStatus.state?.waiting === undefined, - 'no restarts': containerStatus.restartCount === 0, - }; - } - return { + const healthCheck = { 'not waiting to start': containerStatus.state?.waiting === undefined, - started: !!containerStatus.started, - ready: containerStatus.ready, 'no restarts': containerStatus.restartCount === 0, - 'readiness probe set': - containerSpec && containerSpec?.readinessProbe !== undefined, }; + if (containerStatus.state?.terminated?.reason === 'Completed') { + return healthCheck; + } + Object.assign( + healthCheck, + { started: !!containerStatus.started }, + { ready: containerStatus.ready }, + { 'readiness probe set': containerSpec?.readinessProbe !== undefined }, + ); + if (containerSpec && containerSpec?.livenessProbe !== undefined) { + Object.assign(healthCheck, { + 'liveness probe set': containerSpec.livenessProbe, + }); + } + return healthCheck; }; const getCurrentState = (containerStatus: IContainerStatus): string => { diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 5c8c4d6d4b..a2e47a6b1b 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,109 @@ # @backstage/plugin-kubernetes +## 0.11.13 + +### Patch Changes + +- e6c15cc: Adds support for Backstage's new frontend system, available via the `/alpha` sub-path export. +- fe1fbb2: Migrating usages of the deprecated `createExtension` `v1` format to the newer `v2` format, and old `create*Extension` extension creators to blueprints. +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/plugin-kubernetes-react@0.4.2 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2 + +## 0.11.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-common@0.8.2-next.2 + - @backstage/plugin-kubernetes-react@0.4.2-next.3 + +## 0.11.13-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-kubernetes-common@0.8.2-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-react@0.4.2-next.2 + +## 0.11.13-next.1 + +### Patch Changes + +- e6c15cc: Adds support for Backstage's new frontend system, available via the `/alpha` sub-path export. +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-kubernetes-common@0.8.2-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-kubernetes-react@0.4.2-next.1 + +## 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..380c1dffbd 100644 --- a/plugins/kubernetes/README.md +++ b/plugins/kubernetes/README.md @@ -26,8 +26,48 @@ 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`. +By default, the tab will only appear on entities that are Components or Resources. You can override +that behavior by providing a config block to the extension, like so: + +```yaml +app: + extensions: + - entity-content:kubernetes/kubernetes: + config: + filter: kind:component,api,resource,system +``` diff --git a/plugins/kubernetes/api-report-alpha.md b/plugins/kubernetes/api-report-alpha.md new file mode 100644 index 0000000000..15f7efaf1b --- /dev/null +++ b/plugins/kubernetes/api-report-alpha.md @@ -0,0 +1,127 @@ +## 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 { AnyApiFactory } from '@backstage/frontend-plugin-api'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { JSX as JSX_2 } from 'react'; +import { RouteRef } from '@backstage/frontend-plugin-api'; + +// @public (undocumented) +const _default: BackstagePlugin< + { + kubernetes: RouteRef; + }, + {}, + { + 'page:kubernetes': ExtensionDefinition< + { + path: string | undefined; + }, + { + path?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >, + {}, + { + kind: 'page'; + namespace: undefined; + name: undefined; + } + >; + 'entity-content:kubernetes/kubernetes': ExtensionDefinition< + { + path: string | undefined; + title: string | undefined; + filter: string | undefined; + }, + { + filter?: string | undefined; + title?: string | undefined; + path?: string | undefined; + }, + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-content'; + namespace: undefined; + name: 'kubernetes'; + } + >; + 'api:kubernetes/proxy': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + { + kind: 'api'; + namespace: undefined; + name: 'proxy'; + } + >; + 'api:kubernetes/auth-providers': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + { + kind: 'api'; + namespace: undefined; + name: 'auth-providers'; + } + >; + 'api:kubernetes/cluster-link-formatter': ExtensionDefinition< + {}, + {}, + ConfigurableExtensionDataRef, + {}, + { + kind: 'api'; + namespace: undefined; + name: 'cluster-link-formatter'; + } + >; + } +>; +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..c982e67d04 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", "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/catalog/src/alpha.ts b/plugins/kubernetes/src/alpha.ts similarity index 88% rename from plugins/catalog/src/alpha.ts rename to plugins/kubernetes/src/alpha.ts index b78a5e6225..e80f131817 100644 --- a/plugins/catalog/src/alpha.ts +++ b/plugins/kubernetes/src/alpha.ts @@ -16,5 +16,3 @@ export * from './alpha/index'; export { default } from './alpha/index'; -export { catalogTranslationRef } from './translation'; -export * from './translation'; diff --git a/packages/backend-test-utils/src/deprecated.ts b/plugins/kubernetes/src/alpha/KubernetesContentPage.tsx similarity index 69% rename from packages/backend-test-utils/src/deprecated.ts rename to plugins/kubernetes/src/alpha/KubernetesContentPage.tsx index fad7523367..948a492e63 100644 --- a/packages/backend-test-utils/src/deprecated.ts +++ b/plugins/kubernetes/src/alpha/KubernetesContentPage.tsx @@ -14,10 +14,11 @@ * limitations under the License. */ -import { CreateMockDirectoryOptions } from './filesystem'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { KubernetesContent } from '../KubernetesContent'; +import React from 'react'; -/** - * @public - * @deprecated Use `CreateMockDirectoryOptions` from `@backstage/backend-test-utils` instead. - */ -export type MockDirectoryOptions = CreateMockDirectoryOptions; +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..082a2da6c7 --- /dev/null +++ b/plugins/kubernetes/src/alpha/apis.tsx @@ -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 { + ApiBlueprint, + 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 = ApiBlueprint.make({ + params: { + factory: createApiFactory({ + api: kubernetesApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + kubernetesAuthProvidersApi: kubernetesAuthProvidersApiRef, + }, + factory: ({ discoveryApi, fetchApi, kubernetesAuthProvidersApi }) => + new KubernetesBackendClient({ + discoveryApi, + fetchApi, + kubernetesAuthProvidersApi, + }), + }), + }, +}); + +export const kubernetesProxyApi = ApiBlueprint.make({ + name: 'proxy', + params: { + factory: createApiFactory({ + api: kubernetesProxyApiRef, + deps: { + kubernetesApi: kubernetesApiRef, + }, + factory: ({ kubernetesApi }) => + new KubernetesProxyClient({ + kubernetesApi, + }), + }), + }, +}); + +export const kubernetesAuthProvidersApi = ApiBlueprint.make({ + name: 'auth-providers', + params: { + 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 = ApiBlueprint.make({ + name: 'cluster-link-formatter', + params: { + 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..2703fcdfc2 --- /dev/null +++ b/plugins/kubernetes/src/alpha/entityContents.tsx @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { compatWrapper } from '@backstage/core-compat-api'; +import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; + +export const entityKubernetesContent = EntityContentBlueprint.make({ + name: 'kubernetes', + params: { + defaultPath: '/kubernetes', + defaultTitle: 'Kubernetes', + filter: 'kind:component,resource', + loader: () => + import('./KubernetesContentPage').then(m => + compatWrapper(), + ), + }, +}); diff --git a/packages/frontend-app-api/src/tree/index.ts b/plugins/kubernetes/src/alpha/index.ts similarity index 92% rename from packages/frontend-app-api/src/tree/index.ts rename to plugins/kubernetes/src/alpha/index.ts index 4b5ad5e867..2f137f09ee 100644 --- a/packages/frontend-app-api/src/tree/index.ts +++ b/plugins/kubernetes/src/alpha/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { createAppTree } from './createAppTree'; +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..a58ffec94f --- /dev/null +++ b/plugins/kubernetes/src/alpha/pages.tsx @@ -0,0 +1,35 @@ +/* + * 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 { PageBlueprint } from '@backstage/frontend-plugin-api'; +import { + compatWrapper, + convertLegacyRouteRef, +} from '@backstage/core-compat-api'; +import { rootCatalogKubernetesRouteRef } from '../plugin'; + +export const kubernetesPage = PageBlueprint.make({ + params: { + 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..1369133a24 --- /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 { createFrontendPlugin } 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 createFrontendPlugin({ + 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..b040e3fe5a 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,206 @@ # @backstage/plugin-notifications-backend-module-email +## 0.2.0 + +### Minor Changes + +- def53a7: **BREAKING** Following `NotificationTemplateRenderer` methods now return a Promise and **must** be awaited: `getSubject`, `getText` and `getHtml`. + + Required changes and example usage: + + ```diff + import { notificationsEmailTemplateExtensionPoint } from '@backstage/plugin-notifications-backend-module-email'; + import { Notification } from '@backstage/plugin-notifications-common'; + +import { getNotificationSubject, getNotificationTextContent, getNotificationHtmlContent } from 'my-notification-processing-library` + export const notificationsModuleEmailDecorator = createBackendModule({ + pluginId: 'notifications', + moduleId: 'email.templates', + register(reg) { + reg.registerInit({ + deps: { + emailTemplates: notificationsEmailTemplateExtensionPoint, + }, + async init({ emailTemplates }) { + emailTemplates.setTemplateRenderer({ + - getSubject(notification) { + + async getSubject(notification) { + - return `New notification from ${notification.source}`; + + const subject = await getNotificationSubject(notification); + + return `New notification from ${subject}`; + }, + - getText(notification) { + + async getText(notification) { + - return notification.content; + + const text = await getNotificationTextContent(notification); + + return text; + }, + - getHtml(notification) { + + async getHtml(notification) { + - return `

${notification.content}

`; + + const html = await getNotificationHtmlContent(notification); + + return html; + }, + }); + }, + }); + }, + }); + ``` + +### Patch Changes + +- d55b8e3: Avoid sending broadcast emails as a fallback in case the entity-typed notification user can not be resolved. +- cdb630d: Add support for stream transport for debugging purposes +- 83faf24: Notification email processor supports allowing or denying specific email addresses from receiving notifications +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-notifications-node@0.2.4 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + +## 0.2.0-next.3 + +### Patch Changes + +- 83faf24: Notification email processor supports allowing or denying specific email addresses from receiving notifications +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.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.3 + +## 0.2.0-next.2 + +### Patch Changes + +- cdb630d: Add support for stream transport for debugging purposes +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-notifications-node@0.2.4-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/integration-aws-node@0.1.12 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + +## 0.2.0-next.1 + +### Minor Changes + +- def53a7: **BREAKING** Following `NotificationTemplateRenderer` methods now return a Promise and **must** be awaited: `getSubject`, `getText` and `getHtml`. + + Required changes and example usage: + + ```diff + import { notificationsEmailTemplateExtensionPoint } from '@backstage/plugin-notifications-backend-module-email'; + import { Notification } from '@backstage/plugin-notifications-common'; + +import { getNotificationSubject, getNotificationTextContent, getNotificationHtmlContent } from 'my-notification-processing-library` + export const notificationsModuleEmailDecorator = createBackendModule({ + pluginId: 'notifications', + moduleId: 'email.templates', + register(reg) { + reg.registerInit({ + deps: { + emailTemplates: notificationsEmailTemplateExtensionPoint, + }, + async init({ emailTemplates }) { + emailTemplates.setTemplateRenderer({ + - getSubject(notification) { + + async getSubject(notification) { + - return `New notification from ${notification.source}`; + + const subject = await getNotificationSubject(notification); + + return `New notification from ${subject}`; + }, + - getText(notification) { + + async getText(notification) { + - return notification.content; + + const text = await getNotificationTextContent(notification); + + return text; + }, + - getHtml(notification) { + + async getHtml(notification) { + - return `

${notification.content}

`; + + const html = await getNotificationHtmlContent(notification); + + return html; + }, + }); + }, + }); + }, + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration-aws-node@0.1.12 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.1 + +## 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/README.md b/plugins/notifications-backend-module-email/README.md index 7bbac05b62..1defad74b4 100644 --- a/plugins/notifications-backend-module-email/README.md +++ b/plugins/notifications-backend-module-email/README.md @@ -2,7 +2,7 @@ Adds support for sending Backstage notifications as emails to users. -Supports sending emails using `SMTP`, `SES`, or `sendmail`. +Supports sending emails using `SMTP`, `SES`, `sendmail`, or `stream` (for debugging purposes). ## Customizing email content diff --git a/plugins/notifications-backend-module-email/api-report.md b/plugins/notifications-backend-module-email/api-report.md index 7e8eac6cb7..be3b58d1fd 100644 --- a/plugins/notifications-backend-module-email/api-report.md +++ b/plugins/notifications-backend-module-email/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { Notification as Notification_2 } from '@backstage/plugin-notifications-common'; @@ -17,16 +17,16 @@ export interface NotificationsEmailTemplateExtensionPoint { export const notificationsEmailTemplateExtensionPoint: ExtensionPoint; // @public (undocumented) -const notificationsModuleEmail: BackendFeatureCompat; +const notificationsModuleEmail: BackendFeature; 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/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index 92f3385d1b..04e6f7910f 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -79,6 +79,10 @@ export interface Config { * Newline style, defaults to 'unix' */ newline?: 'unix' | 'windows'; + } + | { + /** Only for debugging, disables the actual sending of emails */ + transport: 'stream'; }; /** * Sender email address @@ -132,6 +136,14 @@ export interface Config { */ excludedTopics?: string[]; }; + /** + * White list of addresses to send email to + */ + allowlistEmailAddresses?: string[]; + /** + * Black list of addresses to not send email to + */ + denylistEmailAddresses?: string[]; }; }; }; diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 76bf5432e2..9c799503e8 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.2.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..a876a548d2 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,14 +29,15 @@ import { CatalogClient, } from '@backstage/catalog-client'; import { + getProcessorFiltersFromConfig, Notification, - notificationSeverities, - NotificationSeverity, + NotificationProcessorFilters, } from '@backstage/plugin-notifications-common'; import { createSendmailTransport, createSesTransport, createSmtpTransport, + createStreamTransport, } from './transports'; import { UserEntity } from '@backstage/catalog-model'; import { compact } from 'lodash'; @@ -57,6 +57,8 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly throttleInterval: number; private readonly frontendBaseUrl: string; private readonly filter: NotificationProcessorFilters; + private readonly allowlistEmailAddresses?: string[]; + private readonly denylistEmailAddresses?: string[]; constructor( private readonly logger: LoggerService, @@ -86,30 +88,13 @@ 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.allowlistEmailAddresses = emailProcessorConfig.getOptionalStringArray( + 'allowlistEmailAddresses', ); + this.denylistEmailAddresses = emailProcessorConfig.getOptionalStringArray( + 'denylistEmailAddresses', + ); + this.filter = getProcessorFiltersFromConfig(emailProcessorConfig); } private async getTransporter() { @@ -129,6 +114,8 @@ export class NotificationsEmailProcessor implements NotificationProcessor { ); } else if (transport === 'sendmail') { this.transporter = createSendmailTransport(this.transportConfig); + } else if (transport === 'stream') { + this.transporter = createStreamTransport(); } else { throw new Error(`Unsupported transport: ${transport}`); } @@ -220,15 +207,36 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private async getRecipientEmails( notification: Notification, options: NotificationSendOptions, - ) { - if (options.recipients.type === 'broadcast' || notification.user === null) { - return await this.getBroadcastEmails(); + ): Promise { + let emails: string[]; + if (options.recipients.type === 'broadcast') { + emails = await this.getBroadcastEmails(); + } else if (options.recipients.type === 'entity' && !!notification.user) { + emails = await this.getUserEmail(notification.user); + } else { + this.logger.info( + `Unknown notification type ${options.recipients.type} or missing user.`, + ); + return []; } - return await this.getUserEmail(notification.user); + + if (this.allowlistEmailAddresses) { + emails = emails.filter(email => + this.allowlistEmailAddresses?.includes(email), + ); + } + + if (this.denylistEmailAddresses) { + emails = emails.filter( + email => !this.denylistEmailAddresses?.includes(email), + ); + } + return emails; } private async sendMail(options: Mail.Options) { try { + this.logger.debug(`Sending notification email to ${options.to}`); await this.transporter.sendMail(options); } catch (e) { this.logger.error(`Failed to send email to ${options.to}: ${e}`); @@ -304,10 +312,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, }; @@ -335,6 +343,8 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return; } + this.logger.debug(`Sending notification emails to: ${emails.join(',')}`); + if (!this.templateRenderer) { await this.sendPlainEmail(notification, emails); return; diff --git a/plugins/notifications-backend-module-email/src/processor/transports/index.ts b/plugins/notifications-backend-module-email/src/processor/transports/index.ts index ccea9f77cc..9a679f3723 100644 --- a/plugins/notifications-backend-module-email/src/processor/transports/index.ts +++ b/plugins/notifications-backend-module-email/src/processor/transports/index.ts @@ -16,3 +16,4 @@ export { createSmtpTransport } from './smtp'; export { createSesTransport } from './ses'; export { createSendmailTransport } from './sendmail'; +export { createStreamTransport } from './stream'; diff --git a/plugins/notifications-backend-module-email/src/processor/transports/stream.ts b/plugins/notifications-backend-module-email/src/processor/transports/stream.ts new file mode 100644 index 0000000000..26ed401366 --- /dev/null +++ b/plugins/notifications-backend-module-email/src/processor/transports/stream.ts @@ -0,0 +1,20 @@ +/* + * 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 { createTransport } from 'nodemailer'; + +export const createStreamTransport = () => { + return createTransport({ streamTransport: true }); +}; diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index c42aa95a2e..bd0323d5c2 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,131 @@ # @backstage/plugin-notifications-backend +## 0.3.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- d55b8e3: Avoid sending broadcast emails as a fallback in case the entity-typed notification user can not be resolved. +- 8013044: fix: consider broadcast union with user +- 7a05f50: Allow using notifications without users in the catalog +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-notifications-node@0.2.4 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.9 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.9 + +## 0.3.4-next.3 + +### Patch Changes + +- 7a05f50: Allow using notifications without users in the catalog +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-events-node@0.3.9-next.3 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.3 + - @backstage/plugin-signals-node@0.1.9-next.3 + +## 0.3.4-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-notifications-node@0.2.4-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-events-node@0.3.9-next.2 + - @backstage/plugin-signals-node@0.1.9-next.2 + - @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-common@0.0.5 + +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-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.18-next.1 + - @backstage/plugin-events-node@0.3.9-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.1 + - @backstage/plugin-signals-node@0.1.9-next.1 + +## 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/README.md b/plugins/notifications-backend/README.md index 60c9a4e078..a6b6615d9f 100644 --- a/plugins/notifications-backend/README.md +++ b/plugins/notifications-backend/README.md @@ -4,13 +4,7 @@ Welcome to the notifications backend plugin! ## Getting started -Add the notifications to your backend: - -```ts -const backend = createBackend(); -// ... -backend.add(import('@backstage/plugin-notifications-backend')); -``` +To install, please refer the [Getting Started](https://backstage.io/docs/notifications) Backstage Notifications and Signals documentation section. For users to be able to see notifications in real-time, you have to install also the signals plugin (`@backstage/plugin-signals-node`, `@backstage/plugin-signals-backend`, and @@ -18,58 +12,8 @@ the signals plugin (`@backstage/plugin-signals-node`, `@backstage/plugin-signals ## Extending Notifications -The notifications can be extended with `NotificationProcessor`. These processors allow to decorate notifications -before they are sent or/and send the notifications to external services. - -Start off by creating a notification processor: - -```ts -import { Notification } from '@backstage/plugin-notifications-common'; -import { NotificationProcessor } from '@backstage/plugin-notifications-node'; - -class MyNotificationProcessor implements NotificationProcessor { - async decorate(notification: Notification): Promise { - if (notification.origin === 'plugin-my-plugin') { - notification.payload.icon = 'my-icon'; - } - return notification; - } - - async send(notification: Notification): Promise { - nodemailer.sendEmail({ - from: 'backstage', - to: 'user', - subject: notification.payload.title, - text: notification.payload.description, - }); - } -} -``` - -Both of the processing functions are optional, and you can implement only one of them. - -Add the notification processor to the notification system by: - -```ts -import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; -import { Notification } from '@backstage/plugin-notifications-common'; - -export const myPlugin = createBackendPlugin({ - pluginId: 'myPlugin', - register(env) { - env.registerInit({ - deps: { - notifications: notificationsProcessingExtensionPoint, - // ... - }, - async init({ notifications }) { - // ... - notifications.addProcessor(new MyNotificationProcessor()); - }, - }); - }, -}); -``` +When a notification is created, it's processing can be customized via `processors`. +Please refer [Backstage documentation](https://backstage.io/docs/notifications) for further details. ## Sending Notifications By Backend Plugins diff --git a/plugins/notifications-backend/api-report.md b/plugins/notifications-backend/api-report.md index de5441f85e..c0769be06d 100644 --- a/plugins/notifications-backend/api-report.md +++ b/plugins/notifications-backend/api-report.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @public -const notificationsPlugin: BackendFeatureCompat; +const notificationsPlugin: BackendFeature; export default notificationsPlugin; // (No @packageDocumentation comment for this package) diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 63b3b263b1..faf3118ef8 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", "backstage": { "role": "backend-plugin", "pluginId": "notifications", @@ -52,7 +52,7 @@ "express": "^4.17.1", "express-promise-router": "^4.1.0", "knex": "^3.0.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "uuid": "^9.0.0", "winston": "^3.2.1", "yn": "^4.0.0" diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts index c6df60c56e..d4e678c8f1 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.test.ts @@ -42,6 +42,7 @@ async function createStore(databaseId: TestDatabaseId) { const idOnly = (notification: Notification) => notification.id; const user = 'user:default/john.doe'; +const otherUser = 'user:default/jane.doe'; const id0 = '08e0871e-e60a-4f68-8110-5ae3513f992e'; const id1 = '01e0871e-e60a-4f68-8110-5ae3513f992e'; @@ -140,7 +141,7 @@ const testNotification7: Notification = { }; const otherUserNotification: Notification = { id: id0, - user: 'user:default/jane.doe', + user: otherUser, created: new Date(now), origin: 'plugin-test', payload: { @@ -246,6 +247,34 @@ describe.each(databases.eachSupportedId())( expect(notifications.map(idOnly)).toEqual([id2, id3, id1]); }); + it('should return correct broadcast notifications for different users', async () => { + await storage.saveNotification(testNotification1); + await storage.saveBroadcast(testNotification2); + await storage.saveNotification(testNotification3); + await storage.saveNotification(otherUserNotification); + + await storage.markRead({ ids: [id1, id2], user }); + + const notifications = await storage.getNotifications({ + user, + }); + expect(notifications.map(idOnly)).toEqual([id2, id3, id1]); + expect(notifications[1].user).toBe(user); + + let otherUserNotifications = await storage.getNotifications({ + user: otherUser, + }); + expect(otherUserNotifications.map(idOnly)).toEqual([id0, id2]); + expect(otherUserNotifications[1].user).toBeNull(); + + await storage.markRead({ ids: [id0, id2], user: otherUser }); + otherUserNotifications = await storage.getNotifications({ + user: otherUser, + }); + expect(otherUserNotifications.map(idOnly)).toEqual([id0, id2]); + expect(otherUserNotifications[1].user).toBe(otherUser); + }); + it('should allow searching for notifications', async () => { await storage.saveNotification(testNotification2); await storage.saveBroadcast(testNotification1); @@ -571,6 +600,23 @@ describe.each(databases.eachSupportedId())( const notification = await storage.getNotification({ id: id2 }); expect(notification?.id).toEqual(id2); }); + + it('should consider user for broadcast by id', async () => { + await storage.saveBroadcast(testNotification1); + + let notification = await storage.getNotification({ id: id1, user }); + expect(notification?.id).toEqual(id1); + expect(notification?.user).toBeNull(); + await storage.markRead({ ids: [id1], user }); + notification = await storage.getNotification({ id: id1, user }); + expect(notification?.user).toBe(user); + + const otherNotification = await storage.getNotification({ + id: id1, + user: otherUser, + }); + expect(otherNotification?.user).toBeNull(); + }); }); describe('markRead', () => { diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index 3d027128ec..b23f5254f4 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -138,14 +138,14 @@ export class DatabaseNotificationsStore implements NotificationsStore { }; }; - private getBroadcastUnion = () => { + private getBroadcastUnion = (user?: string | null) => { return this.db('broadcast') - .leftJoin( - 'broadcast_user_status', - 'id', - '=', - 'broadcast_user_status.broadcast_id', - ) + .leftJoin('broadcast_user_status', function clause() { + const join = this.on('id', '=', 'broadcast_user_status.broadcast_id'); + if (user !== null && user !== undefined) { + join.andOnVal('user', '=', user); + } + }) .select(NOTIFICATION_COLUMNS); }; @@ -156,7 +156,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { const subQuery = this.db('notification') .select(NOTIFICATION_COLUMNS) - .unionAll([this.getBroadcastUnion()]) + .unionAll([this.getBroadcastUnion(user)]) .as('notifications'); const query = this.db.from(subQuery).where(q => { @@ -345,16 +345,19 @@ export class DatabaseNotificationsStore implements NotificationsStore { broadcastQuery.update({ ...updateColumns, read: undefined }), ]); - return await this.getNotification({ id }); + return await this.getNotification({ id, user: notification.user }); } - async getNotification(options: { id: string }): Promise { + async getNotification(options: { + id: string; + user?: string | null; + }): Promise { const rows = await this.db .select('*') .from( this.db('notification') .select(NOTIFICATION_COLUMNS) - .unionAll([this.getBroadcastUnion()]) + .unionAll([this.getBroadcastUnion(options.user)]) .as('notifications'), ) .where('id', options.id) diff --git a/plugins/notifications-backend/src/service/getUsersForEntityRef.test.ts b/plugins/notifications-backend/src/service/getUsersForEntityRef.test.ts new file mode 100644 index 0000000000..b06d6f203b --- /dev/null +++ b/plugins/notifications-backend/src/service/getUsersForEntityRef.test.ts @@ -0,0 +1,175 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { mockServices } from '@backstage/backend-test-utils'; +import { getUsersForEntityRef } from './getUsersForEntityRef'; +import { CatalogApi } from '@backstage/catalog-client'; +import { + RELATION_HAS_MEMBER, + RELATION_OWNED_BY, + RELATION_PARENT_OF, +} from '@backstage/catalog-model'; + +describe('getUsersForEntityRef', () => { + const catalogApiMock = { + getEntitiesByRefs: jest.fn(), + getEntityByRef: jest.fn(), + }; + const authMock = mockServices.auth(); + + it('should return empty array if entityRef is null', async () => { + await expect( + getUsersForEntityRef(null, [], { + auth: authMock, + catalogClient: catalogApiMock as unknown as CatalogApi, + }), + ).resolves.toEqual([]); + }); + + it('should resolve users without calling catalog', async () => { + await expect( + getUsersForEntityRef(['user:foo', 'user:ignored'], ['user:ignored'], { + auth: authMock, + catalogClient: catalogApiMock as unknown as CatalogApi, + }), + ).resolves.toEqual(['user:foo']); + expect(catalogApiMock.getEntitiesByRefs).not.toHaveBeenCalled(); + }); + + it('should resolve group entities to users', async () => { + catalogApiMock.getEntitiesByRefs.mockResolvedValueOnce({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'parent_group', + }, + relations: [ + { + type: RELATION_HAS_MEMBER, + targetRef: 'user:default/foo', + }, + { + type: RELATION_PARENT_OF, + targetRef: 'group:default/child_group', + }, + ], + }, + ], + }); + + catalogApiMock.getEntitiesByRefs.mockResolvedValueOnce({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'child_group', + }, + relations: [ + { + type: RELATION_HAS_MEMBER, + targetRef: 'user:default/bar', + }, + { + type: RELATION_HAS_MEMBER, + targetRef: 'user:default/ignored', + }, + ], + }, + ], + }); + + await expect( + getUsersForEntityRef( + 'group:default/parent_group', + ['user:default/ignored'], + { + auth: authMock, + catalogClient: catalogApiMock as unknown as CatalogApi, + }, + ), + ).resolves.toEqual(['user:default/foo', 'user:default/bar']); + }); + + it('should resolve user owner of entity from entity ref', async () => { + catalogApiMock.getEntitiesByRefs.mockResolvedValueOnce({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test_component', + }, + relations: [ + { + type: RELATION_OWNED_BY, + targetRef: 'user:default/foo', + }, + ], + }, + ], + }); + + await expect( + getUsersForEntityRef('component:default/test_component', [], { + auth: authMock, + catalogClient: catalogApiMock as unknown as CatalogApi, + }), + ).resolves.toEqual(['user:default/foo']); + }); + + it('should resolve group owner of entity from entity ref', async () => { + catalogApiMock.getEntitiesByRefs.mockResolvedValueOnce({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test_component', + }, + relations: [ + { + type: RELATION_OWNED_BY, + targetRef: 'group:default/owner_group', + }, + ], + }, + ], + }); + + catalogApiMock.getEntityByRef.mockResolvedValueOnce({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'owner_group', + }, + relations: [ + { + type: RELATION_HAS_MEMBER, + targetRef: 'user:default/foo', + }, + ], + }); + + await expect( + getUsersForEntityRef('component:default/test_component', [], { + auth: authMock, + catalogClient: catalogApiMock as unknown as CatalogApi, + }), + ).resolves.toEqual(['user:default/foo']); + }); +}); diff --git a/plugins/notifications-backend/src/service/getUsersForEntityRef.ts b/plugins/notifications-backend/src/service/getUsersForEntityRef.ts new file mode 100644 index 0000000000..9ced24709b --- /dev/null +++ b/plugins/notifications-backend/src/service/getUsersForEntityRef.ts @@ -0,0 +1,161 @@ +/* + * 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 { + Entity, + isGroupEntity, + isUserEntity, + parseEntityRef, + RELATION_HAS_MEMBER, + RELATION_OWNED_BY, + RELATION_PARENT_OF, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { AuthService } from '@backstage/backend-plugin-api'; +import { CatalogApi } from '@backstage/catalog-client'; + +const isUserEntityRef = (ref: string) => + parseEntityRef(ref).kind.toLocaleLowerCase() === 'user'; + +// Partitions array of entity references to two arrays; user entity refs and other entity refs +const partitionEntityRefs = (refs: string[]): string[][] => + refs.reduce( + ([userEntityRefs, otherEntityRefs]: string[][], ref: string) => { + return isUserEntityRef(ref) + ? [[...userEntityRefs, ref], otherEntityRefs] + : [userEntityRefs, [...otherEntityRefs, ref]]; + }, + [[], []], + ); + +export const getUsersForEntityRef = async ( + entityRef: string | string[] | null, + excludeEntityRefs: string | string[], + options: { + auth: AuthService; + catalogClient: CatalogApi; + }, +): Promise => { + const { auth, catalogClient } = options; + + if (entityRef === null) { + return []; + } + + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: await auth.getOwnServiceCredentials(), + targetPluginId: 'catalog', + }); + + const excluded = Array.isArray(excludeEntityRefs) + ? excludeEntityRefs + : [excludeEntityRefs]; + + const refsArr = Array.isArray(entityRef) ? entityRef : [entityRef]; + const [userEntityRefs, otherEntityRefs] = partitionEntityRefs(refsArr); + const users: string[] = userEntityRefs.filter(ref => !excluded.includes(ref)); + const entityRefs = otherEntityRefs.filter(ref => !excluded.includes(ref)); + + const fields = ['kind', 'metadata.name', 'metadata.namespace', 'relations']; + let entities: Array = []; + if (entityRefs.length > 0) { + const fetchedEntities = await catalogClient.getEntitiesByRefs( + { + entityRefs, + fields, + }, + { token }, + ); + entities = fetchedEntities.items; + } + + const mapEntity = async (entity: Entity | undefined): Promise => { + if (!entity) { + return []; + } + + const currentEntityRef = stringifyEntityRef(entity); + if (excluded.includes(currentEntityRef)) { + return []; + } + + if (isUserEntity(entity)) { + return [currentEntityRef]; + } + + if (isGroupEntity(entity)) { + if (!entity.relations?.length) { + return []; + } + + const groupUsers = entity.relations + .filter( + relation => + relation.type === RELATION_HAS_MEMBER && + isUserEntityRef(relation.targetRef), + ) + .map(r => r.targetRef); + + const childGroupRefs = entity.relations + .filter(relation => relation.type === RELATION_PARENT_OF) + .map(r => r.targetRef); + + let childGroupUsers: string[][] = []; + if (childGroupRefs.length > 0) { + const childGroups = await catalogClient.getEntitiesByRefs( + { + entityRefs: childGroupRefs, + fields, + }, + { token }, + ); + childGroupUsers = await Promise.all(childGroups.items.map(mapEntity)); + } + + return [...groupUsers, ...childGroupUsers.flat(2)].filter( + ref => !excluded.includes(ref), + ); + } + + if (entity.relations?.length) { + const ownerRef = entity.relations.find( + relation => relation.type === RELATION_OWNED_BY, + )?.targetRef; + + if (!ownerRef) { + return []; + } + + if (isUserEntityRef(ownerRef)) { + if (excluded.includes(ownerRef)) { + return []; + } + return [ownerRef]; + } + + const owner = await catalogClient.getEntityByRef(ownerRef, { token }); + return mapEntity(owner); + } + + return []; + }; + + for (const entity of entities) { + const u = await mapEntity(entity); + users.push(...u); + } + + return [...new Set(users)].filter(Boolean); +}; diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 3ca3ce46bc..abd7e2dc13 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -23,15 +23,6 @@ import { } from '../database'; import { v4 as uuid } from 'uuid'; import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; -import { - Entity, - isGroupEntity, - isUserEntity, - RELATION_HAS_MEMBER, - RELATION_OWNED_BY, - RELATION_PARENT_OF, - stringifyEntityRef, -} from '@backstage/catalog-model'; import { NotificationProcessor, NotificationSendOptions, @@ -54,6 +45,7 @@ import { NotificationStatus, } from '@backstage/plugin-notifications-common'; import { parseEntityOrderFieldParams } from './parseEntityOrderFieldParams'; +import { getUsersForEntityRef } from './getUsersForEntityRef'; /** @internal */ export interface RouterOptions { @@ -94,91 +86,6 @@ export async function createRouter( return info.userEntityRef; }; - const getUsersForEntityRef = async ( - entityRef: string | string[] | null, - excludeEntityRefs: string | string[], - ): Promise => { - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: await auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); - - if (entityRef === null) { - return []; - } - - const fields = ['kind', 'metadata.name', 'metadata.namespace', 'relations']; - - const refs = Array.isArray(entityRef) ? entityRef : [entityRef]; - const entities = await catalogClient.getEntitiesByRefs( - { - entityRefs: refs, - fields, - }, - { token }, - ); - - const excluded = Array.isArray(excludeEntityRefs) - ? excludeEntityRefs - : [excludeEntityRefs]; - - const mapEntity = async (entity: Entity | undefined): Promise => { - if (!entity) { - return []; - } - - const currentEntityRef = stringifyEntityRef(entity); - if (excluded.includes(currentEntityRef)) { - return []; - } - - if (isUserEntity(entity)) { - return [currentEntityRef]; - } else if (isGroupEntity(entity) && entity.relations) { - const users = entity.relations - .filter(relation => relation.type === RELATION_HAS_MEMBER) - .map(r => r.targetRef); - - const childGroupRefs = entity.relations - .filter(relation => relation.type === RELATION_PARENT_OF) - .map(r => r.targetRef); - - const childGroups = await catalogClient.getEntitiesByRefs( - { - entityRefs: childGroupRefs, - fields, - }, - { token }, - ); - const childGroupUsers = await Promise.all( - childGroups.items.map(mapEntity), - ); - return [...users, ...childGroupUsers.flat(2)]; - } else if (entity.relations) { - const ownerRef = entity.relations.find( - relation => relation.type === RELATION_OWNED_BY, - )?.targetRef; - - if (ownerRef) { - const owner = await catalogClient.getEntityByRef(ownerRef, { token }); - if (owner) { - return mapEntity(owner); - } - } - } - - return []; - }; - - const users: string[] = []; - for (const entity of entities.items) { - const u = await mapEntity(entity); - users.push(...u); - } - - return users; - }; - const filterProcessors = (payload: NotificationPayload) => { const result: NotificationProcessor[] = []; @@ -543,6 +450,7 @@ export async function createRouter( users = await getUsersForEntityRef( entityRef, recipients.excludeEntityRef ?? [], + { auth, catalogClient }, ); } catch (e) { logger.error(`Failed to resolve notification receivers: ${e}`); 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..1c98305038 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,92 @@ # @backstage/plugin-notifications-node +## 0.2.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.9 + +## 0.2.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-signals-node@0.1.9-next.3 + +## 0.2.4-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-signals-node@0.1.9-next.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/plugin-notifications-common@0.0.5 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @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.1 + +## 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..995b98bc21 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 = @@ -69,7 +65,11 @@ export interface NotificationService { } // @public (undocumented) -export const notificationService: ServiceRef; +export const notificationService: ServiceRef< + NotificationService, + 'plugin', + 'singleton' +>; // @public (undocumented) export type NotificationServiceOptions = { diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index e79b457965..37d2663a3a 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", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library", @@ -44,7 +44,7 @@ "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-signals-node": "workspace:^", "knex": "^3.0.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "uuid": "^9.0.0" }, "devDependencies": { 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..0f257047ba 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,90 @@ # @backstage/plugin-notifications +## 0.3.0 + +### Minor Changes + +- 0410fc9: By default, set notification as read when opening snackbar or web notification link + +### Patch Changes + +- 80b84f7: Fixed issue with notification reloading on page change +- b58e452: Broadcast notifications are now decorated with an icon. +- Updated dependencies + - @backstage/core-components@0.14.10 + - @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.3.0-next.1 + +### Minor Changes + +- 0410fc9: By default, set notification as read when opening snackbar or web notification link + +### Patch Changes + +- 80b84f7: Fixed issue with notification reloading on page change +- 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.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/README.md b/plugins/notifications/README.md index 63212d2c16..78bcf4e753 100644 --- a/plugins/notifications/README.md +++ b/plugins/notifications/README.md @@ -2,38 +2,11 @@ Welcome to the notifications plugin! -_This plugin was created through the Backstage CLI_ - ## Getting started -First, install the `@backstage/plugin-notifications-backend` and `@backstage/plugin-notifications-node` packages. -See the documentation for installation instructions. +To install, please refer the [Getting Started](https://backstage.io/docs/notifications) Backstage Notifications and Signals documentation section. -To add the notifications main menu, add the following to your `packages/app/src/components/Root/Root.tsx`: - -```tsx -import { NotificationsSidebarItem } from '@backstage/plugin-notifications'; - - - - - // ... - - - -; -``` - -Also add the route to notifications to `packages/app/src/App.tsx`: - -```tsx -import { NotificationsPage } from '@backstage/plugin-notifications'; - - - // ... - } /> -; -``` +Please mind installing the `@backstage/plugin-notifications-backend` and `@backstage/plugin-notifications-node` packages before this frontend plugin. ## Real-time notifications diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index 9240bdb1c2..5e06ae2616 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -176,20 +176,5 @@ export function useNotificationsApi( value: T; }; -// @public (undocumented) -export function useTitleCounter(): { - setNotificationCount: (newCount: number) => void; -}; - -// @public (undocumented) -export function useWebNotifications(enabled: boolean): { - sendWebNotification: (options: { - id: string; - title: string; - description: string; - link?: string; - }) => Notification | null; -}; - // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index bc868c56a1..c847fc605d 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.3.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/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index 7b55c63747..22ce11db69 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -111,10 +111,12 @@ export const NotificationsSidebarItem = (props?: { const notificationsApi = useApi(notificationsApiRef); const alertApi = useApi(alertApiRef); const [unreadCount, setUnreadCount] = React.useState(0); - const notificationsRoute = useRouteRef(rootRouteRef); + const notificationsRoute = useRouteRef(rootRouteRef)(); // TODO: Do we want to add long polling in case signals are not available const { lastSignal } = useSignal('notifications'); - const { sendWebNotification } = useWebNotifications(webNotificationsEnabled); + const { sendWebNotification, requestUserPermission } = useWebNotifications( + webNotificationsEnabled, + ); const [refresh, setRefresh] = React.useState(false); const { setNotificationCount } = useTitleCounter(); @@ -124,8 +126,21 @@ export const NotificationsSidebarItem = (props?: { <> { + if (notification.payload.link) { + notificationsApi + .updateNotifications({ + ids: [notification.id], + read: true, + }) + .catch(() => { + alertApi.post({ + message: 'Failed to mark notification as read', + severity: 'error', + }); + }); + } closeSnackbar(snackBarId); }} > @@ -174,7 +189,6 @@ export const NotificationsSidebarItem = (props?: { ) { return; } - notificationsApi .getNotification(signal.notification_id) .then(notification => { @@ -196,6 +210,7 @@ export const NotificationsSidebarItem = (props?: { ? `${notification.payload.title.substring(0, 50)}...` : notification.payload.title; enqueueSnackbar(snackBarText, { + key: notification.id, variant: notification.payload.severity, anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, action, @@ -258,7 +273,10 @@ export const NotificationsSidebarItem = (props?: { /> )} { + requestUserPermission(); + }} hasNotifications={!error && !!unreadCount} text={text} icon={icon} diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx index d7c4c40781..8a6cbd17d7 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx @@ -24,6 +24,7 @@ import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import { Notification } from '@backstage/plugin-notifications-common'; import { useConfirm } from 'material-ui-confirm'; +import BroadcastIcon from '@material-ui/icons/RssFeed'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; import { Link, @@ -40,7 +41,7 @@ import { BulkActions } from './BulkActions'; const ThrottleDelayMs = 1000; -const useStyles = makeStyles({ +const useStyles = makeStyles(theme => ({ description: { maxHeight: '5rem', overflow: 'auto', @@ -48,7 +49,15 @@ const useStyles = makeStyles({ severityItem: { alignContent: 'center', }, -}); + broadcastIcon: { + fontSize: '1rem', + verticalAlign: 'text-bottom', + }, + notificationInfoRow: { + marginLeft: theme.spacing(0.5), + marginRight: theme.spacing(0.5), + }, +})); /** @public */ export type NotificationsTableProps = Pick< @@ -236,15 +245,40 @@ export const NotificationsTable = ({ {notification.payload.description} ) : null} + + {!notification.user && ( + <> + + + )} {notification.origin && ( - <>{notification.origin} •  + <> + + {notification.origin} + + • + )} {notification.payload.topic && ( - <>{notification.payload.topic} •  + <> + + {notification.payload.topic} + + • + )} {notification.created && ( - + )} @@ -272,15 +306,13 @@ export const NotificationsTable = ({ selectedNotifications={new Set([notification.id])} onSwitchReadStatus={onSwitchReadStatus} onSwitchSavedStatus={onSwitchSavedStatus} - // /> ), }, ]; }, [ - markAsReadOnLinkOpen, - selectedNotifications, notifications, + selectedNotifications, isUnread, onSwitchReadStatus, onSwitchSavedStatus, @@ -288,6 +320,9 @@ export const NotificationsTable = ({ onNotificationsSelectChange, classes.severityItem, classes.description, + classes.broadcastIcon, + classes.notificationInfoRow, + markAsReadOnLinkOpen, ]); return ( diff --git a/plugins/notifications/src/hooks/useTitleCounter.ts b/plugins/notifications/src/hooks/useTitleCounter.ts index 0793cd58d6..399bfb84f4 100644 --- a/plugins/notifications/src/hooks/useTitleCounter.ts +++ b/plugins/notifications/src/hooks/useTitleCounter.ts @@ -25,7 +25,7 @@ const throttledSetTitle = throttle((shownTitle: string) => { document.title = shownTitle; }, 100); -/** @public */ +/** @internal */ export function useTitleCounter() { const [title, setTitle] = useState(document.title); const [count, setCount] = useState(0); diff --git a/plugins/notifications/src/hooks/useWebNotifications.ts b/plugins/notifications/src/hooks/useWebNotifications.ts index 728d992130..cc69a0e815 100644 --- a/plugins/notifications/src/hooks/useWebNotifications.ts +++ b/plugins/notifications/src/hooks/useWebNotifications.ts @@ -13,19 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useState } from 'react'; +import { useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { notificationsApiRef } from '../api'; import { rootRouteRef } from '../routes'; -import { useRouteRef } from '@backstage/core-plugin-api'; -import { useNavigate } from 'react-router-dom'; -/** @public */ +/** @internal */ export function useWebNotifications(enabled: boolean) { const [webNotificationPermission, setWebNotificationPermission] = useState('default'); - const notificationsRoute = useRouteRef(rootRouteRef); - const navigate = useNavigate(); + const notificationsRoute = useRouteRef(rootRouteRef)(); + const notificationsApi = useApi(notificationsApiRef); - useEffect(() => { + const requestUserPermission = useCallback(() => { if ( enabled && 'Notification' in window && @@ -57,16 +57,20 @@ export function useWebNotifications(enabled: boolean) { event.preventDefault(); if (options.link) { window.open(options.link, '_blank'); + notificationsApi.updateNotifications({ + ids: [options.id], + read: true, + }); } else { - navigate(notificationsRoute()); + window.open(notificationsRoute); } notification.close(); }; return notification; }, - [webNotificationPermission, navigate, notificationsRoute], + [webNotificationPermission, notificationsApi, notificationsRoute], ); - return { sendWebNotification }; + return { sendWebNotification, requestUserPermission }; } diff --git a/plugins/notifications/src/index.ts b/plugins/notifications/src/index.ts index bb5d76a0cf..25565f1ae1 100644 --- a/plugins/notifications/src/index.ts +++ b/plugins/notifications/src/index.ts @@ -15,5 +15,5 @@ */ export { notificationsPlugin, NotificationsPage } from './plugin'; export * from './api'; -export * from './hooks'; +export { useNotificationsApi } from './hooks'; export * from './components'; diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 8c2ea470da..4570bcf112 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,90 @@ # @backstage/plugin-org-react +## 0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @backstage/core-plugin-api@1.9.3 + +## 0.1.27-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## 0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## 0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## 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..607600fbaa 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", "backstage": { "role": "web-library", "pluginId": "org", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index fe82d2c948..55b9b3431a 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,113 @@ # @backstage/plugin-org +## 0.6.28 + +### Patch Changes + +- c7603e8: Deprecate the old pattern of `create*Extension`, and replace it with the equivalent Blueprint implementation instead +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0 + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/core-compat-api@0.2.8 + - @backstage/catalog-model@1.6.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26 + +## 0.6.28-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/core-compat-api@0.2.8-next.3 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + - @backstage/plugin-catalog-common@1.0.26-next.2 + +## 0.6.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.7.0-next.2 + - @backstage/core-compat-api@0.2.8-next.2 + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-catalog-common@1.0.26-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## 0.6.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/frontend-plugin-api@0.6.8-next.1 + - @backstage/core-compat-api@0.2.8-next.1 + - @backstage/plugin-catalog-common@1.0.26-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/core-components@0.14.10-next.0 + - @backstage/core-plugin-api@1.9.3 + +## 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/api-report-alpha.md b/plugins/org/api-report-alpha.md index 8c5391d857..1187c8f0e3 100644 --- a/plugins/org/api-report-alpha.md +++ b/plugins/org/api-report-alpha.md @@ -4,13 +4,151 @@ ```ts import { BackstagePlugin } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { ExternalRouteRef } from '@backstage/frontend-plugin-api'; +import { default as React_2 } from 'react'; // @alpha (undocumented) const _default: BackstagePlugin< {}, { - catalogIndex: ExternalRouteRef; + catalogIndex: ExternalRouteRef; + }, + { + 'entity-card:org/group-profile': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'group-profile'; + } + >; + 'entity-card:org/members-list': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'members-list'; + } + >; + 'entity-card:org/ownership': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'ownership'; + } + >; + 'entity-card:org/user-profile': ExtensionDefinition< + { + filter: string | undefined; + }, + { + filter?: string | undefined; + }, + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef< + (entity: Entity) => boolean, + 'catalog.entity-filter-function', + { + optional: true; + } + > + | ConfigurableExtensionDataRef< + string, + 'catalog.entity-filter-expression', + { + optional: true; + } + >, + {}, + { + kind: 'entity-card'; + namespace: undefined; + name: 'user-profile'; + } + >; } >; export default _default; diff --git a/plugins/org/package.json b/plugins/org/package.json index 5b749ac215..3940932cbe 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", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin", diff --git a/plugins/org/src/alpha.tsx b/plugins/org/src/alpha.tsx index 3fbf5bcc41..61054a2d8a 100644 --- a/plugins/org/src/alpha.tsx +++ b/plugins/org/src/alpha.tsx @@ -18,53 +18,61 @@ import { compatWrapper, convertLegacyRouteRefs, } from '@backstage/core-compat-api'; -import { createPlugin } from '@backstage/frontend-plugin-api'; +import { createFrontendPlugin } from '@backstage/frontend-plugin-api'; import React from 'react'; import { catalogIndexRouteRef } from './routes'; -import { createEntityCardExtension } from '@backstage/plugin-catalog-react/alpha'; +import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha'; /** @alpha */ -const EntityGroupProfileCard = createEntityCardExtension({ +const EntityGroupProfileCard = EntityCardBlueprint.make({ name: 'group-profile', - filter: 'kind:group', - loader: async () => - import('./components/Cards/Group/GroupProfile/GroupProfileCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:group', + loader: async () => + import('./components/Cards/Group/GroupProfile/GroupProfileCard').then(m => + compatWrapper(), + ), + }, }); /** @alpha */ -const EntityMembersListCard = createEntityCardExtension({ +const EntityMembersListCard = EntityCardBlueprint.make({ name: 'members-list', - filter: 'kind:group', - loader: async () => - import('./components/Cards/Group/MembersList/MembersListCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:group', + loader: async () => + import('./components/Cards/Group/MembersList/MembersListCard').then(m => + compatWrapper(), + ), + }, }); /** @alpha */ -const EntityOwnershipCard = createEntityCardExtension({ +const EntityOwnershipCard = EntityCardBlueprint.make({ name: 'ownership', - filter: 'kind:group,user', - loader: async () => - import('./components/Cards/OwnershipCard/OwnershipCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:group,user', + loader: async () => + import('./components/Cards/OwnershipCard/OwnershipCard').then(m => + compatWrapper(), + ), + }, }); /** @alpha */ -const EntityUserProfileCard = createEntityCardExtension({ +const EntityUserProfileCard = EntityCardBlueprint.make({ name: 'user-profile', - filter: 'kind:user', - loader: async () => - import('./components/Cards/User/UserProfileCard/UserProfileCard').then(m => - compatWrapper(), - ), + params: { + filter: 'kind:user', + loader: async () => + import('./components/Cards/User/UserProfileCard/UserProfileCard').then( + m => compatWrapper(), + ), + }, }); /** @alpha */ -export default createPlugin({ +export default createFrontendPlugin({ id: 'org', extensions: [ EntityGroupProfileCard, diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx index db54be9e02..e5bfa053c0 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx @@ -15,11 +15,14 @@ */ import { Entity, GroupEntity } from '@backstage/catalog-model'; -import { catalogApiRef, EntityProvider } from '@backstage/plugin-catalog-react'; -import { TestApiProvider } from '@backstage/test-utils'; +import { + catalogApiRef, + EntityProvider, + entityRouteRef, +} from '@backstage/plugin-catalog-react'; +import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils'; import Grid from '@material-ui/core/Grid'; -import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; +import React, { ComponentType, PropsWithChildren } from 'react'; import { groupA, mockedCatalogApiSupportingGroups, @@ -29,6 +32,15 @@ import { MembersListCard } from './MembersListCard'; export default { title: 'Plugins/Org/Group Members List Card', component: MembersListCard, + decorators: [ + (Story: ComponentType>) => + wrapInTestApp( +
+ +
, + { mountedRoutes: { '/': entityRouteRef } }, + ), + ], }; const makeUser = ({ @@ -108,45 +120,38 @@ const bob = makeUser({ const catalogApi = (items: Entity[]) => ({ getEntities: () => Promise.resolve({ items }), }); - export const Default = () => ( - - - - - - - + + + + + - - - + + + ); export const Empty = () => ( - - - - - - - + + + + + - - - + + + ); export const AggregateMembersToggle = () => ( - - - - - - - + + + + + - - - + + + ); 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..6d2a77fb3c 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,76 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.1.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + +## 0.1.20-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## 0.1.20-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + +## 0.1.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 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/api-report.md b/plugins/permission-backend-module-policy-allow-all/api-report.md index 02a4255424..26966f4938 100644 --- a/plugins/permission-backend-module-policy-allow-all/api-report.md +++ b/plugins/permission-backend-module-policy-allow-all/api-report.md @@ -3,9 +3,9 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @public -const permissionModuleAllowAllPolicy: BackendFeatureCompat; +const permissionModuleAllowAllPolicy: BackendFeature; export default permissionModuleAllowAllPolicy; ``` diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json index 62a3f6d101..637843cdeb 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", "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..2f43217d5d 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,105 @@ # @backstage/plugin-permission-backend +## 0.5.47 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.5.47-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + +## 0.5.47-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.5.47-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 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/api-report-alpha.md b/plugins/permission-backend/api-report-alpha.md index 7448c4eed0..d0a10b6b3a 100644 --- a/plugins/permission-backend/api-report-alpha.md +++ b/plugins/permission-backend/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const permissionPlugin: BackendFeatureCompat; +const permissionPlugin: BackendFeature; export default permissionPlugin; // (No @packageDocumentation comment for this package) diff --git a/plugins/permission-backend/api-report.md b/plugins/permission-backend/api-report.md index d8d40be440..bd976a8d7b 100644 --- a/plugins/permission-backend/api-report.md +++ b/plugins/permission-backend/api-report.md @@ -4,24 +4,24 @@ ```ts import { AuthService } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import express from 'express'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { LoggerService } from '@backstage/backend-plugin-api'; import { PermissionPolicy } from '@backstage/plugin-permission-node'; +import { RootConfigService } from '@backstage/backend-plugin-api'; import { UserInfoService } from '@backstage/backend-plugin-api'; -// @public +// @public @deprecated export function createRouter(options: RouterOptions): Promise; -// @public +// @public @deprecated export interface RouterOptions { // (undocumented) auth?: AuthService; // (undocumented) - config: Config; + config: RootConfigService; // (undocumented) discovery: DiscoveryService; // (undocumented) diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 24b62d1358..4b6287d7d2 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", "backstage": { "role": "backend-plugin", "pluginId": "permission", @@ -63,7 +63,7 @@ "express": "^4.17.1", "express-promise-router": "^4.1.0", "lodash": "^4.17.21", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "yn": "^4.0.0", "zod": "^3.22.4" }, 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..db2b72f95c 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,11 +37,11 @@ import { ApplyConditionsRequestEntry, ApplyConditionsResponseEntry, PermissionPolicy, + PolicyQueryUser, } from '@backstage/plugin-permission-node'; import { PermissionIntegrationClient } from './PermissionIntegrationClient'; import { memoize } from 'lodash'; import DataLoader from 'dataloader'; -import { Config } from '@backstage/config'; import { AuthService, BackstageCredentials, @@ -53,6 +50,7 @@ import { DiscoveryService, HttpAuthService, LoggerService, + RootConfigService, UserInfoService, } from '@backstage/backend-plugin-api'; @@ -99,13 +97,14 @@ const evaluatePermissionRequestBatchSchema: z.ZodSchema = { @@ -94,9 +95,7 @@ export type EvaluatePermissionResponseBatch = PermissionMessageBatch; // @public -export type EvaluatorRequestOptions = { - token?: string; -}; +export interface EvaluatorRequestOptions {} // @public export type IdentifiedPermissionMessage = T & { @@ -127,6 +126,20 @@ export function isResourcePermission( // @public export function isUpdatePermission(permission: Permission): boolean; +// @public +export type MetadataResponse = { + permissions?: Permission[]; + rules: MetadataResponseSerializedRule[]; +}; + +// @public +export type MetadataResponseSerializedRule = { + name: string; + description: string; + resourceType: string; + paramsSchema?: ReturnType; +}; + // @public export type NotCriteria = { not: PermissionCriteria; @@ -162,14 +175,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 +209,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..4ea5f56814 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.1", "description": "Isomorphic types and client for Backstage permissions and authorization", "backstage": { "role": "common-library", @@ -53,7 +53,8 @@ "@backstage/types": "workspace:^", "cross-fetch": "^4.0.0", "uuid": "^9.0.0", - "zod": "^3.22.4" + "zod": "^3.22.4", + "zod-to-json-schema": "^3.20.4" }, "devDependencies": { "@backstage/cli": "workspace:^", 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-common/src/types/index.ts b/plugins/permission-common/src/types/index.ts index 5702f47c1c..f5380541df 100644 --- a/plugins/permission-common/src/types/index.ts +++ b/plugins/permission-common/src/types/index.ts @@ -40,6 +40,10 @@ export type { NotCriteria, } from './api'; export type { DiscoveryApi } from './discovery'; +export type { + MetadataResponse, + MetadataResponseSerializedRule, +} from './integration'; export type { BasicPermission, PermissionAttributes, diff --git a/plugins/permission-common/src/types/integration.ts b/plugins/permission-common/src/types/integration.ts new file mode 100644 index 0000000000..d531140923 --- /dev/null +++ b/plugins/permission-common/src/types/integration.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import zodToJsonSchema from 'zod-to-json-schema'; +import { Permission } from './permission'; + +/** + * Serialized permission rules, with the paramsSchema + * converted from a ZodSchema to a JsonSchema. + * + * @public + */ +export type MetadataResponseSerializedRule = { + name: string; + description: string; + resourceType: string; + paramsSchema?: ReturnType; +}; + +/** + * Response type for the .metadata endpoint in + * {@link @backstage/plugin-permission-node#createPermissionIntegrationRouter} + * + * @public + */ +export type MetadataResponse = { + permissions?: Permission[]; + rules: MetadataResponseSerializedRule[]; +}; diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 7255dbe266..79b6af785b 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,113 @@ # @backstage/plugin-permission-node +## 0.8.1 + +### Patch Changes + +- df784fe: The MetadataResponse type has been moved to @backstage/plugin-permission-common + to match the recent move of MetadataResponseSerializedRule, and should be + imported from there going forward. To avoid an immediate breaking change, this + type is still re-exported from this package, but is marked as deprecated and + will be removed in a future release. +- 5cd9878: The MetadataResponseSerializedRule type has been moved to @backstage/plugin-permission-common, and should be imported from there going forward. To avoid an immediate breaking change, this type is still re-exported from this package, but is marked as deprecated and will be removed in a future release. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.8.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## 0.8.1-next.2 + +### Patch Changes + +- df784fe: The MetadataResponse type has been moved to @backstage/plugin-permission-common + to match the recent move of MetadataResponseSerializedRule, and should be + imported from there going forward. To avoid an immediate breaking change, this + type is still re-exported from this package, but is marked as deprecated and + will be removed in a future release. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.8.1-next.1 + +### Patch Changes + +- 5cd9878: The MetadataResponseSerializedRule type has been moved to @backstage/plugin-permission-common, and should be imported from there going forward. To avoid an immediate breaking change, this type is still re-exported from this package, but is marked as deprecated and will be removed in a future release. +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-auth-node@0.4.18-next.1 + +## 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..0f0885e180 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -8,13 +8,17 @@ 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'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import express from 'express'; import { IdentifiedPermissionMessage } from '@backstage/plugin-permission-common'; +import { MetadataResponse as MetadataResponse_2 } from '@backstage/plugin-permission-common'; +import { MetadataResponseSerializedRule as MetadataResponseSerializedRule_2 } from '@backstage/plugin-permission-common'; import { NotCriteria } from '@backstage/plugin-permission-common'; import { Permission } from '@backstage/plugin-permission-common'; import { PermissionCondition } from '@backstage/plugin-permission-common'; @@ -25,9 +29,8 @@ import { PermissionsServiceRequestOptions } from '@backstage/backend-plugin-api' import { PolicyDecision } from '@backstage/plugin-permission-common'; import { QueryPermissionRequest } from '@backstage/plugin-permission-common'; import { ResourcePermission } from '@backstage/plugin-permission-common'; -import { TokenManagerService } from '@backstage/backend-plugin-api'; +import { TokenManager } from '@backstage/backend-common'; import { z } from 'zod'; -import zodToJsonSchema from 'zod-to-json-schema'; // @public export type ApplyConditionsRequest = { @@ -186,19 +189,11 @@ export const makeCreatePermissionRule: < rule: PermissionRule, ) => PermissionRule; -// @public -export type MetadataResponse = { - permissions?: Permission[]; - rules: MetadataResponseSerializedRule[]; -}; +// @public @deprecated +export type MetadataResponse = MetadataResponse_2; -// @public -export type MetadataResponseSerializedRule = { - name: string; - description: string; - resourceType: string; - paramsSchema?: ReturnType; -}; +// @public @deprecated +export type MetadataResponseSerializedRule = MetadataResponseSerializedRule_2; // @public export type PermissionIntegrationRouterOptions< @@ -246,10 +241,7 @@ export type PermissionIntegrationRouterOptions< // @public export interface PermissionPolicy { // (undocumented) - handle( - request: PolicyQuery, - user?: BackstageIdentityResponse, - ): Promise; + handle(request: PolicyQuery, user?: PolicyQueryUser): Promise; } // @public @@ -272,6 +264,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) @@ -289,7 +290,7 @@ export class ServerPermissionClient implements PermissionsService { config: Config, options: { discovery: DiscoveryService; - tokenManager: TokenManagerService; + tokenManager?: TokenManager; auth?: AuthService; }, ): ServerPermissionClient; diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 695e28f30b..1d3a9089aa 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", "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..772f1b95d7 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(); @@ -82,44 +82,53 @@ describe('ServerPermissionClient', () => { 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, - }, - ]); + 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(), auth: mockServices.auth(), }); 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(), auth: mockServices.auth(), }); 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', + }), + ); }); }); @@ -144,202 +153,57 @@ describe('ServerPermissionClient', () => { it('should bypass the permission backend if permissions are disabled', async () => { 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, + 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', + }), + ); }); }); @@ -353,7 +217,6 @@ describe('ServerPermissionClient', () => { }), { discovery, - tokenManager: mockServices.tokenManager(), auth: mockServices.auth(), }, ); diff --git a/plugins/permission-node/src/ServerPermissionClient.ts b/plugins/permission-node/src/ServerPermissionClient.ts index cf54f3b894..c34a8fe930 100644 --- a/plugins/permission-node/src/ServerPermissionClient.ts +++ b/plugins/permission-node/src/ServerPermissionClient.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { createLegacyAuthAdapters } from '@backstage/backend-common'; +import { + TokenManager, + createLegacyAuthAdapters, +} from '@backstage/backend-common'; import { AuthService, BackstageCredentials, @@ -22,7 +25,6 @@ import { DiscoveryService, PermissionsService, PermissionsServiceRequestOptions, - TokenManagerService, } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { @@ -51,7 +53,8 @@ export class ServerPermissionClient implements PermissionsService { config: Config, options: { discovery: DiscoveryService; - tokenManager: TokenManagerService; + /** @deprecated This option will be removed in the future, provide a the auth option instead */ + tokenManager?: TokenManager; auth?: AuthService; }, ) { @@ -62,6 +65,7 @@ export class ServerPermissionClient implements PermissionsService { if ( permissionEnabled && + tokenManager && (tokenManager as any).isInsecureServerTokenManager ) { throw new Error( @@ -146,14 +150,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/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index 4a52c33f18..40d215d515 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -24,6 +24,8 @@ import { AuthorizeResult, DefinitivePolicyDecision, IdentifiedPermissionMessage, + MetadataResponse as CommonMetadataResponse, + MetadataResponseSerializedRule as CommonMetadataResponseSerializedRule, Permission, PermissionCondition, PermissionCriteria, @@ -109,23 +111,18 @@ export type ApplyConditionsResponse = { * converted from a ZodSchema to a JsonSchema. * * @public + * @deprecated Please import from `@backstage/plugin-permission-common` instead. */ -export type MetadataResponseSerializedRule = { - name: string; - description: string; - resourceType: string; - paramsSchema?: ReturnType; -}; +export type MetadataResponseSerializedRule = + CommonMetadataResponseSerializedRule; /** * Response type for the .metadata endpoint. * * @public + * @deprecated Please import from `@backstage/plugin-permission-common` instead. */ -export type MetadataResponse = { - permissions?: Permission[]; - rules: MetadataResponseSerializedRule[]; -}; +export type MetadataResponse = CommonMetadataResponse; const applyConditions = ( criteria: PermissionCriteria>, 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..b92bf66ce8 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-permission-react +## 0.4.25 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + +## 0.4.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + +## 0.4.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.3 + +## 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..64f9082af9 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.25", "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..3db9b519fa 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,78 @@ # @backstage/plugin-proxy-backend +## 0.5.4 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- b63d378: Update internal imports +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 0.5.4-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 0.5.4-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + +## 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/api-report-alpha.md b/plugins/proxy-backend/api-report-alpha.md index bbd891b014..e6d161655d 100644 --- a/plugins/proxy-backend/api-report-alpha.md +++ b/plugins/proxy-backend/api-report-alpha.md @@ -3,10 +3,10 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @alpha -const _default: BackendFeatureCompat; +const _default: BackendFeature; export default _default; // (No @packageDocumentation comment for this package) diff --git a/plugins/proxy-backend/api-report.md b/plugins/proxy-backend/api-report.md index 12e6bfa1c9..9e1b5087f1 100644 --- a/plugins/proxy-backend/api-report.md +++ b/plugins/proxy-backend/api-report.md @@ -3,20 +3,20 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { Config } from '@backstage/config'; +import { DiscoveryService } from '@backstage/backend-plugin-api'; import express from 'express'; import { Logger } from 'winston'; -import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { RootConfigService } from '@backstage/backend-plugin-api'; -// @public +// @public @deprecated export function createRouter(options: RouterOptions): Promise; -// @public (undocumented) +// @public @deprecated (undocumented) export interface RouterOptions { // (undocumented) - config: Config; + config: RootConfigService; // (undocumented) - discovery: PluginEndpointDiscovery; + discovery: DiscoveryService; // (undocumented) logger: Logger; // (undocumented) diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 18ad338131..fdddaa6283 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", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin", @@ -79,7 +79,7 @@ "@types/uuid": "^9.0.0", "@types/yup": "^0.32.0", "msw": "^2.0.0", - "node-fetch": "^2.6.7" + "node-fetch": "^2.7.0" }, "configSchema": "config.d.ts" } 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..a48b18e8e2 100644 --- a/plugins/proxy-backend/src/service/router.credentials.test.ts +++ b/plugins/proxy-backend/src/service/router.credentials.test.ts @@ -14,13 +14,11 @@ * limitations under the License. */ -import { - authServiceFactory, - httpAuthServiceFactory, -} from '@backstage/backend-app-api'; +import { authServiceFactory } from '@backstage/backend-defaults/auth'; +import { httpAuthServiceFactory } from '@backstage/backend-defaults/httpAuth'; import { mockServices, - setupRequestMockHandlers, + registerMswTestHooks, startTestBackend, } from '@backstage/backend-test-utils'; import { ResponseError } from '@backstage/errors'; @@ -34,7 +32,7 @@ import fetch from 'node-fetch'; describe('credentials', () => { const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); it('handles all valid credentials settings', async () => { const config = { @@ -82,8 +80,8 @@ describe('credentials', () => { features: [ import('../alpha'), mockServices.rootConfig.factory({ data: config }), - authServiceFactory(), - httpAuthServiceFactory(), + authServiceFactory, + httpAuthServiceFactory, ], }); @@ -297,5 +295,5 @@ describe('credentials', () => { } finally { await backend.stop(); } - }); + }, 20_000); }); diff --git a/plugins/proxy-backend/src/service/router.ts b/plugins/proxy-backend/src/service/router.ts index 6a513678f0..9ea064ea55 100644 --- a/plugins/proxy-backend/src/service/router.ts +++ b/plugins/proxy-backend/src/service/router.ts @@ -25,9 +25,12 @@ import { } from 'http-proxy-middleware'; import { Logger } from 'winston'; import http from 'http'; -import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { JsonObject } from '@backstage/types'; -import { HttpRouterService } from '@backstage/backend-plugin-api'; +import { + DiscoveryService, + HttpRouterService, + RootConfigService, +} from '@backstage/backend-plugin-api'; // A list of headers that are always forwarded to the proxy targets. const safeForwardHeaders = [ @@ -50,11 +53,14 @@ const safeForwardHeaders = [ 'user-agent', ]; -/** @public */ +/** + * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. + */ export interface RouterOptions { logger: Logger; - config: Config; - discovery: PluginEndpointDiscovery; + config: RootConfigService; + discovery: DiscoveryService; skipInvalidProxies?: boolean; reviveConsumedRequestBodies?: boolean; } @@ -278,6 +284,7 @@ function readProxyConfig(config: Config, logger: Logger): JsonObject { * * @see https://backstage.io/docs/plugins/proxying * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export async function createRouter( options: RouterOptions, diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index 99ff7bd723..b6610a9638 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,85 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.1.15 + +### Patch Changes + +- 187f583: Added examples for publish:azure action and updated its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.15-next.3 + +### Patch Changes + +- 187f583: Added examples for publish:azure action and updated its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.1.15-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-azure/api-report.md index be5ab117e4..f61fff9b0f 100644 --- a/plugins/scaffolder-backend-module-azure/api-report.md +++ b/plugins/scaffolder-backend-module-azure/api-report.md @@ -3,14 +3,14 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public -const azureModule: BackendFeatureCompat; +const azureModule: BackendFeature; export default azureModule; // @public diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index 672689ef73..43cdd10a96 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", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.test.ts b/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.test.ts index 35868274fd..dda65a76ea 100644 --- a/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.test.ts +++ b/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.test.ts @@ -19,7 +19,10 @@ import { ConfigReader } from '@backstage/config'; import { createPublishAzureAction } from './azure'; import { ScmIntegrations } from '@backstage/integration'; import { WebApi } from 'azure-devops-node-api'; -import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; +import { + getRepoSourceDirectory, + initRepoAndPush, +} from '@backstage/plugin-scaffolder-node'; import { examples } from './azure.examples'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; @@ -48,6 +51,10 @@ describe('publish:azure examples', () => { host: 'dev.azure.com', credentials: [{ personalAccessToken: 'tokenlols' }], }, + { + host: 'test.azure.com', + credentials: [{ personalAccessToken: 'tokenlols' }], + }, ], }, }); @@ -114,4 +121,172 @@ describe('publish:azure examples', () => { gitAuthorInfo: {}, }); }); + + it(`should ${examples[3].description}`, async () => { + mockGitClient.createRepository.mockResolvedValue({ + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + webUrl: 'https://dev.azure.com/organization/project/_git/repo', + id: '709e891c-dee7-4f91-b963-534713c0737f', + }); + + 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(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + defaultBranch: 'master', + auth: { username: 'notempty', password: 'tokenlols' }, + logger: mockContext.logger, + commitMessage: input.gitCommitMessage, + gitAuthorInfo: {}, + }); + }); + + it(`should ${examples[4].description}`, async () => { + mockGitClient.createRepository.mockResolvedValue({ + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + webUrl: 'https://dev.azure.com/organization/project/_git/repo', + id: '709e891c-dee7-4f91-b963-534713c0737f', + }); + + 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(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + defaultBranch: 'master', + auth: { username: 'notempty', password: 'tokenlols' }, + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: { + name: input.gitAuthorName, + email: input.gitAuthorEmail, + }, + }); + }); + + it(`should ${examples[5].description}`, async () => { + mockGitClient.createRepository.mockResolvedValue({ + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + webUrl: 'https://dev.azure.com/organization/project/_git/repo', + id: '709e891c-dee7-4f91-b963-534713c0737f', + }); + + 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(initRepoAndPush).toHaveBeenCalledWith({ + dir: getRepoSourceDirectory(mockContext.workspacePath, input.sourcePath), + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + defaultBranch: 'master', + auth: { username: 'notempty', password: 'tokenlols' }, + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: {}, + }); + }); + + it(`should ${examples[6].description}`, async () => { + mockGitClient.createRepository.mockResolvedValue({ + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + webUrl: 'https://dev.azure.com/organization/project/_git/repo', + id: '709e891c-dee7-4f91-b963-534713c0737f', + }); + + 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(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://dev.azure.com/organization/project/_git/repo', + defaultBranch: 'master', + auth: { username: 'notempty', password: input.token }, + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: {}, + }); + }); + + it(`should ${examples[7].description}`, async () => { + mockGitClient.createRepository.mockResolvedValue({ + remoteUrl: 'https://test.azure.com/organization/project/_git/repo', + webUrl: 'https://test.azure.com/organization/project/_git/repo', + id: '709e891c-dee7-4f91-b963-534713c0737f', + }); + + 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(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://test.azure.com/organization/project/_git/repo', + defaultBranch: 'master', + auth: { username: 'notempty', password: 'tokenlols' }, + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: {}, + }); + }); }); diff --git a/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.ts b/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.ts index 22c2299b30..ae5bfece2b 100644 --- a/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.ts +++ b/plugins/scaffolder-backend-module-azure/src/actions/azure.examples.ts @@ -70,4 +70,94 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: + 'Initializes an Azure DevOps repository with a custom commit message', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:azure', + name: 'Publish to Azure', + input: { + repoUrl: + 'dev.azure.com?organization=organization&project=project&repo=repo', + gitCommitMessage: 'Initial setup and configuration', + }, + }, + ], + }), + }, + { + description: + 'Initializes an Azure DevOps repository with a custom author name and email', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:azure', + name: 'Publish to Azure', + input: { + repoUrl: + 'dev.azure.com?organization=organization&project=project&repo=repo', + gitAuthorName: 'John Doe', + gitAuthorEmail: 'john.doe@example.com', + }, + }, + ], + }), + }, + { + description: + 'Initializes an Azure DevOps repository using a specific source path', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:azure', + name: 'Publish to Azure', + input: { + repoUrl: + 'dev.azure.com?organization=organization&project=project&repo=repo', + sourcePath: 'path/to/source', + }, + }, + ], + }), + }, + { + description: + 'Initializes an Azure DevOps repository using an authentication token', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:azure', + name: 'Publish to Azure', + input: { + repoUrl: + 'dev.azure.com?organization=organization&project=project&repo=repo', + token: 'personal-access-token', + }, + }, + ], + }), + }, + { + description: + 'Initializes an Azure DevOps repository using an custom repo url', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:azure', + name: 'Publish to Azure', + input: { + repoUrl: + 'test.azure.com?organization=organization&project=project&repo=repo', + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index f4453bab7a..dfec3e29aa 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,96 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.1.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 3fca643: Added autocompletion support for resource `branches` +- d57967c: Add ability to set the initial commit message when initializing a repository using the scaffolder action. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.13-next.3 + +### Patch Changes + +- d57967c: Add ability to set the initial commit message when initializing a repository using the scaffolder action. +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.1.13-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 3fca643: Added autocompletion support for resource `branches` +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-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.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-bitbucket-cloud/api-report.md index a484f49f77..90a8cbc29f 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/api-report.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/api-report.md @@ -3,14 +3,14 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public -const bitbucketCloudModule: BackendFeatureCompat; +const bitbucketCloudModule: BackendFeature; export default bitbucketCloudModule; // @public @@ -36,6 +36,7 @@ export function createPublishBitbucketCloudAction(options: { description?: string | undefined; defaultBranch?: string | undefined; repoVisibility?: 'private' | 'public' | undefined; + gitCommitMessage?: string | undefined; sourcePath?: string | undefined; token?: string | undefined; }, diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index d4432ee3d5..cfedc6048a 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", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", @@ -49,7 +49,7 @@ "@backstage/plugin-bitbucket-cloud-common": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "fs-extra": "^11.2.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "yaml": "^2.0.0" }, "devDependencies": { 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/bitbucketCloud.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts index 99e8e52a43..71eacea955 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts @@ -111,6 +111,7 @@ export function createPublishBitbucketCloudAction(options: { description?: string; defaultBranch?: string; repoVisibility?: 'private' | 'public'; + gitCommitMessage?: string; sourcePath?: string; token?: string; }>({ @@ -141,6 +142,11 @@ export function createPublishBitbucketCloudAction(options: { type: 'string', description: `Sets the default branch on the repository. The default value is 'master'`, }, + gitCommitMessage: { + title: 'Git Commit Message', + type: 'string', + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }, sourcePath: { title: 'Source Path', description: @@ -178,6 +184,7 @@ export function createPublishBitbucketCloudAction(options: { repoUrl, description, defaultBranch = 'master', + gitCommitMessage, repoVisibility = 'private', } = ctx.input; @@ -256,9 +263,9 @@ export function createPublishBitbucketCloudAction(options: { auth, defaultBranch, logger: ctx.logger, - commitMessage: config.getOptionalString( - 'scaffolder.defaultCommitMessage', - ), + commitMessage: + gitCommitMessage || + config.getOptionalString('scaffolder.defaultCommitMessage'), gitAuthorInfo, }); 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-cloud/src/autocomplete/autocomplete.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/autocomplete/autocomplete.test.ts index 0447ff2d28..d99a8358b9 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/autocomplete/autocomplete.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/autocomplete/autocomplete.test.ts @@ -35,6 +35,11 @@ describe('handleAutocompleteRequest', () => { .fn() .mockReturnValue([{ values: [{ slug: 'repository1' }] }]), }), + listBranchesByRepository: jest.fn().mockReturnValue({ + iteratePages: jest + .fn() + .mockReturnValue([{ values: [{ name: 'branch1' }] }]), + }), }; const fromConfig = jest @@ -89,6 +94,19 @@ describe('handleAutocompleteRequest', () => { expect(result).toEqual({ results: [{ title: 'repository1' }] }); }); + it('should return branches', async () => { + const result = await handleAutocompleteRequest({ + token: 'foo', + resource: 'branches', + context: { + workspace: 'workspace1', + repository: 'repository1', + }, + }); + + expect(result).toEqual({ results: [{ title: 'branch1' }] }); + }); + it('should throw an error when passing an invalid resource', async () => { await expect( handleAutocompleteRequest({ diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/autocomplete/autocomplete.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/autocomplete/autocomplete.ts index 31a2103088..75cc170a30 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/autocomplete/autocomplete.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/autocomplete/autocomplete.ts @@ -34,29 +34,29 @@ export async function handleAutocompleteRequest({ switch (resource) { case 'workspaces': { - const result: string[] = []; + const results: { title: string }[] = []; for await (const page of client.listWorkspaces().iteratePages()) { - const slugs = [...page.values!].map(p => p.slug!); - result.push(...slugs); + const slugs = [...page.values!].map(p => ({ title: p.slug! })); + results.push(...slugs); } - return { results: result.map(title => ({ title })) }; + return { results }; } case 'projects': { if (!context.workspace) throw new InputError('Missing workspace context parameter'); - const result: string[] = []; + const results: { title: string }[] = []; for await (const page of client .listProjectsByWorkspace(context.workspace) .iteratePages()) { - const keys = [...page.values!].map(p => p.key!); - result.push(...keys); + const keys = [...page.values!].map(p => ({ title: p.key! })); + results.push(...keys); } - return { results: result.map(title => ({ title })) }; + return { results }; } case 'repositories': { if (!context.workspace || !context.project) @@ -64,18 +64,35 @@ export async function handleAutocompleteRequest({ 'Missing workspace and/or project context parameter', ); - const result: string[] = []; + const results: { title: string }[] = []; for await (const page of client .listRepositoriesByWorkspace(context.workspace, { q: `project.key="${context.project}"`, }) .iteratePages()) { - const slugs = [...page.values!].map(p => p.slug!); - result.push(...slugs); + const slugs = [...page.values!].map(p => ({ title: p.slug! })); + results.push(...slugs); } - return { results: result.map(title => ({ title })) }; + return { results }; + } + case 'branches': { + if (!context.workspace || !context.repository) + throw new InputError( + 'Missing workspace and/or repository context parameter', + ); + + const results: { title: string }[] = []; + + for await (const page of client + .listBranchesByRepository(context.repository, context.workspace) + .iteratePages()) { + const names = [...page.values!].map(p => ({ title: p.name! })); + results.push(...names); + } + + return { results }; } default: throw new InputError(`Invalid resource: ${resource}`); diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index f614b16994..8c12baad68 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,88 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.1.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- e3b64be: Added examples for publish:bitbucketServer action and improve its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.1.13-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.13-next.1 + +### Patch Changes + +- e3b64be: Added examples for publish:bitbucketServer action and improve its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-bitbucket-server/api-report.md index 50377f8db2..d103743b91 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/api-report.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/api-report.md @@ -3,14 +3,14 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public -const bitbucketServerModule: BackendFeatureCompat; +const bitbucketServerModule: BackendFeature; export default bitbucketServerModule; // @public diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index 16ce106570..76d00d0ea3 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", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", @@ -48,7 +48,7 @@ "@backstage/integration": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "fs-extra": "^11.2.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "yaml": "^2.0.0" }, "devDependencies": { 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..8e222a6876 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,12 +29,15 @@ 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'; import { examples } from './bitbucketServer.examples'; -import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; +import { + getRepoSourceDirectory, + initRepoAndPush, +} from '@backstage/plugin-scaffolder-node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; describe('publish:bitbucketServer', () => { @@ -68,7 +71,7 @@ describe('publish:bitbucketServer', () => { }, }); const server = setupServer(); - setupRequestMockHandlers(server); + registerMswTestHooks(server); beforeEach(() => { jest.resetAllMocks(); @@ -266,7 +269,8 @@ describe('publish:bitbucketServer', () => { it(`should ${examples[3].description}`, async () => { expect.assertions(3); - server.use( + + const handlers = [ rest.post( 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', (req, res, ctx) => { @@ -300,7 +304,35 @@ describe('publish:bitbucketServer', () => { ); }, ), - ); + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); await action.handler({ ...mockContext, @@ -334,7 +366,8 @@ describe('publish:bitbucketServer', () => { it(`should ${examples[4].description}`, async () => { expect.assertions(3); - server.use( + + const handlers = [ rest.post( 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', (req, res, ctx) => { @@ -368,7 +401,35 @@ describe('publish:bitbucketServer', () => { ); }, ), - ); + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); await action.handler({ ...mockContext, @@ -399,4 +460,1050 @@ describe('publish:bitbucketServer', () => { gitAuthorInfo: { name: 'Test User', email: 'test.user@example.com' }, }); }); + it(`should ${examples[5].description}`, async () => { + expect.assertions(3); + + const handlers = [ + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe(`Bearer thing`); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); + + 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, + }, + }); + + // Ensure that the initRepoAndPush function was called with the correct remoteUrl and input properties + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://bitbucket.mycompany.com/scm/project/repo', + defaultBranch: 'master', + auth: { token: 'thing' }, + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: { + name: 'Custom Author', + email: 'custom.author@example.com', + }, + }); + }); + + it(`should ${examples[6].description}`, async () => { + expect.assertions(5); + + const handlers = [ + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + { + href: 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); + + 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(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://bitbucket.mycompany.com/scm/project/repo', + defaultBranch: 'master', + auth: { token: 'thing' }, + logger: mockContext.logger, + commitMessage: 'Initial commit with LFS enabled', + gitAuthorInfo: { + email: input.gitAuthorName, + name: input.gitAuthorEmail, + }, + }); + }); + + it(`should ${examples[7].description}`, async () => { + expect.assertions(3); + + const handlers = [ + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer custom-token'); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + { + href: 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); + + 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(initRepoAndPush).toHaveBeenCalledWith({ + dir: getRepoSourceDirectory(mockContext.workspacePath, input.sourcePath), + remoteUrl: 'https://bitbucket.mycompany.com/scm/project/repo', + auth: { token: 'custom-token' }, + defaultBranch: 'master', + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: { name: undefined, email: undefined }, + }); + }); + + it(`should ${examples[8].description}`, async () => { + expect.assertions(3); + + const handlers = [ + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + { + href: 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); + + 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(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://bitbucket.mycompany.com/scm/project/repo', + auth: { token: 'thing' }, + defaultBranch: 'master', + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: { name: input.gitAuthorName, email: input.gitAuthorEmail }, + }); + }); + + it(`should ${examples[9].description}`, async () => { + expect.assertions(3); + + const handlers = [ + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual({ + public: true, + name: 'repo', + defaultBranch: 'master', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + { + href: 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); + + 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(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://bitbucket.mycompany.com/scm/project/repo', + auth: { token: 'thing' }, + defaultBranch: 'master', + logger: mockContext.logger, + commitMessage: 'Public repository initial commit', + gitAuthorInfo: { name: input.gitAuthorName, email: input.gitAuthorEmail }, + }); + }); + + it(`should ${examples[10].description}`, async () => { + expect.assertions(5); + + const handlers = [ + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer custom-token'); + expect(req.body).toEqual({ + public: false, + name: 'repo', + description: 'A fully customized repository', + defaultBranch: 'development', + }); + + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + { + href: 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer custom-token'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); + + 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(initRepoAndPush).toHaveBeenCalledWith({ + dir: getRepoSourceDirectory(mockContext.workspacePath, input.sourcePath), + remoteUrl: 'https://bitbucket.mycompany.com/scm/project/repo', + auth: { token: input.token }, + defaultBranch: input.defaultBranch, + logger: mockContext.logger, + commitMessage: input.gitCommitMessage, + gitAuthorInfo: { name: input.gitAuthorName, email: input.gitAuthorEmail }, + }); + }); + + it(`should ${examples[11].description}`, async () => { + expect.assertions(3); + + const handlers = [ + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'main', + }); + + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + { + href: 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer custom-token'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); + + 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(initRepoAndPush).toHaveBeenCalledWith({ + dir: input.sourcePath + ? getRepoSourceDirectory(mockContext.workspacePath, input.sourcePath) + : mockContext.workspacePath, + remoteUrl: 'https://bitbucket.mycompany.com/scm/project/repo', + auth: { token: input.token ?? 'thing' }, + defaultBranch: input.defaultBranch, + logger: mockContext.logger, + commitMessage: input.gitCommitMessage ?? 'initial commit', + gitAuthorInfo: { name: input.gitAuthorName, email: input.gitAuthorEmail }, + }); + }); + + it(`should ${examples[12].description}`, async () => { + expect.assertions(3); + + const handlers = [ + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual({ + public: true, + name: 'repo', + defaultBranch: 'master', + description: 'A public repository with a custom description', + }); + + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + { + href: 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer custom-token'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); + + let input; + try { + input = yaml.parse(examples[12].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: input.sourcePath + ? getRepoSourceDirectory(mockContext.workspacePath, input.sourcePath) + : mockContext.workspacePath, + remoteUrl: 'https://bitbucket.mycompany.com/scm/project/repo', + auth: { token: input.token ?? 'thing' }, + defaultBranch: input.defaultBranch ?? 'master', + logger: mockContext.logger, + commitMessage: input.gitCommitMessage ?? 'initial commit', + gitAuthorInfo: { name: input.gitAuthorName, email: input.gitAuthorEmail }, + }); + }); + + it(`should ${examples[13].description}`, async () => { + expect.assertions(3); + + const handlers = [ + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + 'Bearer custom-auth-token', + ); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); + + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + { + href: 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer custom-token'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); + + let input; + try { + input = yaml.parse(examples[13].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: input.sourcePath + ? getRepoSourceDirectory(mockContext.workspacePath, input.sourcePath) + : mockContext.workspacePath, + remoteUrl: 'https://bitbucket.mycompany.com/scm/project/repo', + auth: { token: input.token ?? 'thing' }, + defaultBranch: input.defaultBranch ?? 'master', + logger: mockContext.logger, + commitMessage: input.gitCommitMessage ?? 'initial commit', + gitAuthorInfo: { name: input.gitAuthorName, email: input.gitAuthorEmail }, + }); + }); + + it(`should ${examples[14].description}`, async () => { + expect.assertions(3); + + const handlers = [ + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); + + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + { + href: 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer custom-token'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); + + let input; + try { + input = yaml.parse(examples[14].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: input.sourcePath + ? getRepoSourceDirectory(mockContext.workspacePath, input.sourcePath) + : mockContext.workspacePath, + remoteUrl: 'https://bitbucket.mycompany.com/scm/project/repo', + auth: { token: input.token ?? 'thing' }, + defaultBranch: input.defaultBranch ?? 'master', + logger: mockContext.logger, + commitMessage: input.gitCommitMessage ?? 'initial commit', + gitAuthorInfo: { name: input.gitAuthorName, email: input.gitAuthorEmail }, + }); + }); + + it(`should ${examples[15].description}`, async () => { + expect.assertions(5); + + const handlers = [ + rest.post( + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); + + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + { + href: 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + rest.put( + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer thing'); + expect(req.body).toEqual(''); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + self: [ + { + href: 'https://bitbucket.mycompany.com/projects/project/repos/repo', + }, + ], + clone: [ + { + name: 'http', + href: 'https://bitbucket.mycompany.com/scm/project/repo', + }, + ], + }, + }), + ); + }, + ), + ]; + + server.use(...handlers); + + let input; + try { + input = yaml.parse(examples[15].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: input.sourcePath + ? getRepoSourceDirectory(mockContext.workspacePath, input.sourcePath) + : mockContext.workspacePath, + remoteUrl: 'https://bitbucket.mycompany.com/scm/project/repo', + auth: { token: input.token ?? 'thing' }, + defaultBranch: input.defaultBranch ?? 'master', + logger: mockContext.logger, + commitMessage: input.gitCommitMessage ?? 'initial commit', + gitAuthorInfo: { name: input.gitAuthorName, email: input.gitAuthorEmail }, + }); + }); }); diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.examples.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.examples.ts index b3a68eed57..e28f9ca48c 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.examples.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.examples.ts @@ -129,4 +129,207 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: + 'Initialize git repository with default settings and custom author information', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer', + id: 'publish-bitbucket-server-custom-author', + name: 'Publish To Bitbucket Server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + gitAuthorName: 'Custom Author', + gitAuthorEmail: 'custom.author@example.com', + }, + }, + ], + }), + }, + { + description: + 'Initialize git repository with LFS enabled and a specific commit message', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer', + id: 'publish-bitbucket-server-lfs-commit', + name: 'Publish To Bitbucket Server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + enableLFS: true, + gitCommitMessage: 'Initial commit with LFS enabled', + }, + }, + ], + }), + }, + { + description: + 'Initialize git repository with a custom source path and token', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer', + id: 'publish-bitbucket-server-custom-source', + name: 'Publish To Bitbucket Server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + sourcePath: 'custom/source/path', + token: 'custom-token', + }, + }, + ], + }), + }, + { + description: + 'Initialize git repository with private visibility and custom author details', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer', + id: 'publish-bitbucket-server-private-custom-author', + name: 'Publish To Bitbucket Server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + repoVisibility: 'private', + gitAuthorName: 'Private Author', + gitAuthorEmail: 'private.author@example.com', + }, + }, + ], + }), + }, + { + description: + 'Initialize git repository with public visibility and specific commit message', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer', + id: 'publish-bitbucket-server-public-commit', + name: 'Publish To Bitbucket Server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + repoVisibility: 'public', + gitCommitMessage: 'Public repository initial commit', + }, + }, + ], + }), + }, + { + description: 'Initialize git repository with all settings customized', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer', + id: 'publish-bitbucket-server-all-custom', + name: 'Publish To Bitbucket Server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + description: 'A fully customized repository', + repoVisibility: 'private', + defaultBranch: 'development', + sourcePath: 'src/backend', + enableLFS: true, + token: 'custom-token', + gitCommitMessage: 'Fully customized initial commit', + gitAuthorName: 'Custom Dev', + gitAuthorEmail: 'custom.dev@example.com', + }, + }, + ], + }), + }, + { + description: + 'Initialize git repository with a specific default branch and no LFS', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer', + id: 'publish-bitbucket-server-no-lfs', + name: 'Publish To Bitbucket Server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + defaultBranch: 'main', + enableLFS: false, + }, + }, + ], + }), + }, + { + description: + 'Initialize git repository with a custom repository description and public visibility', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer', + id: 'publish-bitbucket-server-custom-description', + name: 'Publish To Bitbucket Server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + description: 'A public repository with a custom description', + repoVisibility: 'public', + }, + }, + ], + }), + }, + { + description: + 'Initialize git repository with a custom token for authentication', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer', + id: 'publish-bitbucket-server-custom-token', + name: 'Publish To Bitbucket Server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + token: 'custom-auth-token', + }, + }, + ], + }), + }, + { + description: + 'Initialize git repository with a different repository root path', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer', + id: 'publish-bitbucket-server-different-root', + name: 'Publish To Bitbucket Server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + sourcePath: 'different/root/path', + }, + }, + ], + }), + }, + { + description: + 'Initialize git repository with private visibility and LFS enabled', + example: yaml.stringify({ + steps: [ + { + action: 'publish:bitbucketServer', + id: 'publish-bitbucket-server-private-lfs', + name: 'Publish To Bitbucket Server', + input: { + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', + repoVisibility: 'private', + enableLFS: true, + }, + }, + ], + }), + }, ]; 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..5f36050fff 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,98 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.2.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.2.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.3 + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.3 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.2.13-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-bitbucket/api-report.md index db73dcbf2c..952b1665fb 100644 --- a/plugins/scaffolder-backend-module-bitbucket/api-report.md +++ b/plugins/scaffolder-backend-module-bitbucket/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import * as bitbucketCloud from '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud'; import * as bitbucketServer from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; import { Config } from '@backstage/config'; @@ -12,7 +12,7 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public @deprecated -const bitbucketModule: BackendFeatureCompat; +const bitbucketModule: BackendFeature; export default bitbucketModule; // @public @deprecated (undocumented) diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index 35b23e39d9..d222e24fc3 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", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", @@ -50,7 +50,7 @@ "@backstage/plugin-scaffolder-backend-module-bitbucket-server": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "fs-extra": "^11.2.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "yaml": "^2.0.0" }, "devDependencies": { 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..3fbba8f6da 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,92 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.2.24 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.2.24-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.2.24-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.2.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-confluence-to-markdown/api-report.md index b56ade73a2..2dd5e244dd 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/api-report.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/api-report.md @@ -3,20 +3,20 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public -const confluenceToMarkdownModule: BackendFeatureCompat; +const confluenceToMarkdownModule: BackendFeature; export default confluenceToMarkdownModule; // @public (undocumented) export const createConfluenceToMarkdownAction: (options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; config: Config; }) => TemplateAction< diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 34e49f740f..a85e032cea 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", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", @@ -53,7 +53,7 @@ "@backstage/plugin-scaffolder-node": "workspace:^", "fs-extra": "^11.2.0", "git-url-parse": "^14.0.0", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "node-html-markdown": "^1.3.0", "yaml": "^2.0.0" }, 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..5cf8969b55 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 @@ -14,13 +14,13 @@ * limitations under the License. */ import { createConfluenceToMarkdownAction } from './confluenceToMarkdown'; -import { UrlReader, loggerToWinstonLogger } from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createMockDirectory, mockServices, - setupRequestMockHandlers, + registerMswTestHooks, } from '@backstage/backend-test-utils'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -28,11 +28,12 @@ import { examples } from './confluenceToMarkdown.examples'; import yaml from 'yaml'; import { ActionContext } from '@backstage/plugin-scaffolder-node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('confluence:transform:markdown examples', () => { const baseUrl = `https://confluence.example.com`; const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); const config = new ConfigReader({ confluence: { @@ -51,7 +52,7 @@ describe('confluence:transform:markdown examples', () => { }), ); - let reader: UrlReader; + let reader: UrlReaderService; let mockContext: ActionContext<{ confluenceUrls: string[]; repoUrl: string; 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..a54e5dcc63 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 @@ -15,23 +15,24 @@ */ import { createConfluenceToMarkdownAction } from './confluenceToMarkdown'; -import { UrlReader, loggerToWinstonLogger } from '@backstage/backend-common'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; 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'; import { setupServer } from 'msw/node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('confluence:transform:markdown', () => { const baseUrl = `https://nodomain.confluence.com`; const worker = setupServer(); - setupRequestMockHandlers(worker); + registerMswTestHooks(worker); const config = new ConfigReader({ confluence: { @@ -50,7 +51,7 @@ describe('confluence:transform:markdown', () => { }), ); - let reader: UrlReader; + let reader: UrlReaderService; let mockContext: ActionContext<{ confluenceUrls: string[]; repoUrl: string; diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts index e88f42e39c..aa9f94988a 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/src/actions/confluence/confluenceToMarkdown.ts @@ -15,7 +15,6 @@ */ import { Config } from '@backstage/config'; -import { UrlReader } from '@backstage/backend-common'; import { ScmIntegrations } from '@backstage/integration'; import { createTemplateAction, @@ -34,13 +33,14 @@ import { getConfluenceConfig, } from './helpers'; import { examples } from './confluenceToMarkdown.examples'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; /** * @public */ export const createConfluenceToMarkdownAction = (options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; config: Config; }) => { diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index dfa740b4a6..38b4185f16 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,101 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.47 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- dae85df: Add examples for `fetch:cookiecutter` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.47-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.2.47-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.2.47-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-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.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-cookiecutter/api-report.md index 2dbf462063..19f43389dc 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/api-report.md +++ b/plugins/scaffolder-backend-module-cookiecutter/api-report.md @@ -5,20 +5,20 @@ ```ts /// -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { ContainerRunner } from '@backstage/backend-common'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public -const cookiecutterModule: BackendFeatureCompat; +const cookiecutterModule: BackendFeature; export default cookiecutterModule; // @public export function createFetchCookiecutterAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; containerRunner?: ContainerRunner; }): TemplateAction< diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index ea0a7fad77..4e30c0cf98 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", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module", @@ -44,6 +44,7 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", @@ -53,6 +54,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..73c17d8ac0 --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.examples.test.ts @@ -0,0 +1,275 @@ +/* + * 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 { 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'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; + +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: UrlReaderService = { + 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.test.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts index fad544488a..52946ce7fd 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader, ContainerRunner } from '@backstage/backend-common'; +import { ContainerRunner } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; @@ -24,6 +24,7 @@ import { join } from 'path'; import type { ActionContext } from '@backstage/plugin-scaffolder-node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { Writable } from 'stream'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; const executeShellCommand = jest.fn(); const commandExists = jest.fn(); @@ -70,7 +71,7 @@ describe('fetch:cookiecutter', () => { runContainer: jest.fn(), }; - const mockReader: UrlReader = { + const mockReader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), 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..6e540d7fca 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts @@ -14,8 +14,11 @@ * limitations under the License. */ -import { ContainerRunner, UrlReader } from '@backstage/backend-common'; -import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; +import { ContainerRunner } from '@backstage/backend-common'; +import { + UrlReaderService, + resolveSafeChildPath, +} from '@backstage/backend-plugin-api'; import { JsonObject, JsonValue } from '@backstage/types'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; @@ -28,6 +31,7 @@ import { fetchContents, executeShellCommand, } from '@backstage/plugin-scaffolder-node'; +import { examples } from './cookiecutter.examples'; export class CookiecutterRunner { private readonly containerRunner?: ContainerRunner; @@ -139,7 +143,7 @@ export class CookiecutterRunner { * @public */ export function createFetchCookiecutterAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; containerRunner?: ContainerRunner; }) { @@ -156,6 +160,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..46c3fe7957 100644 --- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,86 @@ # @backstage/plugin-scaffolder-backend-module-gcp +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-gcp/api-report.md index d75a6ce155..8928792ba9 100644 --- a/plugins/scaffolder-backend-module-gcp/api-report.md +++ b/plugins/scaffolder-backend-module-gcp/api-report.md @@ -3,9 +3,9 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @public -const gcpBucketModule: BackendFeatureCompat; +const gcpBucketModule: BackendFeature; export default gcpBucketModule; ``` diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index 2f8f130d39..71f16f4b96 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", "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..7db14044e9 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,84 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.1.15 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.15-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.1.15-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-gerrit/api-report.md index 60cd91355f..128f80954e 100644 --- a/plugins/scaffolder-backend-module-gerrit/api-report.md +++ b/plugins/scaffolder-backend-module-gerrit/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; @@ -43,6 +43,6 @@ export function createPublishGerritReviewAction(options: { >; // @public -const gerritModule: BackendFeatureCompat; +const gerritModule: BackendFeature; export default gerritModule; ``` diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 06c5d063d4..3557ac6719 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", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", @@ -47,7 +47,7 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "yaml": "^2.0.0" }, "devDependencies": { diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.test.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.test.ts new file mode 100644 index 0000000000..16ffd198fb --- /dev/null +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.test.ts @@ -0,0 +1,590 @@ +/* + * 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. + */ + +jest.mock('@backstage/plugin-scaffolder-node', () => { + return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), + initRepoAndPush: jest.fn().mockResolvedValue({ + commitHash: '460f19cc36b551763d157f1b5e4a4b446165dbb8', + }), + commitAndPushRepo: jest.fn().mockResolvedValue({ + commitHash: '460f19cc36b551763d157f1b5e4a4b446165dbb8', + }), + }; +}); + +import path from 'path'; +import { createPublishGerritAction } from './gerrit'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import { initRepoAndPush } from '@backstage/plugin-scaffolder-node'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { examples } from './gerrit.examples'; +import yaml from 'yaml'; + +describe('publish:gerrit', () => { + const config = new ConfigReader({ + integrations: { + gerrit: [ + { + host: 'gerrit.com', + gitilesBaseUrl: 'https://gerrit.com/gitiles', + username: 'demouser', + password: 'accesstoken', + }, + ], + }, + }); + + const description = 'sample description'; + const integrations = ScmIntegrations.fromConfig(config); + const action = createPublishGerritAction({ integrations, config }); + const mockContext = createMockActionContext({ + input: { + repoUrl: + 'gerrit.com?owner=owner&workspace=parent&project=project&repo=repo', + description, + }, + }); + const server = setupServer(); + registerMswTestHooks(server); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it(`should ${examples[0].description}`, async () => { + expect.assertions(5); + server.use( + rest.put('https://gerrit.com/a/projects/repo', (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + 'Basic ZGVtb3VzZXI6YWNjZXNzdG9rZW4=', + ); + expect(req.body).toEqual({ + branches: ['master'], + create_empty_commit: false, + owners: ['owner'], + description, + parent: 'parent', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + const suffixPath = '&workspace=parent&project=test-project'; + 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, + repoUrl: input.repoUrl + suffixPath, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gerrit.com/a/repo', + defaultBranch: 'master', + auth: { username: 'demouser', password: 'accesstoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: {}, + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://gerrit.com/a/repo', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrit.com/gitiles/repo/+/refs/heads/master', + ); + }); + + it(`should ${examples[1].description}`, async () => { + expect.assertions(5); + server.use( + rest.put('https://gerrit.com/a/projects/repo', (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + 'Basic ZGVtb3VzZXI6YWNjZXNzdG9rZW4=', + ); + expect(req.body).toEqual({ + branches: ['master'], + create_empty_commit: false, + owners: ['owner'], + description: yaml.parse(examples[1].example).steps[0].input + .description, + parent: 'parent', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + const suffixPath = '&workspace=parent&project=test-project'; + 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, + repoUrl: input.repoUrl + suffixPath, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gerrit.com/a/repo', + defaultBranch: 'master', + auth: { username: 'demouser', password: 'accesstoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: {}, + }); + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://gerrit.com/a/repo', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrit.com/gitiles/repo/+/refs/heads/master', + ); + }); + + it(`should ${examples[2].description}`, async () => { + expect.assertions(5); + server.use( + rest.put('https://gerrit.com/a/projects/repo', (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + 'Basic ZGVtb3VzZXI6YWNjZXNzdG9rZW4=', + ); + expect(req.body).toEqual({ + branches: ['staging'], + create_empty_commit: false, + owners: ['owner'], + description, + parent: 'parent', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + const suffixPath = '&workspace=parent&project=test-project'; + 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, + repoUrl: input.repoUrl + suffixPath, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gerrit.com/a/repo', + defaultBranch: 'staging', + auth: { username: 'demouser', password: 'accesstoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: {}, + }); + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://gerrit.com/a/repo', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrit.com/gitiles/repo/+/refs/heads/staging', + ); + }); + + it(`should ${examples[3].description}`, async () => { + expect.assertions(5); + server.use( + rest.put('https://gerrit.com/a/projects/repo', (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + 'Basic ZGVtb3VzZXI6YWNjZXNzdG9rZW4=', + ); + expect(req.body).toEqual({ + branches: ['master'], + create_empty_commit: false, + owners: ['owner'], + description, + parent: 'parent', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + const suffixPath = '&workspace=parent&project=test-project'; + 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, + repoUrl: input.repoUrl + suffixPath, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gerrit.com/a/repo', + defaultBranch: 'master', + auth: { username: 'demouser', password: 'accesstoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining( + 'Initial Commit Message\n\nChange-Id:', + ), + gitAuthorInfo: {}, + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://gerrit.com/a/repo', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrit.com/gitiles/repo/+/refs/heads/master', + ); + }); + + it(`should ${examples[4].description}`, async () => { + expect.assertions(5); + server.use( + rest.put('https://gerrit.com/a/projects/repo', (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + 'Basic ZGVtb3VzZXI6YWNjZXNzdG9rZW4=', + ); + expect(req.body).toEqual({ + branches: ['master'], + create_empty_commit: false, + owners: ['owner'], + description, + parent: 'parent', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + const suffixPath = '&workspace=parent&project=test-project'; + 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, + repoUrl: input.repoUrl + suffixPath, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gerrit.com/a/repo', + defaultBranch: 'master', + auth: { username: 'demouser', password: 'accesstoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: { name: 'John Doe' }, + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://gerrit.com/a/repo', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrit.com/gitiles/repo/+/refs/heads/master', + ); + }); + + it(`should ${examples[5].description}`, async () => { + expect.assertions(5); + server.use( + rest.put('https://gerrit.com/a/projects/repo', (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + 'Basic ZGVtb3VzZXI6YWNjZXNzdG9rZW4=', + ); + expect(req.body).toEqual({ + branches: ['master'], + create_empty_commit: false, + owners: ['owner'], + description, + parent: 'parent', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + const suffixPath = '&workspace=parent&project=test-project'; + 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, + repoUrl: input.repoUrl + suffixPath, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gerrit.com/a/repo', + defaultBranch: 'master', + auth: { username: 'demouser', password: 'accesstoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: { email: 'johndoe@email.com' }, + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://gerrit.com/a/repo', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrit.com/gitiles/repo/+/refs/heads/master', + ); + }); + + it(`should ${examples[6].description}`, async () => { + expect.assertions(5); + server.use( + rest.put('https://gerrit.com/a/projects/repo', (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + 'Basic ZGVtb3VzZXI6YWNjZXNzdG9rZW4=', + ); + expect(req.body).toEqual({ + branches: ['master'], + create_empty_commit: false, + owners: ['owner'], + description, + parent: 'parent', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + const suffixPath = '&workspace=parent&project=test-project'; + 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, + repoUrl: input.repoUrl + suffixPath, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: `${mockContext.workspacePath}${path.sep}repository${path.sep}`, + remoteUrl: 'https://gerrit.com/a/repo', + defaultBranch: 'master', + auth: { username: 'demouser', password: 'accesstoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: {}, + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://gerrit.com/a/repo', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrit.com/gitiles/repo/+/refs/heads/master', + ); + }); + + it(`should ${examples[7].description}`, async () => { + expect.assertions(5); + server.use( + rest.put('https://gerrit.com/a/projects/repo', (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + 'Basic ZGVtb3VzZXI6YWNjZXNzdG9rZW4=', + ); + expect(req.body).toEqual({ + branches: ['staging'], + create_empty_commit: false, + owners: ['owner'], + description: 'Initialize a gerrit repository', + parent: 'parent', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + const suffixPath = '&workspace=parent&project=test-project'; + 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, + repoUrl: input.repoUrl + suffixPath, + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: `${mockContext.workspacePath}${path.sep}repository${path.sep}`, + remoteUrl: 'https://gerrit.com/a/repo', + defaultBranch: 'staging', + auth: { username: 'demouser', password: 'accesstoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining( + 'Initial Commit Message\n\nChange-Id:', + ), + gitAuthorInfo: { name: 'John Doe', email: 'johndoe@email.com' }, + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://gerrit.com/a/repo', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrit.com/gitiles/repo/+/refs/heads/staging', + ); + }); + + it('should not create new projects on dryRun', async () => { + await action.handler({ + ...mockContext, + isDryRun: true, + input: { + ...mockContext.input, + repoUrl: 'gerrit.com?workspace=workspace&repo=repo', + sourcePath: 'repository/', + }, + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'commitHash', + 'abcd-dry-run-1234', + ); + }); + + it('should fail if repoUrl is incorrect', async () => { + await expect( + action.handler({ + ...mockContext, + input: { repoUrl: 'check.com?workspace=w&owner=o', description }, + }), + ).rejects.toThrow( + /No matching integration configuration for host check.com, please check your integrations config/, + ); + }); + + it('should fail if no integration config is given', async () => { + await expect( + action.handler({ + ...mockContext, + input: { + repoUrl: 'missing.com?workspace=w&owner=o&repo=r', + description, + }, + }), + ).rejects.toThrow(/No matching integration configuration/); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); +}); diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.ts index 6729b3602f..261465b3a0 100644 --- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.examples.ts @@ -156,4 +156,22 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: + 'Initialize a Gerrit Repository with Custom Default Branch and Commit Message', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:gerrit', + name: 'Publish to Gerrit', + input: { + repoUrl: 'gerrit.com?repo=repo&owner=owner', + defaultBranch: 'feature-branch', + gitCommitMessage: 'Feature branch initialized', + }, + }, + ], + }), + }, ]; 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..dd0f55ce6c 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,86 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.1.13 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 24de005: Added test cases for publish:gitea examples +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.13-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.1.13-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.13-next.1 + +### Patch Changes + +- 24de005: Added test cases for publish:gitea examples +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-gitea/api-report.md index 1dca414fa9..6d021bdf12 100644 --- a/plugins/scaffolder-backend-module-gitea/api-report.md +++ b/plugins/scaffolder-backend-module-gitea/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; @@ -28,6 +28,6 @@ export function createPublishGiteaAction(options: { >; // @public -const giteaModule: BackendFeatureCompat; +const giteaModule: BackendFeature; export default giteaModule; ``` diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index f6563795cd..51055aae54 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", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", @@ -47,7 +47,7 @@ "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "yaml": "^2.0.0" }, "devDependencies": { diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.test.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.test.ts new file mode 100644 index 0000000000..5575921c34 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.test.ts @@ -0,0 +1,806 @@ +/* + * 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 { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import { createPublishGiteaAction } from './gitea'; +import { + getRepoSourceDirectory, + initRepoAndPush, +} from '@backstage/plugin-scaffolder-node'; +import { rest } from 'msw'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { setupServer } from 'msw/node'; +import { examples } from './gitea.examples'; +import yaml from 'yaml'; + +jest.mock('@backstage/plugin-scaffolder-node', () => { + return { + ...jest.requireActual('@backstage/plugin-scaffolder-node'), + initRepoAndPush: jest.fn().mockResolvedValue({ + commitHash: '431f19cc36b551763d157f1b5e4a4b446165dbn2', + }), + }; +}); + +describe('publish:gitea', () => { + const config = new ConfigReader({ + integrations: { + gitea: [ + { + host: 'gitea.com', + username: 'sample_user', + password: 'password_token', + }, + ], + }, + }); + + const description = 'gitea description'; + const integrations = ScmIntegrations.fromConfig(config); + const action = createPublishGiteaAction({ integrations, config }); + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitea.com?repo=repo&owner=owner', + description, + }, + }); + + const server = setupServer(); + registerMswTestHooks(server); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it(`should ${examples[0].description}`, async () => { + server.use( + rest.get('https://gitea.com/api/v1/orgs/org1', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + id: 1, + name: 'org1', + visibility: 'public', + repo_admin_change_team_access: false, + username: 'org1', + }), + ); + }), + rest.get( + 'https://gitea.com/org1/repo/src/branch/main', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + rest.post('https://gitea.com/api/v1/orgs/org1/repos', (req, res, ctx) => { + // Basic auth must match the user and password defined part of the config + expect(req.headers.get('Authorization')).toBe( + 'basic c2FtcGxlX3VzZXI6cGFzc3dvcmRfdG9rZW4=', + ); + expect(req.body).toEqual({ + name: 'repo', + private: false, + description, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + 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, + repoUrl: 'gitea.com?repo=repo&owner=org1', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gitea.com/org1/repo.git', + defaultBranch: 'main', + auth: { username: 'sample_user', password: 'password_token' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: { + email: undefined, + name: undefined, + }, + }); + }); + + it(`should ${examples[1].description}`, async () => { + server.use( + rest.get('https://gitea.com/api/v1/orgs/org1', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + id: 1, + name: 'org1', + visibility: 'public', + repo_admin_change_team_access: false, + username: 'org1', + }), + ); + }), + rest.get( + 'https://gitea.com/org1/repo/src/branch/main', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + rest.post('https://gitea.com/api/v1/orgs/org1/repos', (req, res, ctx) => { + // Basic auth must match the user and password defined part of the config + expect(req.headers.get('Authorization')).toBe( + 'basic c2FtcGxlX3VzZXI6cGFzc3dvcmRfdG9rZW4=', + ); + expect(req.body).toEqual({ + name: 'repo', + private: false, + description: 'Initialize a gitea repository', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + 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, + repoUrl: 'gitea.com?repo=repo&owner=org1', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gitea.com/org1/repo.git', + defaultBranch: 'main', + auth: { username: 'sample_user', password: 'password_token' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: { + email: undefined, + name: undefined, + }, + }); + }); + + it(`should ${examples[2].description}`, async () => { + server.use( + rest.get('https://gitea.com/api/v1/orgs/org1', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + id: 1, + name: 'org1', + visibility: 'public', + repo_admin_change_team_access: false, + username: 'org1', + }), + ); + }), + rest.get( + 'https://gitea.com/org1/repo/src/branch/main', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + rest.post('https://gitea.com/api/v1/orgs/org1/repos', (req, res, ctx) => { + // Basic auth must match the user and password defined part of the config + expect(req.headers.get('Authorization')).toBe( + 'basic c2FtcGxlX3VzZXI6cGFzc3dvcmRfdG9rZW4=', + ); + expect(req.body).toEqual({ + name: 'repo', + private: true, + description, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + 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, + repoUrl: 'gitea.com?repo=repo&owner=org1', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gitea.com/org1/repo.git', + defaultBranch: 'main', + auth: { username: 'sample_user', password: 'password_token' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: { + email: undefined, + name: undefined, + }, + }); + }); + + it(`should ${examples[3].description}`, async () => { + server.use( + rest.get('https://gitea.com/api/v1/orgs/org1', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + id: 1, + name: 'org1', + visibility: 'public', + repo_admin_change_team_access: false, + username: 'org1', + }), + ); + }), + rest.get( + 'https://gitea.com/org1/repo/src/branch/staging', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + rest.post('https://gitea.com/api/v1/orgs/org1/repos', (req, res, ctx) => { + // Basic auth must match the user and password defined part of the config + expect(req.headers.get('Authorization')).toBe( + 'basic c2FtcGxlX3VzZXI6cGFzc3dvcmRfdG9rZW4=', + ); + expect(req.body).toEqual({ + name: 'repo', + private: false, + description, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + 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, + repoUrl: 'gitea.com?repo=repo&owner=org1', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gitea.com/org1/repo.git', + defaultBranch: 'staging', + auth: { username: 'sample_user', password: 'password_token' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: { + email: undefined, + name: undefined, + }, + }); + }); + + it(`should ${examples[4].description}`, async () => { + server.use( + rest.get('https://gitea.com/api/v1/orgs/org1', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + id: 1, + name: 'org1', + visibility: 'public', + repo_admin_change_team_access: false, + username: 'org1', + }), + ); + }), + rest.get( + 'https://gitea.com/org1/repo/src/branch/main', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + rest.post('https://gitea.com/api/v1/orgs/org1/repos', (req, res, ctx) => { + // Basic auth must match the user and password defined part of the config + expect(req.headers.get('Authorization')).toBe( + 'basic c2FtcGxlX3VzZXI6cGFzc3dvcmRfdG9rZW4=', + ); + expect(req.body).toEqual({ + name: 'repo', + private: false, + description, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + 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, + repoUrl: 'gitea.com?repo=repo&owner=org1', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gitea.com/org1/repo.git', + defaultBranch: 'main', + auth: { username: 'sample_user', password: 'password_token' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining( + 'Initial Commit Message\n\nChange-Id:', + ), + gitAuthorInfo: { + email: undefined, + name: undefined, + }, + }); + }); + + it(`should ${examples[5].description}`, async () => { + server.use( + rest.get('https://gitea.com/api/v1/orgs/org1', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + id: 1, + name: 'org1', + visibility: 'public', + repo_admin_change_team_access: false, + username: 'org1', + }), + ); + }), + rest.get( + 'https://gitea.com/org1/repo/src/branch/main', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + rest.post('https://gitea.com/api/v1/orgs/org1/repos', (req, res, ctx) => { + // Basic auth must match the user and password defined part of the config + expect(req.headers.get('Authorization')).toBe( + 'basic c2FtcGxlX3VzZXI6cGFzc3dvcmRfdG9rZW4=', + ); + expect(req.body).toEqual({ + name: 'repo', + private: false, + description, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + 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, + repoUrl: 'gitea.com?repo=repo&owner=org1', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gitea.com/org1/repo.git', + defaultBranch: 'main', + auth: { username: 'sample_user', password: 'password_token' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: { + email: undefined, + name: 'John Doe', + }, + }); + }); + + it(`should ${examples[6].description}`, async () => { + server.use( + rest.get('https://gitea.com/api/v1/orgs/org1', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + id: 1, + name: 'org1', + visibility: 'public', + repo_admin_change_team_access: false, + username: 'org1', + }), + ); + }), + rest.get( + 'https://gitea.com/org1/repo/src/branch/main', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + rest.post('https://gitea.com/api/v1/orgs/org1/repos', (req, res, ctx) => { + // Basic auth must match the user and password defined part of the config + expect(req.headers.get('Authorization')).toBe( + 'basic c2FtcGxlX3VzZXI6cGFzc3dvcmRfdG9rZW4=', + ); + expect(req.body).toEqual({ + name: 'repo', + private: false, + description, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + 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, + repoUrl: 'gitea.com?repo=repo&owner=org1', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gitea.com/org1/repo.git', + defaultBranch: 'main', + auth: { username: 'sample_user', password: 'password_token' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: { + email: 'johndoe@email.com', + name: undefined, + }, + }); + }); + + it(`should ${examples[7].description}`, async () => { + server.use( + rest.get('https://gitea.com/api/v1/orgs/org1', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + id: 1, + name: 'org1', + visibility: 'public', + repo_admin_change_team_access: false, + username: 'org1', + }), + ); + }), + rest.get( + 'https://gitea.com/org1/repo/src/branch/main', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + rest.post('https://gitea.com/api/v1/orgs/org1/repos', (req, res, ctx) => { + // Basic auth must match the user and password defined part of the config + expect(req.headers.get('Authorization')).toBe( + 'basic c2FtcGxlX3VzZXI6cGFzc3dvcmRfdG9rZW4=', + ); + expect(req.body).toEqual({ + name: 'repo', + private: false, + description, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + 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, + repoUrl: 'gitea.com?repo=repo&owner=org1', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: getRepoSourceDirectory(mockContext.workspacePath, 'repository/'), + remoteUrl: 'https://gitea.com/org1/repo.git', + defaultBranch: 'main', + auth: { username: 'sample_user', password: 'password_token' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: { + email: undefined, + name: undefined, + }, + }); + }); + + it(`should ${examples[8].description}`, async () => { + server.use( + rest.get('https://gitea.com/api/v1/orgs/org1', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + id: 1, + name: 'org1', + visibility: 'public', + repo_admin_change_team_access: false, + username: 'org1', + }), + ); + }), + rest.get( + 'https://gitea.com/org1/repo/src/branch/staging', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + rest.post('https://gitea.com/api/v1/orgs/org1/repos', (req, res, ctx) => { + // Basic auth must match the user and password defined part of the config + expect(req.headers.get('Authorization')).toBe( + 'basic c2FtcGxlX3VzZXI6cGFzc3dvcmRfdG9rZW4=', + ); + expect(req.body).toEqual({ + name: 'repo', + private: false, + description: 'Initialize a gitea repository', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + 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, + repoUrl: 'gitea.com?repo=repo&owner=org1', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: getRepoSourceDirectory(mockContext.workspacePath, 'repository/'), + remoteUrl: 'https://gitea.com/org1/repo.git', + defaultBranch: 'staging', + auth: { username: 'sample_user', password: 'password_token' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining( + 'Initial Commit Message\n\nChange-Id:', + ), + gitAuthorInfo: { + email: 'johndoe@email.com', + name: 'John Doe', + }, + }); + }); + + it(`should ${examples[0].description}`, async () => { + server.use( + rest.get('https://gitea.com/api/v1/orgs/org1', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + id: 1, + name: 'org1', + visibility: 'public', + repo_admin_change_team_access: false, + username: 'org1', + }), + ); + }), + rest.get( + 'https://gitea.com/org1/repo/src/branch/main', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + rest.post('https://gitea.com/api/v1/orgs/org1/repos', (req, res, ctx) => { + // Basic auth must match the user and password defined part of the config + expect(req.headers.get('Authorization')).toBe( + 'basic c2FtcGxlX3VzZXI6cGFzc3dvcmRfdG9rZW4=', + ); + expect(req.body).toEqual({ + name: 'repo', + private: false, + description, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + 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, + repoUrl: 'gitea.com?repo=repo&owner=org1', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gitea.com/org1/repo.git', + defaultBranch: 'main', + auth: { username: 'sample_user', password: 'password_token' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: { + email: undefined, + name: undefined, + }, + }); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.ts index dd4d038ead..5cf640de87 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.ts @@ -78,7 +78,7 @@ export const examples: TemplateExample[] = [ name: 'Publish to Gitea', input: { repoUrl: 'gitea.com?repo=repo&owner=owner', - defaultBranch: 'main', + defaultBranch: 'staging', }, }, ], 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..3c96c7b73d 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,114 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.4.1 + +### Patch Changes + +- d21d307: Added examples for github:environment:create action and improve its test cases +- 6d4cb97: Added examples for github:repo:create action and improved test cases +- cd203f1: Added examples for action github:pages and improved its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.4.1-next.3 + +### Patch Changes + +- 6d4cb97: Added examples for github:repo:create action and improved test cases +- cd203f1: Added examples for action github:pages and improved its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.4.1-next.1 + +### Patch Changes + +- d21d307: Added examples for github:environment:create action and improve its test cases +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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 79c2394e5c..1fec7f0422 100644 --- a/plugins/scaffolder-backend-module-github/api-report.md +++ b/plugins/scaffolder-backend-module-github/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { createPullRequest } from 'octokit-plugin-create-pull-request'; import { GithubCredentialsProvider } from '@backstage/integration'; @@ -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: { @@ -409,6 +424,6 @@ export function getOctokitOptions(options: { }): Promise; // @public -const githubModule: BackendFeatureCompat; +const githubModule: BackendFeature; export default githubModule; ``` diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 1fd404c6f9..e8dd77bd5f 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", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-github/src/actions/gitHubEnvironment.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/gitHubEnvironment.examples.ts index 606424bef0..7c5f7efa3d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/gitHubEnvironment.examples.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/gitHubEnvironment.examples.ts @@ -96,4 +96,210 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Create a GitHub Environment with Custom Tag Policies', + example: yaml.stringify({ + steps: [ + { + action: 'github:environment:create', + name: 'Create Environment', + input: { + repoUrl: 'github.com?repo=repository&owner=owner', + name: 'envname', + customTagPolicyNames: ['release/*/*', 'v*.*.*'], + }, + }, + ], + }), + }, + { + description: + 'Create a GitHub Environment with Both Branch and Tag Policies', + example: yaml.stringify({ + steps: [ + { + action: 'github:environment:create', + name: 'Create Environment', + input: { + repoUrl: 'github.com?repo=repository&owner=owner', + name: 'envname', + deploymentBranchPolicy: { + protected_branches: false, + custom_branch_policies: true, + }, + customBranchPolicyNames: ['feature/*', 'hotfix/*'], + customTagPolicyNames: ['release/*', 'v*'], + }, + }, + ], + }), + }, + { + description: 'Create a GitHub Environment with Full Configuration', + example: yaml.stringify({ + steps: [ + { + action: 'github:environment:create', + name: 'Create Environment', + input: { + repoUrl: 'github.com?repo=repository&owner=owner', + name: 'envname', + deploymentBranchPolicy: { + protected_branches: false, + custom_branch_policies: true, + }, + customBranchPolicyNames: ['dev/*', 'test/*'], + customTagPolicyNames: ['v1.*', 'v2.*'], + environmentVariables: { + API_KEY: '123456789', + NODE_ENV: 'production', + }, + secrets: { + DATABASE_URL: 'supersecretdatabaseurl', + API_SECRET: 'supersecretapisecret', + }, + token: 'ghp_abcdef1234567890', + }, + }, + ], + }), + }, + { + description: 'Create a GitHub Environment with Only Token Authentication', + example: yaml.stringify({ + steps: [ + { + action: 'github:environment:create', + name: 'Create Environment', + input: { + repoUrl: 'github.com?repo=repository&owner=owner', + name: 'envname', + token: 'ghp_abcdef1234567890', + }, + }, + ], + }), + }, + { + description: 'Create a GitHub Environment with No Deployment Policies', + example: yaml.stringify({ + steps: [ + { + action: 'github:environment:create', + name: 'Create Environment', + input: { + repoUrl: 'github.com?repo=repository&owner=owner', + name: 'envname', + deploymentBranchPolicy: null, + }, + }, + ], + }), + }, + { + description: + 'Create a GitHub Environment with Custom Branch and Tag Policies', + example: yaml.stringify({ + steps: [ + { + action: 'github:environment:create', + name: 'Create Environment', + input: { + repoUrl: 'github.com?repo=repository&owner=owner', + name: 'envname', + deploymentBranchPolicy: { + protected_branches: false, + custom_branch_policies: true, + }, + customBranchPolicyNames: ['release/*', 'hotfix/*'], + customTagPolicyNames: ['v*', 'release-*'], + }, + }, + ], + }), + }, + { + description: 'Create a GitHub Environment with Environment Variables Only', + example: yaml.stringify({ + steps: [ + { + action: 'github:environment:create', + name: 'Create Environment', + input: { + repoUrl: 'github.com?repo=repository&owner=owner', + name: 'envname', + environmentVariables: { + VAR1: 'value1', + VAR2: 'value2', + }, + }, + }, + ], + }), + }, + { + description: 'Create a GitHub Environment with Deployment Secrets Only', + example: yaml.stringify({ + steps: [ + { + action: 'github:environment:create', + name: 'Create Environment', + input: { + repoUrl: 'github.com?repo=repository&owner=owner', + name: 'envname', + secrets: { + SECRET1: 'secretvalue1', + SECRET2: 'secretvalue2', + }, + }, + }, + ], + }), + }, + { + description: + 'Create a GitHub Environment with Deployment Branch Policy and Token', + example: yaml.stringify({ + steps: [ + { + action: 'github:environment:create', + name: 'Create Environment', + input: { + repoUrl: 'github.com?repo=repository&owner=owner', + name: 'envname', + deploymentBranchPolicy: { + protected_branches: true, + custom_branch_policies: false, + }, + token: 'ghp_abcdef1234567890', + }, + }, + ], + }), + }, + { + description: + 'Create a GitHub Environment with Environment Variables, Secrets, and Token', + example: yaml.stringify({ + steps: [ + { + action: 'github:environment:create', + name: 'Create Environment', + input: { + repoUrl: 'github.com?repo=repository&owner=owner', + name: 'envname', + environmentVariables: { + VAR1: 'value1', + VAR2: 'value2', + }, + secrets: { + SECRET1: 'secretvalue1', + SECRET2: 'secretvalue2', + }, + token: 'ghp_abcdef1234567890', + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts index e41e642ca0..c8b133859b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts @@ -264,4 +264,522 @@ describe('github:environment:create examples', () => { encrypted_value: expect.any(String), }); }); + + it(`should ${examples[4].description}`, async () => { + const input = yaml.parse(examples[4].example).steps[0].input; + + await action.handler({ + ...mockContext, + input, + }); + + expect( + mockOctokit.rest.repos.createOrUpdateEnvironment, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repository', + environment_name: 'envname', + deployment_branch_policy: null, + }); + + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledTimes(2); + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'release/*/*', + owner: 'owner', + repo: 'repository', + type: 'tag', + }); + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'v*.*.*', + owner: 'owner', + repo: 'repository', + type: 'tag', + }); + + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).not.toHaveBeenCalled(); + + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).not.toHaveBeenCalled(); + }); + + it(`should ${examples[5].description}`, async () => { + const input = yaml.parse(examples[5].example).steps[0].input; + + await action.handler({ + ...mockContext, + input, + }); + + expect( + mockOctokit.rest.repos.createOrUpdateEnvironment, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repository', + environment_name: 'envname', + deployment_branch_policy: { + custom_branch_policies: true, + protected_branches: false, + }, + }); + + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledTimes(4); + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'release/*', + owner: 'owner', + repo: 'repository', + type: 'tag', + }); + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'feature/*', + owner: 'owner', + repo: 'repository', + type: 'branch', + }); + + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).not.toHaveBeenCalled(); + + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).not.toHaveBeenCalled(); + }); + + it(`should ${examples[6].description}`, async () => { + const input = yaml.parse(examples[6].example).steps[0].input; + + await action.handler({ + ...mockContext, + input, + }); + + expect( + mockOctokit.rest.repos.createOrUpdateEnvironment, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repository', + environment_name: 'envname', + deployment_branch_policy: { + custom_branch_policies: true, + protected_branches: false, + }, + }); + + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledTimes(4); + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'dev/*', + owner: 'owner', + repo: 'repository', + type: 'branch', + }); + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'test/*', + owner: 'owner', + repo: 'repository', + type: 'branch', + }); + + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).toHaveBeenCalledTimes(2); + + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'API_KEY', + owner: 'owner', + repo: 'repository', + repository_id: 'repoid', + value: '123456789', + }); + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'NODE_ENV', + owner: 'owner', + repo: 'repository', + repository_id: 'repoid', + value: 'production', + }); + + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).toHaveBeenCalledTimes(2); + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).toHaveBeenCalledWith({ + encrypted_value: expect.any(String), + environment_name: 'envname', + key_id: 'keyid', + owner: 'owner', + repo: 'repository', + repository_id: 'repoid', + secret_name: 'API_SECRET', + }); + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).toHaveBeenCalledWith({ + encrypted_value: expect.any(String), + environment_name: 'envname', + key_id: 'keyid', + owner: 'owner', + repo: 'repository', + repository_id: 'repoid', + secret_name: 'DATABASE_URL', + }); + }); + + it(`should ${examples[7].description}`, async () => { + const input = yaml.parse(examples[7].example).steps[0].input; + + await action.handler({ + ...mockContext, + input, + }); + + expect( + mockOctokit.rest.repos.createOrUpdateEnvironment, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repository', + environment_name: 'envname', + deployment_branch_policy: null, + }); + + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).not.toHaveBeenCalled(); + + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).not.toHaveBeenCalled(); + + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).not.toHaveBeenCalled(); + }); + + it(`should ${examples[8].description}`, async () => { + const input = yaml.parse(examples[8].example).steps[0].input; + + await action.handler({ + ...mockContext, + input, + }); + + expect( + mockOctokit.rest.repos.createOrUpdateEnvironment, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repository', + environment_name: 'envname', + deployment_branch_policy: null, + }); + + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).not.toHaveBeenCalled(); + + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).not.toHaveBeenCalled(); + + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).not.toHaveBeenCalled(); + }); + + it(`should ${examples[9].description}`, async () => { + const input = yaml.parse(examples[9].example).steps[0].input; + + await action.handler({ + ...mockContext, + input, + }); + + expect( + mockOctokit.rest.repos.createOrUpdateEnvironment, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repository', + environment_name: 'envname', + deployment_branch_policy: { + custom_branch_policies: true, + protected_branches: false, + }, + }); + + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledTimes(4); + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'release/*', + owner: 'owner', + repo: 'repository', + type: 'branch', + }); + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'hotfix/*', + owner: 'owner', + repo: 'repository', + type: 'branch', + }); + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'v*', + owner: 'owner', + repo: 'repository', + type: 'tag', + }); + + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).not.toHaveBeenCalled(); + + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).not.toHaveBeenCalled(); + }); + + it(`should ${examples[10].description}`, async () => { + const input = yaml.parse(examples[10].example).steps[0].input; + + await action.handler({ + ...mockContext, + input, + }); + + expect( + mockOctokit.rest.repos.createOrUpdateEnvironment, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repository', + environment_name: 'envname', + deployment_branch_policy: null, + }); + + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).not.toHaveBeenCalled(); + + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).toHaveBeenCalledTimes(2); + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'VAR1', + owner: 'owner', + repo: 'repository', + repository_id: 'repoid', + value: 'value1', + }); + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'VAR2', + owner: 'owner', + repo: 'repository', + repository_id: 'repoid', + value: 'value2', + }); + + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).not.toHaveBeenCalled(); + }); + + it(`should ${examples[11].description}`, async () => { + const input = yaml.parse(examples[11].example).steps[0].input; + + await action.handler({ + ...mockContext, + input, + }); + + expect( + mockOctokit.rest.repos.createOrUpdateEnvironment, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repository', + environment_name: 'envname', + deployment_branch_policy: null, + }); + + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).not.toHaveBeenCalled(); + + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).not.toHaveBeenCalled(); + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).toHaveBeenCalledTimes(2); + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).toHaveBeenCalledWith({ + encrypted_value: expect.any(String), + environment_name: 'envname', + key_id: 'keyid', + owner: 'owner', + repo: 'repository', + repository_id: 'repoid', + secret_name: 'SECRET1', + }); + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).toHaveBeenCalledWith({ + encrypted_value: expect.any(String), + environment_name: 'envname', + key_id: 'keyid', + owner: 'owner', + repo: 'repository', + repository_id: 'repoid', + secret_name: 'SECRET2', + }); + }); + + it(`should ${examples[12].description}`, async () => { + const input = yaml.parse(examples[12].example).steps[0].input; + + await action.handler({ + ...mockContext, + input, + }); + + expect( + mockOctokit.rest.repos.createOrUpdateEnvironment, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repository', + environment_name: 'envname', + deployment_branch_policy: { + custom_branch_policies: false, + protected_branches: true, + }, + }); + + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).not.toHaveBeenCalled(); + + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).not.toHaveBeenCalled(); + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).not.toHaveBeenCalled(); + }); + + it(`should ${examples[13].description}`, async () => { + const input = yaml.parse(examples[13].example).steps[0].input; + + await action.handler({ + ...mockContext, + input, + }); + + expect( + mockOctokit.rest.repos.createOrUpdateEnvironment, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repository', + environment_name: 'envname', + deployment_branch_policy: null, + }); + + expect( + mockOctokit.rest.repos.createDeploymentBranchPolicy, + ).not.toHaveBeenCalled(); + + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).toHaveBeenCalledTimes(2); + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'VAR1', + owner: 'owner', + repo: 'repository', + repository_id: 'repoid', + value: 'value1', + }); + expect( + mockOctokit.rest.actions.createEnvironmentVariable, + ).toHaveBeenCalledWith({ + environment_name: 'envname', + name: 'VAR2', + owner: 'owner', + repo: 'repository', + repository_id: 'repoid', + value: 'value2', + }); + + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).toHaveBeenCalledTimes(2); + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).toHaveBeenCalledWith({ + encrypted_value: expect.any(String), + environment_name: 'envname', + key_id: 'keyid', + owner: 'owner', + repo: 'repository', + repository_id: 'repoid', + secret_name: 'SECRET1', + }); + expect( + mockOctokit.rest.actions.createOrUpdateEnvironmentSecret, + ).toHaveBeenCalledWith({ + encrypted_value: expect.any(String), + environment_name: 'envname', + key_id: 'keyid', + owner: 'owner', + repo: 'repository', + repository_id: 'repoid', + secret_name: 'SECRET2', + }); + }); }); 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..f0aa0ea5a2 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts @@ -0,0 +1,446 @@ +/* + * 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 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 ${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(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', + }, + }, + ); + }); + 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(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'customOwner', + repo: 'customPathRepo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/docs', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + 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(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'legacyOwner', + repo: 'legacyRepo', + build_type: 'legacy', + source: { + branch: 'main', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + 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(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'branchOwner', + repo: 'customBranchRepo', + build_type: 'workflow', + source: { + branch: 'develop', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + 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(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'customOwner', + repo: 'fullCustomRepo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/docs', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + 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(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'minimalOwner', + repo: 'minimalRepo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + 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(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'customOwner', + repo: 'customBuildPathRepo', + build_type: 'legacy', + source: { + branch: 'main', + path: '/custom-path', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + 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(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'branchPathOwner', + repo: 'customBranchPathRepo', + build_type: 'workflow', + source: { + branch: 'feature-branch', + path: '/project-docs', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + 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(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'customOwnerName', + repo: 'customRepoName', + build_type: 'workflow', + source: { + branch: 'main', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + 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(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'tokenOwner', + repo: 'customTokenRepo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/site', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + it(`Should ${examples[10].description}`, async () => { + 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(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'tokenOwner', + repo: 'specificTokenRepo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); + + it(`Should ${examples[11].description}`, async () => { + 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(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'docsOwner', + repo: 'docSiteRepo', + build_type: 'workflow', + source: { + branch: 'docs-branch', + path: '/docs-site', + }, + 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..57102c4b92 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.ts @@ -0,0 +1,245 @@ +/* + * 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', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with a custom source path.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-custom-path', + name: 'Enable GitHub Pages with Custom Source Path', + input: { + repoUrl: 'github.com?repo=customPathRepo&owner=customOwner', + sourcePath: '/docs', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository using legacy build type.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-legacy', + name: 'Enable GitHub Pages with Legacy Build Type', + input: { + repoUrl: 'github.com?repo=legacyRepo&owner=legacyOwner', + buildType: 'legacy', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with a custom source branch.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-custom-branch', + name: 'Enable GitHub Pages with Custom Source Branch', + input: { + repoUrl: 'github.com?repo=customBranchRepo&owner=branchOwner', + sourceBranch: 'develop', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + + { + description: + 'Enables GitHub Pages for a repository with full customization.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-full-custom', + name: 'Enable GitHub Pages with Full Customization', + input: { + repoUrl: 'github.com?repo=fullCustomRepo&owner=customOwner', + buildType: 'workflow', + sourceBranch: 'main', + sourcePath: '/docs', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with minimal configuration.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-minimal', + name: 'Enable GitHub Pages with Minimal Configuration', + input: { + repoUrl: 'github.com?repo=minimalRepo&owner=minimalOwner', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with custom build type and source path.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-custom-build-path', + name: 'Enable GitHub Pages with Custom Build Type and Source Path', + input: { + repoUrl: 'github.com?repo=customBuildPathRepo&owner=customOwner', + buildType: 'legacy', + sourcePath: '/custom-path', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with custom source branch and path.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-custom-branch-path', + name: 'Enable GitHub Pages with Custom Source Branch and Path', + input: { + repoUrl: + 'github.com?repo=customBranchPathRepo&owner=branchPathOwner', + sourceBranch: 'feature-branch', + sourcePath: '/project-docs', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with a custom owner and repository name.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-custom-owner-repo', + name: 'Enable GitHub Pages with Custom Owner and Repository Name', + input: { + repoUrl: 'github.com?repo=customRepoName&owner=customOwnerName', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with full customization and a different token.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-full-custom-diff-token', + name: 'Enable GitHub Pages with Full Customization and Different Token', + input: { + repoUrl: 'github.com?repo=customTokenRepo&owner=tokenOwner', + buildType: 'workflow', + sourceBranch: 'main', + sourcePath: '/site', + token: 'gph_DifferentGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a repository with a specific token for authorization.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-specific-token', + name: 'Enable GitHub Pages with Specific Token', + input: { + repoUrl: 'github.com?repo=specificTokenRepo&owner=tokenOwner', + token: 'gph_SpecificGitHubToken', + }, + }, + ], + }), + }, + { + description: + 'Enables GitHub Pages for a documentation site with custom configuration.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages-doc-site', + name: 'Enable GitHub Pages for Documentation Site', + input: { + repoUrl: 'github.com?repo=docSiteRepo&owner=docsOwner', + buildType: 'workflow', + sourceBranch: 'docs-branch', + sourcePath: '/docs-site', + token: 'gph_DocsGitHubToken', + }, + }, + ], + }), + }, +]; 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/githubRepoCreate.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts index 843a32f758..1a81a54c64 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.test.ts @@ -58,6 +58,7 @@ const mockOctokit = { getRepoPublicKey: jest.fn(), }, }, + request: jest.fn().mockResolvedValue({}), }; jest.mock('octokit', () => ({ Octokit: class { @@ -188,4 +189,2218 @@ describe('github:repo:create examples', () => { has_wiki: false, // disable wiki }); }); + + it(`Should ${examples[3].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + 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(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: 'https://example.com', + }); + }); + + it(`Should ${examples[4].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + 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(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[5].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + 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(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[6].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + 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(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[7].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + 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(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: false, + allow_rebase_merge: false, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[8].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + 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(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'pull_request_title', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[9].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + 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(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'blank', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[10].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + 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(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: true, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[11].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + 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(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[12].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[12].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[13].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[13].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[14].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[14].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[15].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[15].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[16].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[16].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[17].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[17].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: true, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[18].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[18].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[19].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[19].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[20].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[20].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: false, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'internal', + has_issues: undefined, + description: 'A repository for project XYZ', + has_projects: undefined, + has_wiki: undefined, + homepage: 'https://project-xyz.com', + }); + }); + + it(`Should ${examples[21].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[21].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[22].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[22].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[23].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[23].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: true, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[24].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[24].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: false, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[25].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[25].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: false, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'internal', + has_issues: false, + description: undefined, + has_projects: false, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[26].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[26].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[27].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[27].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[28].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[28].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: 'Repository for project ABC', + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[29].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[29].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: false, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'public', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[30].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[30].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[31].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[31].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[32].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[32].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[33].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[33].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[34].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[34].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: false, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'internal', + has_issues: undefined, + description: 'Internal repository for team collaboration', + has_projects: undefined, + has_wiki: undefined, + homepage: 'https://internal.example.com', + }); + }); + + it(`Should ${examples[35].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[35].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[36].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[36].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: true, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[37].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[37].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[38].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[38].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[39].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[39].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: false, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'internal', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[40].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[40].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: 'Repository for web application project', + has_projects: undefined, + has_wiki: undefined, + homepage: 'https://webapp.example.com', + }); + }); + + it(`Should ${examples[41].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[41].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'pull_request_description', + allow_merge_commit: false, + allow_rebase_merge: false, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[42].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[42].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: false, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: false, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[43].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[43].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[44].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[44].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[45].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[45].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: false, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'public', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[46].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[46].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: false, + description: 'Repository for backend service', + has_projects: undefined, + has_wiki: false, + homepage: undefined, + }); + }); + + it(`Should ${examples[47].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[47].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[48].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[48].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[49].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[49].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[50].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[50].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: false, + allow_rebase_merge: true, + allow_auto_merge: true, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[51].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[51].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: 'https://example.com', + }); + }); + + it(`Should ${examples[52].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[52].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: 'Repository for microservice development', + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[53].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[53].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[54].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[54].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: true, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[55].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[55].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); + + it(`Should ${examples[56].description}`, async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + let input; + try { + input = yaml.parse(examples[56].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: undefined, + description: undefined, + has_projects: undefined, + has_wiki: undefined, + homepage: undefined, + }); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts index a58da1ee29..1c1f1ca5d2 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.examples.ts @@ -63,4 +63,932 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Set repository homepage.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with homepage', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + homepage: 'https://example.com', + }, + }, + ], + }), + }, + { + description: 'Create a private repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new private GitHub repository', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'private', + }, + }, + ], + }), + }, + { + description: 'Enable required code owner reviews.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with required code owner reviews', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requireCodeOwnerReviews: true, + }, + }, + ], + }), + }, + { + description: 'Set required approving review count to 2.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with required approving review count', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredApprovingReviewCount: 2, + }, + }, + ], + }), + }, + { + description: 'Allow squash merge only.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository allowing only squash merge', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowMergeCommit: false, + allowSquashMerge: true, + allowRebaseMerge: false, + }, + }, + ], + }), + }, + { + description: 'Set squash merge commit title to pull request title.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with squash merge commit title set to pull request title', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + squashMergeCommitTitle: 'pull_request_title', + }, + }, + ], + }), + }, + { + description: 'Set squash merge commit message to blank.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with squash merge commit message set to blank', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + squashMergeCommitMessage: 'blank', + }, + }, + ], + }), + }, + { + description: 'Allow auto-merge.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository allowing auto-merge', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowAutoMerge: true, + }, + }, + ], + }), + }, + { + description: 'Set collaborators with push access.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with collaborators having push access', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + collaborators: [ + { username: 'user1', permission: 'push' }, + { username: 'user2', permission: 'push' }, + ], + }, + }, + ], + }), + }, + { + description: 'Add topics to repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with topics', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + topics: ['devops', 'kubernetes', 'ci-cd'], + }, + }, + ], + }), + }, + { + description: 'Add secret variables to repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with secret variables', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + secrets: [ + { name: 'SECRET_KEY', value: 'supersecretkey' }, + { name: 'API_TOKEN', value: 'tokenvalue' }, + ], + }, + }, + ], + }), + }, + { + description: 'Enable branch protection requiring status checks.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch protection requiring status checks', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredStatusCheckContexts: ['ci/circleci: build'], + }, + }, + ], + }), + }, + { + description: 'Require branches to be up-to-date before merging.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository requiring branches to be up-to-date before merging', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requireBranchesToBeUpToDate: true, + }, + }, + ], + }), + }, + { + description: 'Require conversation resolution before merging.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository requiring conversation resolution before merging', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredConversationResolution: true, + }, + }, + ], + }), + }, + { + description: 'Delete branch on merge.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch deletion on merge', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + deleteBranchOnMerge: true, + }, + }, + ], + }), + }, + { + description: 'Customize OIDC token.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with OIDC token customization', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + oidcCustomization: { + sub: 'repo:owner/repo', + aud: 'https://github.com', + }, + }, + }, + ], + }), + }, + { + description: 'Require commit signing.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository requiring commit signing', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredCommitSigning: true, + }, + }, + ], + }), + }, + { + description: + 'Set multiple properties including description, homepage, and visibility.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with multiple properties', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + description: 'A repository for project XYZ', + homepage: 'https://project-xyz.com', + repoVisibility: 'internal', + }, + }, + ], + }), + }, + { + description: 'Configure branch protection with multiple settings.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch protection settings', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredStatusCheckContexts: [ + 'ci/circleci: build', + 'ci/circleci: test', + ], + requireBranchesToBeUpToDate: true, + requiredConversationResolution: true, + requiredApprovingReviewCount: 2, + }, + }, + ], + }), + }, + { + description: + 'Set repository access to private and add collaborators with admin access.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new private GitHub repository with collaborators', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'private', + collaborators: [ + { username: 'admin1', permission: 'admin' }, + { username: 'admin2', permission: 'admin' }, + ], + }, + }, + ], + }), + }, + { + description: 'Enable GitHub Projects for the repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with GitHub Projects enabled', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + hasProjects: true, + }, + }, + ], + }), + }, + { + description: + 'Disable merge commits and allow only rebase and squash merges.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository allowing only rebase and squash merges', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowMergeCommit: false, + allowRebaseMerge: true, + allowSquashMerge: true, + }, + }, + ], + }), + }, + { + description: + 'Set repository access to internal with no projects and issues.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new internal GitHub repository without projects and issues', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'internal', + hasProjects: false, + hasIssues: false, + }, + }, + ], + }), + }, + { + description: + 'Create repository with OIDC customization for specific audience.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with OIDC customization for specific audience', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + oidcCustomization: { + sub: 'repo:owner/repo', + aud: 'https://specific-audience.com', + }, + }, + }, + ], + }), + }, + { + description: 'Require all branches to be up-to-date before merging.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository requiring all branches to be up-to-date', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requireBranchesToBeUpToDate: true, + }, + }, + ], + }), + }, + { + description: 'Set description and topics for the repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with description and topics', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + description: 'Repository for project ABC', + topics: ['python', 'machine-learning', 'data-science'], + }, + }, + ], + }), + }, + { + description: + 'Set repository visibility to public and enable commit signing.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new public GitHub repository with commit signing required', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'public', + requiredCommitSigning: true, + }, + }, + ], + }), + }, + { + description: + 'Create a repository with collaborators and default branch protection.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with collaborators and branch protection', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + collaborators: [ + { username: 'contributor1', permission: 'write' }, + { username: 'contributor2', permission: 'write' }, + ], + requiredStatusCheckContexts: ['ci/travis: build'], + }, + }, + ], + }), + }, + { + description: 'Add multiple secret variables.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with multiple secret variables', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + secrets: [ + { name: 'SECRET_KEY_1', value: 'value1' }, + { name: 'SECRET_KEY_2', value: 'value2' }, + ], + }, + }, + ], + }), + }, + { + description: 'Require a minimum of 2 approving reviews for merging.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with 2 required approving reviews', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredApprovingReviewCount: 2, + }, + }, + ], + }), + }, + { + description: + 'Enable branch protection with conversation resolution required.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch protection and conversation resolution required', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredConversationResolution: true, + }, + }, + ], + }), + }, + { + description: + 'Set repository visibility to internal with description and homepage.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new internal GitHub repository with description and homepage', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'internal', + description: 'Internal repository for team collaboration', + homepage: 'https://internal.example.com', + }, + }, + ], + }), + }, + { + description: 'Disable auto-merge.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with auto-merge disabled', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowAutoMerge: false, + }, + }, + ], + }), + }, + { + description: 'Set repository topics and enable GitHub Projects.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with topics and GitHub Projects enabled', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + topics: ['opensource', 'nodejs', 'api'], + hasProjects: true, + }, + }, + ], + }), + }, + { + description: + 'Create a private repository with collaborators having admin and write access.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new private GitHub repository with multiple collaborators', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'private', + collaborators: [ + { username: 'admin1', permission: 'admin' }, + { username: 'writer1', permission: 'write' }, + ], + }, + }, + ], + }), + }, + { + description: 'Disable branch deletion on merge.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch deletion on merge disabled', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + deleteBranchOnMerge: false, + }, + }, + ], + }), + }, + { + description: + 'Set repository visibility to internal and enable commit signing.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new internal GitHub repository with commit signing required', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'internal', + requiredCommitSigning: true, + }, + }, + ], + }), + }, + { + description: + 'Create repository with description, homepage, and required status checks.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with description, homepage, and status checks', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + description: 'Repository for web application project', + homepage: 'https://webapp.example.com', + requiredStatusCheckContexts: [ + 'ci/travis: build', + 'ci/travis: lint', + ], + }, + }, + ], + }), + }, + { + description: + 'Enable squash merges only and set commit message to pull request description.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository allowing only squash merges with commit message set to pull request description', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowMergeCommit: false, + allowSquashMerge: true, + allowRebaseMerge: false, + squashMergeCommitMessage: 'pull_request_description', + }, + }, + ], + }), + }, + { + description: 'Enable rebase merges only and require commit signing.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository allowing only rebase merges with commit signing required', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowMergeCommit: false, + allowRebaseMerge: true, + allowSquashMerge: false, + requiredCommitSigning: true, + }, + }, + ], + }), + }, + { + description: + 'Create repository with OIDC customization for multiple audiences.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with OIDC customization for multiple audiences', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + oidcCustomization: { + sub: 'repo:owner/repo', + aud: ['https://audience1.com', 'https://audience2.com'], + }, + }, + }, + ], + }), + }, + { + description: + 'Enable branch protection with required approving reviews and status checks.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch protection requiring approving reviews and status checks', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredApprovingReviewCount: 2, + requiredStatusCheckContexts: [ + 'ci/circleci: build', + 'ci/circleci: test', + ], + }, + }, + ], + }), + }, + { + description: 'Create a public repository with topics and secret variables.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new public GitHub repository with topics and secret variables', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'public', + topics: ['javascript', 'react', 'frontend'], + secrets: [ + { name: 'API_KEY', value: 'apikeyvalue' }, + { name: 'DB_PASSWORD', value: 'dbpasswordvalue' }, + ], + }, + }, + ], + }), + }, + { + description: 'Set repository description and disable issues and wiki.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with description, and disable issues and wiki', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + description: 'Repository for backend service', + hasIssues: false, + hasWiki: false, + }, + }, + ], + }), + }, + { + description: 'Enable required conversation resolution and commit signing.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with required conversation resolution and commit signing', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredConversationResolution: true, + requiredCommitSigning: true, + }, + }, + ], + }), + }, + { + description: + 'Set repository visibility to private and require branches to be up-to-date.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new private GitHub repository requiring branches to be up-to-date', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + repoVisibility: 'private', + requireBranchesToBeUpToDate: true, + }, + }, + ], + }), + }, + { + description: + 'Create a repository with default settings and add multiple topics.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with default settings and topics', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + topics: ['devops', 'ci-cd', 'automation'], + }, + }, + ], + }), + }, + { + description: + 'Disable merge commits, enable auto-merge, and require commit signing.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository disabling merge commits, enabling auto-merge, and requiring commit signing', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + allowMergeCommit: false, + allowAutoMerge: true, + requiredCommitSigning: true, + }, + }, + ], + }), + }, + { + description: + 'Create a repository with homepage, collaborators, and topics.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with homepage, collaborators, and topics', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + homepage: 'https://example.com', + collaborators: [ + { username: 'user1', permission: 'push' }, + { username: 'user2', permission: 'admin' }, + ], + topics: ['opensource', 'contribution'], + }, + }, + ], + }), + }, + { + description: 'Create a repository with branch protection and description.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch protection and description', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredStatusCheckContexts: ['ci/travis: build'], + requiredApprovingReviewCount: 1, + description: 'Repository for microservice development', + }, + }, + ], + }), + }, + { + description: 'Create a repository with OIDC customization and topics.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with OIDC customization and topics', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + oidcCustomization: { + sub: 'repo:owner/repo', + aud: 'https://api.example.com', + }, + topics: ['api', 'security'], + }, + }, + ], + }), + }, + { + description: + 'Enable required code owner reviews and branch deletion on merge.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with required code owner reviews and branch deletion on merge', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requireCodeOwnerReviews: true, + deleteBranchOnMerge: true, + }, + }, + ], + }), + }, + { + description: + 'Create a repository with multiple secret variables and collaborators.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with multiple secret variables and collaborators', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + secrets: [ + { name: 'API_SECRET', value: 'secretvalue' }, + { name: 'DB_USER', value: 'dbuser' }, + ], + collaborators: [ + { username: 'dev1', permission: 'write' }, + { username: 'dev2', permission: 'push' }, + ], + }, + }, + ], + }), + }, + { + description: + 'Enable branch protection requiring status checks and conversation resolution.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository with branch protection requiring status checks and conversation resolution', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + requiredStatusCheckContexts: ['ci/build'], + requiredConversationResolution: true, + }, + }, + ], + }), + }, ]; 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/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..4c9aa96b19 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,116 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.4.5 + +### Patch Changes + +- da97131: Added test cases for gitlab:issues:create examples +- 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-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.4.5-next.3 + +### Patch Changes + +- da97131: Added test cases for gitlab:issues:create examples +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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..49bda648f6 100644 --- a/plugins/scaffolder-backend-module-gitlab/api-report.md +++ b/plugins/scaffolder-backend-module-gitlab/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; @@ -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; @@ -254,7 +255,7 @@ export const editGitlabIssueAction: (options: { >; // @public -const gitlabModule: BackendFeatureCompat; +const gitlabModule: BackendFeature; export default gitlabModule; // @public diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 7444bde77d..8db4f1ade5 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", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts new file mode 100644 index 0000000000..917ba61d91 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.test.ts @@ -0,0 +1,711 @@ +/* + * 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 { createGitlabIssueAction } from './gitlabIssueCreate'; +import { examples } from './gitlabIssueCreate.examples'; +import yaml from 'yaml'; + +const mockGitlabClient = { + Issues: { + create: jest.fn(), + }, +}; +jest.mock('@gitbeaker/rest', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:issues:create', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.useFakeTimers({ + now: new Date(2005, 6, 4, 11, 0, 0), + }); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'sample-token', + apiBaseUrl: 'https://gitlab.com/api/v1', + }, + ], + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = createGitlabIssueAction({ integrations }); + + 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); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: undefined, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: undefined, + weight: undefined, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + 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); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: undefined, + description: input.description, + assigneeIds: [18], + confidential: false, + epicId: undefined, + labels: '', + createdAt: input.createdAt, + dueDate: input.dueDate, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: undefined, + weight: undefined, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + 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); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + epicId: undefined, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: undefined, + description: input.description, + assigneeIds: [18, 15], + confidential: input.confidential, + epicId: undefined, + labels: input.labels, + createdAt: input.createdAt, + dueDate: input.dueDate, + discussionToResolve: input.discussionToResolve, + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: undefined, + weight: undefined, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + 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); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: undefined, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: undefined, + weight: undefined, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + 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); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: undefined, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + 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); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: input.issueType, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + 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); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: input.issueType, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + 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); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: input.issueType, + description: input.description, + assigneeIds: [18, 22], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + 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); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: input.issueType, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + 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); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: input.issueType, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: '', + mergeRequestToResolveDiscussionsOf: undefined, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); + + it(`should ${examples[10].description}`, async () => { + let input; + try { + input = yaml.parse(examples[10].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: 234, + title: 'Computer banks to rule the world', + token: 'myAwesomeToken', + }, + workspacePath: 'seen2much', + }); + + mockGitlabClient.Issues.create.mockResolvedValue({ + id: 12, + iid: 4, + web_url: 'https://gitlab.com/hangar18-/issues/42', + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); + + expect(mockGitlabClient.Issues.create).toHaveBeenCalledWith( + 12, + input.title, + { + issueType: input.issueType, + description: input.description, + assigneeIds: [], + confidential: false, + epicId: undefined, + labels: '', + createdAt: new Date().toISOString(), + dueDate: undefined, + discussionToResolve: 'abc123', + mergeRequestToResolveDiscussionsOf: + input.mergeRequestToResolveDiscussionsOf, + milestoneId: input.milestoneId, + weight: input.weight, + }, + ); + + expect(mockContext.output).toHaveBeenCalledWith('issueId', 12); + expect(mockContext.output).toHaveBeenCalledWith('issueIid', 4); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://gitlab.com/hangar18-/issues/42', + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.ts index 760caf0f9f..04fdeba46d 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabIssueCreate.examples.ts @@ -50,8 +50,8 @@ export const examples: TemplateExample[] = [ title: 'Test Issue', assignees: [18], description: 'This is the description of the issue', - createdAt: '2022-09-27 18:00:00.000', - dueDate: '2022-09-28 12:00:00.000', + createdAt: '2022-09-27T18:00:00.000Z', + dueDate: '2022-09-28T12:00:00.000Z', }, }, ], @@ -72,9 +72,9 @@ export const examples: TemplateExample[] = [ assignees: [18, 15], description: 'This is the description of the issue', confidential: false, - createdAt: '2022-09-27 18:00:00.000', - dueDate: '2022-09-28 12:00:00.000', - discussionToResolve: 1, + createdAt: '2022-09-27T18:00:00.000Z', + dueDate: '2022-09-28T12:00:00.000Z', + discussionToResolve: '1', epicId: 1, labels: 'phase1:label1,phase2:label2', }, @@ -82,4 +82,162 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Create a GitLab issue with token', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Test Issue', + description: 'This is the description of the issue', + token: 'sample token', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue with a specific milestone and weight', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Test Issue with Milestone', + description: 'This is the description of the issue', + milestoneId: 5, + weight: 3, + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue of type INCIDENT', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Confidential Test Issue', + description: 'This is the description of the issue', + issueType: 'incident', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue of type TEST', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Confidential Test Issue', + description: 'This is the description of the issue', + issueType: 'test_case', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue of type TASK with assignees', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Confidential Test Issue', + description: 'This is the description of the issue', + issueType: 'task', + assignees: [18, 22], + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue of type ISSUE and close it', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Confidential Test Issue', + description: 'This is the description of the issue', + issueType: 'issue', + stateEvent: 'close', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab issue of type INCIDENT and reopen it', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Confidential Test Issue', + description: 'This is the description of the issue', + issueType: 'incident', + stateEvent: 'reopen', + }, + }, + ], + }), + }, + { + description: + 'Create a GitLab issue to resolve a discussion in a merge request', + example: yaml.stringify({ + steps: [ + { + id: 'gitlabIssue', + name: 'Issues', + action: 'gitlab:issues:create', + input: { + ...commonGitlabConfigExample, + projectId: 12, + title: 'Test Issue for MR Discussion', + description: 'This is the description of the issue', + mergeRequestToResolveDiscussionsOf: 42, + discussionToResolve: 'abc123', + }, + }, + ], + }), + }, ]; 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..c6feaf8741 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,84 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.0.6 + +### Patch Changes + +- 6fc03c7: Add examples for notification:send scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-notifications-node@0.2.4 + - @backstage/plugin-notifications-common@0.0.5 + +## 0.0.6-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.3 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/plugin-notifications-node@0.2.4-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-notifications-common@0.0.5 + +## 0.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/plugin-notifications-common@0.0.5 + - @backstage/plugin-notifications-node@0.2.4-next.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-notifications/api-report.md index 4befadf403..091782449f 100644 --- a/plugins/scaffolder-backend-module-notifications/api-report.md +++ b/plugins/scaffolder-backend-module-notifications/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; import { NotificationService } from '@backstage/plugin-notifications-node'; import { NotificationSeverity } from '@backstage/plugin-notifications-common'; @@ -27,6 +27,6 @@ export function createSendNotificationAction(options: { >; // @public -const scaffolderModuleNotifications: BackendFeatureCompat; +const scaffolderModuleNotifications: BackendFeature; export default scaffolderModuleNotifications; ``` diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index bb8b7807e8..58c70478f2 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", "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..1cb678999e 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,100 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.40 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- 449def7: Add examples for fetch:rails scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/integration@1.14.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.40-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.4.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/integration@1.14.0-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-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.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-rails/api-report.md index 9b663d33d6..07b3713b15 100644 --- a/plugins/scaffolder-backend-module-rails/api-report.md +++ b/plugins/scaffolder-backend-module-rails/api-report.md @@ -3,16 +3,16 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { ContainerRunner } from '@backstage/backend-common'; import { JsonObject } from '@backstage/types'; import { ScmIntegrations } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; // @public export function createFetchRailsAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; containerRunner?: ContainerRunner; allowedImageNames?: string[]; @@ -27,6 +27,6 @@ export function createFetchRailsAction(options: { >; // @public -const railsModule: BackendFeatureCompat; +const railsModule: BackendFeature; export default railsModule; ``` diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index a736106df1..f92cdb2c4f 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", "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..6bc84e04ad --- /dev/null +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.examples.test.ts @@ -0,0 +1,342 @@ +/* + * 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 } 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'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; + +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: UrlReaderService = { + 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.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts index 40514dc7c5..616c90e0de 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts @@ -27,7 +27,7 @@ jest.mock('./railsNewRunner', () => { }; }); -import { ContainerRunner, UrlReader } from '@backstage/backend-common'; +import { ContainerRunner } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { resolve as resolvePath } from 'path'; @@ -36,6 +36,7 @@ import { fetchContents } from '@backstage/plugin-scaffolder-node'; import { createMockDirectory } from '@backstage/backend-test-utils'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { Writable } from 'stream'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('fetch:rails', () => { const mockDir = createMockDirectory(); @@ -65,7 +66,7 @@ describe('fetch:rails', () => { workspacePath: mockDir.path, }); - const mockReader: UrlReader = { + const mockReader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), 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..4d5db0f3ef 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 @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ContainerRunner, UrlReader } from '@backstage/backend-common'; +import { ContainerRunner } from '@backstage/backend-common'; import { JsonObject } from '@backstage/types'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; @@ -27,6 +27,8 @@ import { import { resolve as resolvePath } from 'path'; import { RailsNewRunner } from './railsNewRunner'; import { PassThrough } from 'stream'; +import { examples } from './index.examples'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; /** * Creates the `fetch:rails` Scaffolder action. @@ -39,7 +41,7 @@ import { PassThrough } from 'stream'; * @public */ export function createFetchRailsAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; containerRunner?: ContainerRunner; /** A list of image names that are allowed to be passed as imageName input */ @@ -56,6 +58,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..3a1c970c87 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,79 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.1.31 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 382e868: Added test cases for sentry:project:create examples +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.31-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.1.31-next.2 + +### Patch Changes + +- 93095ee: Make sure node-fetch is version 2.7.0 or greater +- 382e868: Added test cases for sentry:project:create examples +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## 0.1.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-sentry/api-report.md index 69d164a2f3..a857d10fdd 100644 --- a/plugins/scaffolder-backend-module-sentry/api-report.md +++ b/plugins/scaffolder-backend-module-sentry/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -23,7 +23,7 @@ export function createSentryCreateProjectAction(options: { >; // @public -const sentryModule: BackendFeatureCompat; +const sentryModule: BackendFeature; export default sentryModule; // (No @packageDocumentation comment for this package) diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 7943de9b5c..9c2f40628e 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", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", @@ -46,7 +46,7 @@ "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", - "node-fetch": "^2.6.7", + "node-fetch": "^2.7.0", "yaml": "^2.3.3" }, "devDependencies": { diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.test.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.test.ts new file mode 100644 index 0000000000..3e31aef3bc --- /dev/null +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.test.ts @@ -0,0 +1,232 @@ +/* + * 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 { registerMswTestHooks } from '@backstage/backend-test-utils'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { ActionContext } from '@backstage/plugin-scaffolder-node'; +import { JsonObject } from '@backstage/types'; +import { randomBytes } from 'crypto'; +import { setupServer } from 'msw/node'; +import { HttpResponse, http } from 'msw'; +import { createSentryCreateProjectAction } from './createProject'; +import yaml from 'yaml'; +import { examples } from './createProject.examples'; + +describe('sentry:project:create action', () => { + const worker = setupServer(); + registerMswTestHooks(worker); + + const createScaffolderConfig = (configData: JsonObject = {}) => ({ + config: new ConfigReader({ + scaffolder: { + ...configData, + }, + }), + }); + + const getActionContext = (): ActionContext<{ + organizationSlug: string; + teamSlug: string; + name: string; + slug?: string; + authToken?: string; + }> => + createMockActionContext({ + workspacePath: './dev/proj', + logger: jest.createMockFromModule('winston'), + input: { + organizationSlug: 'org', + teamSlug: 'team', + name: 'test project', + authToken: randomBytes(5).toString('hex'), + }, + }); + + it(`should ${examples[0].description}`, async () => { + expect.assertions(3); + + let input; + try { + input = yaml.parse(examples[0].example).steps[1].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const action = createSentryCreateProjectAction(createScaffolderConfig()); + const actionContext = getActionContext(); + + worker.use( + http.post( + `https://sentry.io/api/0/teams/${input.organizationSlug}/${input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer b15711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d4gf93`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: 'Scaffolded project B', + }); + return HttpResponse.json( + { detail: 'project creation mocked result' }, + { status: 201 }, + ); + }, + ), + ); + + await action.handler({ + ...actionContext, + input: { + ...actionContext.input, + ...input, + }, + }); + }); + + it(`should ${examples[0].description}`, async () => { + expect.assertions(3); + + let input; + try { + input = yaml.parse(examples[0].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const action = createSentryCreateProjectAction(createScaffolderConfig()); + const actionContext = getActionContext(); + actionContext.input = { ...actionContext.input, slug: 'project-slug' }; + + worker.use( + http.post( + `https://sentry.io/api/0/teams/${input.organizationSlug}/${input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer a14711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d5df96`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: 'Scaffolded project A', + slug: 'scaff-proj-a', + }); + return HttpResponse.json( + { detail: 'project creation mocked result' }, + { status: 201 }, + ); + }, + ), + ); + + await action.handler({ + ...actionContext, + input: { + ...actionContext.input, + ...input, + }, + }); + }); + + it(`should ${examples[1].description}`, async () => { + expect.assertions(3); + + let input; + try { + input = yaml.parse(examples[1].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const sentryScaffolderConfigToken = randomBytes(5).toString('hex'); + const action = createSentryCreateProjectAction( + createScaffolderConfig({ + sentry: { + token: sentryScaffolderConfigToken, + }, + }), + ); + const actionContext = getActionContext(); + actionContext.input.authToken = undefined; + + worker.use( + http.post( + `https://sentry.io/api/0/teams/${input.organizationSlug}/${input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer ${sentryScaffolderConfigToken}`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: 'Scaffolded project A', + }); + return HttpResponse.json( + { detail: 'project creation mocked result' }, + { status: 201 }, + ); + }, + ), + ); + + await action.handler({ + ...actionContext, + input: { + ...actionContext.input, + ...input, + }, + }); + }); + + it(`should ${examples[2].description}`, async () => { + expect.assertions(3); + + let input; + try { + input = yaml.parse(examples[2].example).steps[0].input; + } catch (error) { + console.error('Failed to parse YAML:', error); + } + + const action = createSentryCreateProjectAction(createScaffolderConfig()); + const actionContext = getActionContext(); + + worker.use( + http.post( + `https://sentry.io/api/0/teams/${input.organizationSlug}/${input.teamSlug}/projects/`, + async ({ request }) => { + expect(request.headers.get('Authorization')).toBe( + `Bearer c16711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d6gf94`, + ); + expect(request.headers.get('Content-Type')).toBe(`application/json`); + await expect(request.json()).resolves.toEqual({ + name: 'A very long name for the scaffolded project C that will generate a slug', + }); + return HttpResponse.json( + { detail: 'project creation mocked result' }, + { status: 201 }, + ); + }, + ), + ); + + await action.handler({ + ...actionContext, + input: { + ...actionContext.input, + ...input, + }, + }); + }); +}); diff --git a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts index 9d4551c1b5..3af88a4663 100644 --- a/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts +++ b/plugins/scaffolder-backend-module-sentry/src/actions/createProject.examples.ts @@ -50,4 +50,41 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Creates a Sentry project when no auth token is passed', + example: yaml.stringify({ + steps: [ + { + id: 'create-sentry-project', + action: 'sentry:project:create', + name: 'Create a Sentry project with provided project slug.', + input: { + organizationSlug: 'my-org', + teamSlug: 'team-a', + name: 'Scaffolded project A', + }, + }, + ], + }), + }, + { + description: + 'Creates a Sentry project with a long project name and automatically generated slug.', + example: yaml.stringify({ + steps: [ + { + id: 'create-sentry-project', + action: 'sentry:project:create', + name: 'Create a Sentry project with a long name and no slug provided.', + input: { + organizationSlug: 'my-org', + teamSlug: 'team-c', + name: 'A very long name for the scaffolded project C that will generate a slug', + authToken: + 'c16711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d6gf94', + }, + }, + ], + }), + }, ]; 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..97fdd27e4a 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,75 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.3.7 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.10 + +## 0.3.7-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + - @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.3 + +## 0.3.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.2 + - @backstage/types@1.1.1 + +## 0.3.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.10-next.1 + +## 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/api-report.md b/plugins/scaffolder-backend-module-yeoman/api-report.md index 278c9b41ae..9980dc2152 100644 --- a/plugins/scaffolder-backend-module-yeoman/api-report.md +++ b/plugins/scaffolder-backend-module-yeoman/api-report.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; @@ -18,6 +18,6 @@ export function createRunYeomanAction(): TemplateAction< >; // @public -const yeomanModule: BackendFeatureCompat; +const yeomanModule: BackendFeature; export default yeomanModule; ``` diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 70133616ef..2599de5921 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", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 5737dc0165..afa0291723 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,248 @@ # @backstage/plugin-scaffolder-backend +## 1.24.0 + +### Minor Changes + +- fc24d9e: Stop using `@backstage/backend-tasks` as it will be deleted in near future. +- dcd6a79: Added OpenTelemetry support to Scaffolder metrics + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- c544f81: Add support for status filtering in scaffolder tasks endpoint +- b63d378: Update internal imports +- ef87e06: Fix scaffolder action `catalog:write` to write to directories that don't already exist +- Updated dependencies + - @backstage/backend-defaults@0.4.2 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1 + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/plugin-permission-common@0.8.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13 + - @backstage/plugin-auth-node@0.5.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.15 + - @backstage/plugin-permission-node@0.8.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5 + - @backstage/plugin-catalog-node@1.12.5 + - @backstage/integration@1.14.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.22 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @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 + - @backstage/plugin-scaffolder-common@1.5.5 + +## 1.23.1-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.3 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.3 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.3 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.3 + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.3 + - @backstage/backend-tasks@0.5.28-next.3 + - @backstage/catalog-client@1.6.6-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-node@0.5.0-next.3 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.3 + - @backstage/plugin-catalog-node@1.12.5-next.3 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-permission-node@0.8.1-next.3 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.3 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.3 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.3 + - @backstage/plugin-scaffolder-common@1.5.5-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 1.23.1-next.2 + +### Patch Changes + +- c544f81: Add support for status filtering in scaffolder tasks endpoint +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-auth-node@0.5.0-next.2 + - @backstage/plugin-permission-node@0.8.1-next.2 + - @backstage/backend-tasks@0.5.28-next.2 + - @backstage/plugin-catalog-node@1.12.5-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.2 + - @backstage/plugin-scaffolder-common@1.5.5-next.1 + - @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/errors@1.2.4 + - @backstage/types@1.1.1 + +## 1.23.1-next.1 + +### Patch Changes + +- ef87e06: Fix scaffolder action `catalog:write` to write to directories that don't already exist +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.4.1-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.13-next.1 + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/plugin-permission-node@0.8.1-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.13-next.1 + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-catalog-node@1.12.5-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.13-next.1 + - @backstage/backend-tasks@0.5.28-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-node@0.4.18-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.22-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.21-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.15-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.13-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.15-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.4.5-next.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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-alpha.md b/plugins/scaffolder-backend/api-report-alpha.md index 5bb6cb1701..db62c6caf7 100644 --- a/plugins/scaffolder-backend/api-report-alpha.md +++ b/plugins/scaffolder-backend/api-report-alpha.md @@ -3,7 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeatureCompat } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common'; import { Conditions } from '@backstage/plugin-permission-node'; import { JsonObject } from '@backstage/types'; @@ -78,7 +78,7 @@ export const scaffolderActionConditions: Conditions<{ }>; // @alpha -const scaffolderPlugin: BackendFeatureCompat; +const scaffolderPlugin: BackendFeature; export default scaffolderPlugin; // @alpha diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index a3beee2b53..00f690c646 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -15,6 +15,7 @@ import * as bitbucketCloud from '@backstage/plugin-scaffolder-backend-module-bit import * as bitbucketServer from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; +import { DatabaseService } from '@backstage/backend-plugin-api'; import { DiscoveryService } from '@backstage/backend-plugin-api'; import { Duration } from 'luxon'; import { executeShellCommand as executeShellCommand_2 } from '@backstage/plugin-scaffolder-node'; @@ -37,10 +38,10 @@ import { PermissionRule } from '@backstage/plugin-permission-node'; import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { PluginDatabaseManager } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-common/alpha'; import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-common/alpha'; import { ScaffolderEntitiesProcessor as ScaffolderEntitiesProcessor_2 } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model'; +import { SchedulerService } from '@backstage/backend-plugin-api'; import { Schema } from 'jsonschema'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmIntegrations } from '@backstage/integration'; @@ -63,7 +64,7 @@ import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common'; import { TemplateFilter as TemplateFilter_2 } from '@backstage/plugin-scaffolder-node'; import { TemplateGlobal as TemplateGlobal_2 } from '@backstage/plugin-scaffolder-node'; import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha'; import { ZodType } from 'zod'; import { ZodTypeDef } from 'zod'; @@ -95,7 +96,7 @@ export interface CreateBuiltInActionsOptions { catalogClient: CatalogApi; config: Config; integrations: ScmIntegrations; - reader: UrlReader; + reader: UrlReaderService; } // @public @@ -154,7 +155,7 @@ export function createFetchCatalogEntityAction(options: { // @public export function createFetchPlainAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; }): TemplateAction_2< { @@ -167,7 +168,7 @@ export function createFetchPlainAction(options: { // @public export function createFetchPlainFileAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; }): TemplateAction_2< { @@ -180,7 +181,7 @@ export function createFetchPlainFileAction(options: { // @public export function createFetchTemplateAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; @@ -312,7 +313,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; @@ -320,7 +321,7 @@ export const createPublishGitlabMergeRequestAction: (options: { JsonObject >; -// @public +// @public @deprecated export function createRouter(options: RouterOptions): Promise; // @public @deprecated (undocumented) @@ -427,7 +428,7 @@ export class DatabaseTaskStore implements TaskStore { // (undocumented) heartbeatTask(taskId: string): Promise; // (undocumented) - list(options: { createdBy?: string }): Promise<{ + list(options: { createdBy?: string; status?: TaskStatus_2 }): Promise<{ tasks: SerializedTask_2[]; }>; // (undocumented) @@ -469,7 +470,7 @@ export const executeShellCommand: typeof executeShellCommand_2; // @public @deprecated export const fetchContents: typeof fetchContents_2; -// @public +// @public @deprecated export interface RouterOptions { // (undocumented) actions?: TemplateAction_2[]; @@ -489,7 +490,7 @@ export interface RouterOptions { // (undocumented) config: Config; // (undocumented) - database: PluginDatabaseManager; + database: DatabaseService; // (undocumented) discovery?: DiscoveryService; // (undocumented) @@ -507,9 +508,9 @@ export interface RouterOptions { // (undocumented) permissions?: PermissionsService; // (undocumented) - reader: UrlReader; + reader: UrlReaderService; // (undocumented) - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; // (undocumented) taskBroker?: TaskBroker_2; // @deprecated (undocumented) @@ -646,7 +647,7 @@ export interface TaskStore { // (undocumented) heartbeatTask(taskId: string): Promise; // (undocumented) - list?(options: { createdBy?: string }): Promise<{ + list?(options: { createdBy?: string; status?: TaskStatus }): Promise<{ tasks: SerializedTask[]; }>; // (undocumented) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index c9c19d9983..25b717b6ca 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.24.0", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin", @@ -62,8 +62,8 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", @@ -86,6 +86,7 @@ "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", + "@opentelemetry/api": "^1.3.0", "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", "concat-stream": "^2.0.0", @@ -115,6 +116,7 @@ }, "devDependencies": { "@backstage/backend-app-api": "workspace:^", + "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/plugin-scaffolder-node-test-utils": "workspace:^", diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.examples.test.ts index e6b4bcde47..f900e87a1d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.examples.test.ts @@ -55,8 +55,8 @@ describe('catalog:write', () => { input: yaml.parse(examples[0].example).steps[0].input, }); - expect(fsMock.writeFile).toHaveBeenCalledTimes(1); - expect(fsMock.writeFile).toHaveBeenCalledWith( + expect(fsMock.outputFile).toHaveBeenCalledTimes(1); + expect(fsMock.outputFile).toHaveBeenCalledWith( resolvePath(mockContext.workspacePath, 'catalog-info.yaml'), yaml.stringify(entity), ); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts index 658e4365bf..469b4d5c6d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts @@ -59,8 +59,8 @@ describe('catalog:write', () => { }, }); - expect(fsMock.writeFile).toHaveBeenCalledTimes(1); - expect(fsMock.writeFile).toHaveBeenCalledWith( + expect(fsMock.outputFile).toHaveBeenCalledTimes(1); + expect(fsMock.outputFile).toHaveBeenCalledWith( resolvePath(mockContext.workspacePath, 'catalog-info.yaml'), yaml.stringify(entity), ); @@ -88,8 +88,8 @@ describe('catalog:write', () => { }, }); - expect(fsMock.writeFile).toHaveBeenCalledTimes(1); - expect(fsMock.writeFile).toHaveBeenCalledWith( + expect(fsMock.outputFile).toHaveBeenCalledTimes(1); + expect(fsMock.outputFile).toHaveBeenCalledWith( resolvePath(mockContext.workspacePath, 'some-dir/entity-info.yaml'), yaml.stringify(entity), ); @@ -118,8 +118,8 @@ describe('catalog:write', () => { 'backstage.io/source-template': 'template:default/test-skeleton', }; - expect(fsMock.writeFile).toHaveBeenCalledTimes(1); - expect(fsMock.writeFile).toHaveBeenCalledWith( + expect(fsMock.outputFile).toHaveBeenCalledTimes(1); + expect(fsMock.outputFile).toHaveBeenCalledWith( resolvePath(mockContext.workspacePath, 'catalog-info.yaml'), yaml.stringify(expectedEntity), ); 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..29ce6c986d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts @@ -49,12 +49,12 @@ 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( + await fs.outputFile( resolveSafeChildPath(ctx.workspacePath, path), yaml.stringify({ ...entity, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index 673776bec9..dba867c96b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import { @@ -82,7 +81,7 @@ import { } from '@backstage/plugin-scaffolder-backend-module-gitlab'; import { createPublishGiteaAction } from '@backstage/plugin-scaffolder-backend-module-gitea'; -import { AuthService } from '@backstage/backend-plugin-api'; +import { AuthService, UrlReaderService } from '@backstage/backend-plugin-api'; /** * The options passed to {@link createBuiltinActions} @@ -90,9 +89,9 @@ import { AuthService } from '@backstage/backend-plugin-api'; */ export interface CreateBuiltInActionsOptions { /** - * The {@link @backstage/backend-common#UrlReader} interface that will be used in the default actions. + * The {@link @backstage/backend-plugin-api#UrlReaderService} interface that will be used in the default actions. */ - reader: UrlReader; + reader: UrlReaderService; /** * The {@link @backstage/integrations#ScmIntegrations} that will be used in the default actions. */ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.examples.test.ts index 82a32fcbac..9dbb1f9a03 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.examples.test.ts @@ -17,13 +17,13 @@ import yaml from 'yaml'; import { resolve as resolvePath } from 'path'; -import { UrlReader } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createFetchPlainAction } from './plain'; import { fetchContents } from '@backstage/plugin-scaffolder-node'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { examples } from './plain.examples'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; jest.mock('@backstage/plugin-scaffolder-node', () => ({ ...jest.requireActual('@backstage/plugin-scaffolder-node'), @@ -38,7 +38,7 @@ describe('fetch:plain examples', () => { }, }), ); - const reader: UrlReader = { + const reader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts index bc20fafe19..8a52ef7392 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts @@ -21,11 +21,11 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { resolve as resolvePath } from 'path'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { UrlReader } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { fetchContents } from '@backstage/plugin-scaffolder-node'; import { createFetchPlainAction } from './plain'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('fetch:plain', () => { const integrations = ScmIntegrations.fromConfig( @@ -35,7 +35,7 @@ describe('fetch:plain', () => { }, }), ); - const reader: UrlReader = { + const reader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts index c6beaa2d67..a4f795dd54 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; -import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; +import { + resolveSafeChildPath, + UrlReaderService, +} from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { examples } from './plain.examples'; @@ -32,7 +34,7 @@ export const ACTION_ID = 'fetch:plain'; * @public */ export function createFetchPlainAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; }) { const { reader, integrations } = options; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.examples.test.ts index 9978d6d8f5..a091a6ecfe 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.examples.test.ts @@ -23,12 +23,12 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import yaml from 'yaml'; import { resolve as resolvePath } from 'path'; -import { UrlReader } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createFetchPlainFileAction } from './plainFile'; import { fetchFile } from '@backstage/plugin-scaffolder-node'; import { examples } from './plainFile.examples'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('fetch:plain:file examples', () => { const integrations = ScmIntegrations.fromConfig( @@ -38,7 +38,7 @@ describe('fetch:plain:file examples', () => { }, }), ); - const reader: UrlReader = { + const reader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts index bf40c7ad0e..637a209bd9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.test.ts @@ -21,11 +21,11 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { resolve as resolvePath } from 'path'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; -import { UrlReader } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { fetchFile } from '@backstage/plugin-scaffolder-node'; import { createFetchPlainFileAction } from './plainFile'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; describe('fetch:plain:file', () => { const integrations = ScmIntegrations.fromConfig( @@ -35,7 +35,7 @@ describe('fetch:plain:file', () => { }, }), ); - const reader: UrlReader = { + const reader: UrlReaderService = { readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts index 92db7aa3ed..a07c9bfc6b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plainFile.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { examples } from './plainFile.examples'; @@ -29,7 +29,7 @@ import { * @public */ export function createFetchPlainFileAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; }) { const { reader, integrations } = options; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts index 02fe0c30a4..41ce875317 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.examples.test.ts @@ -16,7 +16,7 @@ import { join as joinPath, sep as pathSep } from 'path'; import fs from 'fs-extra'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; @@ -71,7 +71,7 @@ describe('fetch:template examples', () => { beforeEach(() => { mockDir.clear(); action = createFetchTemplateAction({ - reader: Symbol('UrlReader') as unknown as UrlReader, + reader: Symbol('UrlReader') as unknown as UrlReaderService, integrations: Symbol('Integrations') as unknown as ScmIntegrations, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts index 0542b6a50d..61918e8450 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts @@ -21,8 +21,10 @@ jest.mock('@backstage/plugin-scaffolder-node', () => { import { join as joinPath, sep as pathSep } from 'path'; import fs from 'fs-extra'; -import { UrlReader } from '@backstage/backend-common'; -import { resolvePackagePath } from '@backstage/backend-plugin-api'; +import { + UrlReaderService, + resolvePackagePath, +} from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { createFetchTemplateAction } from './template'; import { @@ -78,7 +80,7 @@ describe('fetch:template', () => { workspace: {}, }); action = createFetchTemplateAction({ - reader: Symbol('UrlReader') as unknown as UrlReader, + reader: Symbol('UrlReader') as unknown as UrlReaderService, integrations: Symbol('Integrations') as unknown as ScmIntegrations, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 0983199339..2025202c22 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -15,7 +15,7 @@ */ import { extname } from 'path'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; @@ -40,7 +40,7 @@ import { examples } from './template.examples'; * @public */ export function createFetchTemplateAction(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; 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-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts index f4fae0715b..78a362b5c7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.test.ts @@ -95,6 +95,33 @@ describe('DatabaseTaskStore', () => { expect(tasks[0].id).toBeDefined(); }); + it('should list filtered created tasks by status', async () => { + const { store } = await createStore(); + + const { taskId } = await store.createTask({ + spec: {} as TaskSpec, + createdBy: 'me', + }); + + await store.createTask({ + spec: {} as TaskSpec, + createdBy: 'him', + }); + + const message = `This task was marked as stale as it exceeded its timeout`; + await store.completeTask({ + taskId, + status: 'cancelled', + eventBody: { message }, + }); + + const { tasks } = await store.list({ status: 'open' }); + expect(tasks.length).toBe(1); + expect(tasks[0].createdBy).toBe('him'); + expect(tasks[0].status).toBe('open'); + expect(tasks[0].id).toBeDefined(); + }); + it('should sent an event to start cancelling the task', async () => { const { store } = await createStore(); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index fe62572af9..ba675b2cc7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -22,19 +22,19 @@ import { Knex } from 'knex'; import { v4 as uuid } from 'uuid'; import { TaskStore, - TaskStoreEmitOptions, - TaskStoreListEventsOptions, TaskStoreCreateTaskOptions, TaskStoreCreateTaskResult, - TaskStoreShutDownTaskOptions, + TaskStoreEmitOptions, + TaskStoreListEventsOptions, TaskStoreRecoverTaskOptions, + TaskStoreShutDownTaskOptions, } from './types'; import { - SerializedTaskEvent, SerializedTask, - TaskStatus, + SerializedTaskEvent, TaskEventType, TaskSecrets, + TaskStatus, } from '@backstage/plugin-scaffolder-node'; import { DateTime, Duration } from 'luxon'; import { TaskRecovery, TaskSpec } from '@backstage/plugin-scaffolder-common'; @@ -182,6 +182,7 @@ export class DatabaseTaskStore implements TaskStore { async list(options: { createdBy?: string; + status?: TaskStatus; }): Promise<{ tasks: SerializedTask[] }> { const queryBuilder = this.db('tasks'); @@ -191,6 +192,10 @@ export class DatabaseTaskStore implements TaskStore { }); } + if (options.status) { + queryBuilder.where({ status: options.status }); + } + const results = await queryBuilder.orderBy('created_at', 'desc').select(); const tasks = results.map(result => ({ diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 3b88fd0f10..3d7d8b8b1d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -26,6 +26,7 @@ import { PassThrough } from 'stream'; import { generateExampleOutput, isTruthy } from './helper'; import { validate as validateJsonSchema } from 'jsonschema'; import { TemplateActionRegistry } from '../actions'; +import { metrics } from '@opentelemetry/api'; import { SecureTemplater, SecureTemplateRenderer, @@ -520,36 +521,62 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } function scaffoldingTracker() { - const taskCount = createCounterMetric({ + // prom-client metrics are deprecated in favour of OpenTelemetry metrics. + const promTaskCount = createCounterMetric({ name: 'scaffolder_task_count', help: 'Count of task runs', labelNames: ['template', 'user', 'result'], }); - const taskDuration = createHistogramMetric({ + const promTaskDuration = createHistogramMetric({ name: 'scaffolder_task_duration', help: 'Duration of a task run', labelNames: ['template', 'result'], }); - const stepCount = createCounterMetric({ + const promtStepCount = createCounterMetric({ name: 'scaffolder_step_count', help: 'Count of step runs', labelNames: ['template', 'step', 'result'], }); - const stepDuration = createHistogramMetric({ + const promStepDuration = createHistogramMetric({ name: 'scaffolder_step_duration', help: 'Duration of a step runs', labelNames: ['template', 'step', 'result'], }); + const meter = metrics.getMeter('default'); + const taskCount = meter.createCounter('scaffolder.task.count', { + description: 'Count of task runs', + }); + + const taskDuration = meter.createHistogram('scaffolder.task.duration', { + description: 'Duration of a task run', + unit: 'seconds', + }); + + const stepCount = meter.createCounter('scaffolder.step.count', { + description: 'Count of step runs', + }); + + const stepDuration = meter.createHistogram('scaffolder.step.duration', { + description: 'Duration of a step runs', + unit: 'seconds', + }); + async function taskStart(task: TaskContext) { await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`); const template = task.spec.templateInfo?.entityRef || ''; const user = task.spec.user?.ref || ''; - const taskTimer = taskDuration.startTimer({ + const startTime = process.hrtime(); + const taskTimer = promTaskDuration.startTimer({ template, }); + function endTime() { + const delta = process.hrtime(startTime); + return delta[0] + delta[1] / 1e9; + } + async function skipDryRun( step: TaskStep, action: TemplateAction, @@ -561,12 +588,17 @@ function scaffoldingTracker() { } async function markSuccessful() { - taskCount.inc({ + promTaskCount.inc({ template, user, result: 'ok', }); taskTimer({ result: 'ok' }); + + taskCount.add(1, { template, user, result: 'ok' }); + taskDuration.record(endTime(), { + result: 'ok', + }); } async function markFailed(step: TaskStep, err: Error) { @@ -574,12 +606,17 @@ function scaffoldingTracker() { stepId: step.id, status: 'failed', }); - taskCount.inc({ + promTaskCount.inc({ template, user, result: 'failed', }); taskTimer({ result: 'failed' }); + + taskCount.add(1, { template, user, result: 'failed' }); + taskDuration.record(endTime(), { + result: 'failed', + }); } async function markCancelled(step: TaskStep) { @@ -587,12 +624,17 @@ function scaffoldingTracker() { stepId: step.id, status: 'cancelled', }); - taskCount.inc({ + promTaskCount.inc({ template, user, result: 'cancelled', }); taskTimer({ result: 'cancelled' }); + + taskCount.add(1, { template, user, result: 'cancelled' }); + taskDuration.record(endTime(), { + result: 'cancelled', + }); } return { @@ -610,40 +652,61 @@ function scaffoldingTracker() { }); const template = task.spec.templateInfo?.entityRef || ''; - const stepTimer = stepDuration.startTimer({ + const startTime = process.hrtime(); + const stepTimer = promStepDuration.startTimer({ template, step: step.name, }); + function endTime() { + const delta = process.hrtime(startTime); + return delta[0] + delta[1] / 1e9; + } + async function markSuccessful() { await task.emitLog(`Finished step ${step.name}`, { stepId: step.id, status: 'completed', }); - stepCount.inc({ + promtStepCount.inc({ template, step: step.name, result: 'ok', }); stepTimer({ result: 'ok' }); + + stepCount.add(1, { template, step: step.name, result: 'ok' }); + stepDuration.record(endTime(), { + result: 'ok', + }); } async function markCancelled() { - stepCount.inc({ + promtStepCount.inc({ template, step: step.name, result: 'cancelled', }); stepTimer({ result: 'cancelled' }); + + stepCount.add(1, { template, step: step.name, result: 'cancelled' }); + stepDuration.record(endTime(), { + result: 'cancelled', + }); } async function markFailed() { - stepCount.inc({ + promtStepCount.inc({ template, step: step.name, result: 'failed', }); stepTimer({ result: 'failed' }); + + stepCount.add(1, { template, step: step.name, result: 'failed' }); + stepDuration.record(endTime(), { + result: 'failed', + }); } async function skipFalsy() { @@ -652,6 +715,11 @@ function scaffoldingTracker() { { stepId: step.id, status: 'skipped' }, ); stepTimer({ result: 'skipped' }); + + stepCount.add(1, { template, step: step.name, result: 'skipped' }); + stepDuration.record(endTime(), { + result: 'skipped', + }); } return { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index f4946e6652..d17fb33b3b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -20,13 +20,14 @@ import { JsonObject, JsonValue, Observable } from '@backstage/types'; import { Logger } from 'winston'; import ObservableImpl from 'zen-observable'; import { - TaskSecrets, SerializedTask, SerializedTaskEvent, TaskBroker, TaskBrokerDispatchOptions, TaskCompletionState, TaskContext, + TaskSecrets, + TaskStatus, } from '@backstage/plugin-scaffolder-node'; import { InternalTaskSecrets, TaskStore } from './types'; import { readDuration } from './helper'; @@ -279,13 +280,17 @@ export class StorageTaskBroker implements TaskBroker { async list(options?: { createdBy?: string; + status?: TaskStatus; }): Promise<{ tasks: SerializedTask[] }> { if (!this.storage.list) { throw new Error( 'TaskStore does not implement the list method. Please implement the list method to be able to list tasks', ); } - return await this.storage.list({ createdBy: options?.createdBy }); + return await this.storage.list({ + createdBy: options?.createdBy, + status: options?.status, + }); } private deferredDispatch = defer(); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 0e0ebd706b..89bcce3cc9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -14,20 +14,20 @@ * limitations under the License. */ -import { JsonValue, JsonObject, HumanDuration } from '@backstage/types'; +import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; import { TaskSpec, TaskStep } from '@backstage/plugin-scaffolder-common'; -import { TaskSecrets } from '@backstage/plugin-scaffolder-node'; import { - TemplateAction, - TaskStatus as _TaskStatus, - TaskCompletionState as _TaskCompletionState, SerializedTask as _SerializedTask, - TaskEventType as _TaskEventType, SerializedTaskEvent as _SerializedTaskEvent, - TaskBrokerDispatchResult as _TaskBrokerDispatchResult, - TaskBrokerDispatchOptions as _TaskBrokerDispatchOptions, - TaskContext as _TaskContext, TaskBroker as _TaskBroker, + TaskBrokerDispatchOptions as _TaskBrokerDispatchOptions, + TaskBrokerDispatchResult as _TaskBrokerDispatchResult, + TaskCompletionState as _TaskCompletionState, + TaskContext as _TaskContext, + TaskEventType as _TaskEventType, + TaskSecrets, + TaskStatus as _TaskStatus, + TemplateAction, } from '@backstage/plugin-scaffolder-node'; /** @@ -190,7 +190,10 @@ export interface TaskStore { tasks: { taskId: string }[]; }>; - list?(options: { createdBy?: string }): Promise<{ tasks: SerializedTask[] }>; + list?(options: { + createdBy?: string; + status?: TaskStatus; + }): Promise<{ tasks: SerializedTask[] }>; emitLogEvent(options: TaskStoreEmitOptions): Promise; diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index af06fc4034..51e11d630f 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -18,7 +18,6 @@ import { DatabaseManager, loggerToWinstonLogger, PluginDatabaseManager, - UrlReaders, } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; import { ConfigReader } from '@backstage/config'; @@ -47,7 +46,8 @@ import { } from '@backstage/plugin-permission-common'; import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; import { AutocompleteHandler } from '@backstage/plugin-scaffolder-node/alpha'; -import { MiddlewareFactory } from '@backstage/backend-app-api'; +import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter'; +import { UrlReaders } from '@backstage/backend-defaults/urlReader'; const mockAccess = jest.fn(); @@ -432,10 +432,11 @@ describe('createRouter', () => { }); const response = await request(app).get( - `/v2/tasks?createdBy=user:default/foo`, + `/v2/tasks?createdBy=user:default/foo&status=completed`, ); expect(taskBroker.list).toHaveBeenCalledWith({ createdBy: 'user:default/foo', + status: 'completed', }); expect(response.status).toEqual(200); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 769a042a78..3eec4e4450 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -15,12 +15,9 @@ */ import { - HostDiscovery, - PluginDatabaseManager, - UrlReader, createLegacyAuthAdapters, + HostDiscovery, } from '@backstage/backend-common'; -import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { CatalogApi } from '@backstage/catalog-client'; import { CompoundEntityRef, @@ -35,22 +32,22 @@ import { ScmIntegrations } from '@backstage/integration'; import { HumanDuration, JsonObject, JsonValue } from '@backstage/types'; import { TaskSpec, + TemplateEntityStepV1beta3, TemplateEntityV1beta3, templateEntityV1beta3Validator, TemplateParametersV1beta3, - TemplateEntityStepV1beta3, } from '@backstage/plugin-scaffolder-common'; import { RESOURCE_TYPE_SCAFFOLDER_ACTION, RESOURCE_TYPE_SCAFFOLDER_TEMPLATE, scaffolderActionPermissions, + scaffolderTaskPermissions, scaffolderTemplatePermissions, taskCancelPermission, taskCreatePermission, taskReadPermission, templateParameterReadPermission, templateStepReadPermission, - scaffolderTaskPermissions, } from '@backstage/plugin-scaffolder-common/alpha'; import express from 'express'; import Router from 'express-promise-router'; @@ -58,8 +55,9 @@ import { validate } from 'jsonschema'; import { Logger } from 'winston'; import { z } from 'zod'; import { - TemplateAction, TaskBroker, + TaskStatus, + TemplateAction, TemplateFilter, TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; @@ -87,6 +85,9 @@ import { HttpAuthService, LifecycleService, PermissionsService, + UrlReaderService, + SchedulerService, + DatabaseService, } from '@backstage/backend-plugin-api'; import { IdentityApi, @@ -139,15 +140,16 @@ function isActionPermissionRuleInput( * RouterOptions * * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export interface RouterOptions { logger: Logger; config: Config; - reader: UrlReader; + reader: UrlReaderService; lifecycle?: LifecycleService; - database: PluginDatabaseManager; + database: DatabaseService; catalogClient: CatalogApi; - scheduler?: PluginTaskScheduler; + scheduler?: SchedulerService; actions?: TemplateAction[]; /** * @deprecated taskWorkers is deprecated in favor of concurrentTasksLimit option with a single TaskWorker @@ -257,6 +259,7 @@ const readDuration = ( /** * A method to create a router for the scaffolder backend plugin. * @public + * @deprecated Please migrate to the new backend system as this will be removed in the future. */ export async function createRouter( options: RouterOptions, @@ -587,8 +590,17 @@ export async function createRouter( ); } + const [statusQuery] = [req.query.status].flat(); + if ( + typeof statusQuery !== 'string' && + typeof statusQuery !== 'undefined' + ) { + throw new InputError('status query parameter must be a string'); + } + const tasks = await taskBroker.list({ createdBy: userEntityRef, + status: statusQuery ? (statusQuery as TaskStatus) : undefined, }); res.status(200).json(tasks); diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 12f3f9e235..7e121a4e93 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-scaffolder-common +## 1.5.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1 + - @backstage/catalog-model@1.6.0 + - @backstage/types@1.1.1 + +## 1.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.8.1-next.1 + +## 1.5.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.1 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## 1.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.8.1-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/types@1.1.1 + +## 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..2dced65144 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.5", "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..dd4ed1e2a1 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,77 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.24.0 + - @backstage/backend-test-utils@0.5.0 + - @backstage/plugin-scaffolder-node@0.4.9 + - @backstage/types@1.1.1 + +## 0.1.10-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.3 + - @backstage/backend-test-utils@0.4.5-next.3 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.3 + +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@0.4.5-next.2 + - @backstage/plugin-scaffolder-node@0.4.9-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/types@1.1.1 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.23.4-next.1 + - @backstage/backend-test-utils@0.4.5-next.1 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node@0.4.9-next.1 + +## 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..9675a689ab 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", "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..d04d7f16e0 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,102 @@ # @backstage/plugin-scaffolder-node +## 0.4.9 + +### Patch Changes + +- 389f5a4: Update deprecated url-reader-related imports. +- c544f81: Add support for status filtering in scaffolder tasks endpoint +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0 + - @backstage/backend-common@0.24.0 + - @backstage/integration@1.14.0 + - @backstage/catalog-model@1.6.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5 + +## 0.4.9-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.3 + - @backstage/backend-common@0.23.4-next.3 + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.14.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.2 + +## 0.4.9-next.2 + +### Patch Changes + +- c544f81: Add support for status filtering in scaffolder tasks endpoint +- Updated dependencies + - @backstage/backend-plugin-api@0.8.0-next.2 + - @backstage/backend-common@0.23.4-next.2 + - @backstage/plugin-scaffolder-common@1.5.5-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 0.4.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.7.1-next.1 + - @backstage/backend-common@0.23.4-next.1 + - @backstage/integration@1.14.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.5-next.0 + - @backstage/catalog-model@1.5.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## 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/api-report.md b/plugins/scaffolder-node/api-report.md index 66e8ccd678..8b38008eee 100644 --- a/plugins/scaffolder-node/api-report.md +++ b/plugins/scaffolder-node/api-report.md @@ -16,7 +16,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { SpawnOptionsWithoutStdio } from 'child_process'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { UserEntity } from '@backstage/catalog-model'; import { Writable } from 'stream'; import { z } from 'zod'; @@ -196,7 +196,7 @@ export type ExecuteShellCommandOptions = { // @public export function fetchContents(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; baseUrl?: string; fetchUrl?: string; @@ -206,7 +206,7 @@ export function fetchContents(options: { // @public export function fetchFile(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; baseUrl?: string; fetchUrl?: string; @@ -315,7 +315,7 @@ export interface TaskBroker { // (undocumented) get(taskId: string): Promise; // (undocumented) - list?(options?: { createdBy?: string }): Promise<{ + list?(options?: { createdBy?: string; status?: TaskStatus }): Promise<{ tasks: SerializedTask[]; }>; // (undocumented) diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 2568809647..52964f814a 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", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-node/src/actions/fetch.test.ts b/plugins/scaffolder-node/src/actions/fetch.test.ts index ea7f458fde..f831d20f35 100644 --- a/plugins/scaffolder-node/src/actions/fetch.test.ts +++ b/plugins/scaffolder-node/src/actions/fetch.test.ts @@ -18,7 +18,7 @@ jest.mock('fs-extra'); import fs from 'fs-extra'; import { resolve as resolvePath } from 'path'; -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { fetchContents, fetchFile } from './fetch'; @@ -39,7 +39,7 @@ describe('fetchContents helper', () => { const readUrl = jest.fn(); const readTree = jest.fn(); - const reader: UrlReader = { + const reader: UrlReaderService = { readUrl, readTree, search: jest.fn(), diff --git a/plugins/scaffolder-node/src/actions/fetch.ts b/plugins/scaffolder-node/src/actions/fetch.ts index 63bc4b1352..a0e8dd9f85 100644 --- a/plugins/scaffolder-node/src/actions/fetch.ts +++ b/plugins/scaffolder-node/src/actions/fetch.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; +import { UrlReaderService } from '@backstage/backend-plugin-api'; import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; @@ -28,7 +28,7 @@ import path from 'path'; * @public */ export async function fetchContents(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; baseUrl?: string; fetchUrl?: string; @@ -67,7 +67,7 @@ export async function fetchContents(options: { * @public */ export async function fetchFile(options: { - reader: UrlReader; + reader: UrlReaderService; integrations: ScmIntegrations; baseUrl?: string; fetchUrl?: string; diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index a2adb97b9c..8324e352f2 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -180,5 +180,8 @@ export interface TaskBroker { get(taskId: string): Promise; - list?(options?: { createdBy?: string }): Promise<{ tasks: SerializedTask[] }>; + list?(options?: { + createdBy?: string; + status?: TaskStatus; + }): Promise<{ tasks: SerializedTask[] }>; } diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index baa34dbc5b..57799ab836 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,160 @@ # @backstage/plugin-scaffolder-react +## 1.11.0 + +### Minor Changes + +- 8839381: Add scaffolder option to display object items in separate rows on review page + +### Patch Changes + +- 072c00c: Fixed a bug in `DefaultTableOutputs` where output elements overlapped on smaller screen sizes +- 46e5e55: Change scaffolder widgets to use `TextField` component for more flexibility in theme overrides. +- 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 +- 04759f2: Fix null check in `isJsonObject` utility function for scaffolder review state component +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3 + - @backstage/core-components@0.14.10 + - @backstage/catalog-model@1.6.0 + - @backstage/catalog-client@1.6.6 + - @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.25 + - @backstage/plugin-scaffolder-common@1.5.5 + +## 1.11.0-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.6.0-next.0 + - @backstage/plugin-catalog-react@1.12.3-next.3 + - @backstage/catalog-client@1.6.6-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-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.2 + +## 1.11.0-next.2 + +### Patch Changes + +- 072c00c: Fixed a bug in `DefaultTableOutputs` where output elements overlapped on smaller screen sizes +- 04759f2: Fix null check in `isJsonObject` utility function for scaffolder review state component +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.2 + - @backstage/plugin-permission-react@0.4.25-next.1 + - @backstage/plugin-scaffolder-common@1.5.5-next.1 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.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 + +## 1.11.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.12.3-next.1 + - @backstage/plugin-permission-react@0.4.25-next.0 + - @backstage/plugin-scaffolder-common@1.5.5-next.0 + - @backstage/catalog-client@1.6.5 + - @backstage/catalog-model@1.5.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 + +## 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-alpha.md b/plugins/scaffolder-react/api-report-alpha.md index 4655cbfd62..545cc02e9e 100644 --- a/plugins/scaffolder-react/api-report-alpha.md +++ b/plugins/scaffolder-react/api-report-alpha.md @@ -152,7 +152,10 @@ export type ScaffolderReactTemplateCategoryPickerClassKey = 'root' | 'label'; // @alpha export const SecretWidget: ( - props: Pick, + props: Pick< + WidgetProps, + 'name' | 'onChange' | 'schema' | 'required' | 'disabled' + >, ) => React_2.JSX.Element; // @alpha 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 d5fe7ca9cb..97e04c8d42 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", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library", @@ -78,6 +78,7 @@ "@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/PasswordWidget/PasswordWidget.tsx b/plugins/scaffolder-react/src/next/components/PasswordWidget/PasswordWidget.tsx index 6c1c2c176f..1807df8ae2 100644 --- a/plugins/scaffolder-react/src/next/components/PasswordWidget/PasswordWidget.tsx +++ b/plugins/scaffolder-react/src/next/components/PasswordWidget/PasswordWidget.tsx @@ -15,8 +15,7 @@ */ import { WidgetProps } from '@rjsf/utils'; -import InputLabel from '@material-ui/core/InputLabel'; -import Input from '@material-ui/core/Input'; +import TextField from '@material-ui/core/TextField'; import React from 'react'; import FormHelperText from '@material-ui/core/FormHelperText'; import { MarkdownContent } from '@backstage/core-components'; @@ -32,9 +31,9 @@ export const PasswordWidget = ( return ( <> - {title} - { onChange(e.target.value); 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..69b651e672 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,16 @@ describe('ReviewState', () => { ); expect(getByRole('row', { name: 'Name lols' })).toBeInTheDocument(); + expect(getByRole('row', { name: 'Test bob' })).toBeInTheDocument(); + expect(getByRole('row', { name: 'Nest > Foo lols' })).toBeInTheDocument(); }); it('should display enum label from enumNames', async () => { const formState = { name: 'type2', + nest: { + foo: 'type2', + }, }; const schemas: ParsedTemplateSchema[] = [ @@ -153,6 +193,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 +220,9 @@ describe('ReviewState', () => { expect( queryByRole('row', { name: 'Name Label-type2' }), ).toBeInTheDocument(); + expect( + queryByRole('row', { name: 'Nest > Foo Label-type2' }), + ).toBeInTheDocument(); }); it('should display enum value if no corresponding enumNames', async () => { @@ -202,4 +256,182 @@ 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: 'Name > Foo type3' }), + ).toBeInTheDocument(); + expect( + queryByRole('row', { name: 'Name > Bar type4' }), + ).toBeInTheDocument(); + }); + + it('should display nested objects in separate rows', async () => { + const formState = { + name: { + foo: 'type3', + bar: 'type4', + example: { + test: 'type6', + foo: 'type7', + }, + }, + }; + + 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', + }, + foo: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + schema: {}, + title: 'test', + uiSchema: {}, + }, + ]; + + const { queryByRole } = render( + , + ); + + expect( + queryByRole('row', { name: 'Name > Foo type3' }), + ).toBeInTheDocument(); + expect( + queryByRole('row', { name: 'Name > Bar type4' }), + ).toBeInTheDocument(); + expect( + queryByRole('row', { name: 'Name > Example > Test type6' }), + ).toBeInTheDocument(); + expect( + queryByRole('row', { name: 'Name > Example > Foo type7' }), + ).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: 'Name > Foo type3' }), + ).toBeInTheDocument(); + expect( + queryByRole('row', { name: 'Name > Bar type4' }), + ).toBeInTheDocument(); + expect( + queryByRole('row', { name: 'Name > Example > 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..fb6a592dce 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, formatKey } from './util'; /** * The props for the {@link ReviewState} component. @@ -28,6 +29,58 @@ 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 [[key, backstageReviewOptions.mask]]; + } + if (backstageReviewOptions.show === false) { + return []; + } + } + + if ( + definitionInSchema['ui:widget'] === 'password' || + definitionInSchema['ui:field']?.toLocaleLowerCase('en-us') === 'secret' + ) { + return [[key, '******']]; + } + + if (definitionInSchema.enum && definitionInSchema.enumNames) { + return [ + [ + 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 [[key, value]]; +} + /** * The component used by the {@link Stepper} to render the review step. * @alpha @@ -35,44 +88,16 @@ 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), ); - return ; + const options = { + titleFormat: formatKey, + }; + return ; }; 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..bc8001baea --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/ReviewState/util.test.ts @@ -0,0 +1,80 @@ +/* + * 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 { isJsonObject, formatKey } 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); + }); + + it('should return false for null values', () => { + expect(isJsonObject(null)).toBe(false); + }); +}); + +describe('formatKey', () => { + it('should replace / with > globally in the key', () => { + expect(formatKey('simple/key')).toBe('Simple > Key'); + }); + + it('should leave a top-level key untouched', () => { + expect(formatKey('topLevel')).toBe('Top Level'); + }); + + it('should handle keys with a leading slash', () => { + expect(formatKey('/simple/key')).toBe('Simple > Key'); + }); + + it('should handle keys with trailing slash', () => { + expect(formatKey('parent/child/')).toBe('Parent > Child'); + }); + + it('should handle empty string', () => { + expect(formatKey('')).toBe(''); + }); + + it('should handle keys with multiple consecutive slashes', () => { + expect(formatKey('parent//child')).toBe('Parent > Child'); + }); + + it('should handle keys with only slashes', () => { + expect(formatKey('////')).toBe(''); + }); + + it('should handle keys with spaces', () => { + expect(formatKey('parent/child with spaces')).toBe( + 'Parent > Child With Spaces', + ); + }); + + it('should remove special characters', () => { + expect(formatKey('parent/child@!#$%^&*()')).toBe('Parent > 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..44311ee206 --- /dev/null +++ b/plugins/scaffolder-react/src/next/components/ReviewState/util.ts @@ -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 { JsonObject, JsonValue } from '@backstage/types'; +import { startCase } from 'lodash'; + +export function isJsonObject(value?: JsonValue): value is JsonObject { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +// Helper function to format a key into a human-readable string +export function formatKey(key: string): string { + const parts = key.split('/'); + return parts + .filter(Boolean) + .map(part => startCase(part)) + .join(' > '); +} diff --git a/plugins/scaffolder-react/src/next/components/SecretWidget/SecretWidget.tsx b/plugins/scaffolder-react/src/next/components/SecretWidget/SecretWidget.tsx index e2d139f98b..6b9094c199 100644 --- a/plugins/scaffolder-react/src/next/components/SecretWidget/SecretWidget.tsx +++ b/plugins/scaffolder-react/src/next/components/SecretWidget/SecretWidget.tsx @@ -16,8 +16,7 @@ import { WidgetProps } from '@rjsf/utils'; import { useTemplateSecrets } from '@backstage/plugin-scaffolder-react'; -import InputLabel from '@material-ui/core/InputLabel'; -import Input from '@material-ui/core/Input'; +import TextField from '@material-ui/core/TextField'; import React from 'react'; /** @@ -25,29 +24,38 @@ import React from 'react'; * @alpha */ export const SecretWidget = ( - props: Pick, + props: Pick< + WidgetProps, + 'name' | 'onChange' | 'schema' | 'required' | 'disabled' + >, ) => { const { setSecrets, secrets } = useTemplateSecrets(); const { name, onChange, - schema: { title }, + schema: { title, minLength, maxLength }, + required, + disabled, } = props; return ( - <> - {title} - { - onChange(Array(e.target?.value.length).fill('*').join('')); - setSecrets({ [name]: e.target?.value }); - }} - value={secrets[name] ?? ''} - type="password" - autoComplete="off" - /> - + { + onChange(Array(e.target.value.length).fill('*').join('')); + setSecrets({ [name]: e.target.value }); + }} + value={secrets[name] ?? ''} + type="password" + autoComplete="off" + required={required} + disabled={disabled} + inputProps={{ + minLength, + maxLength, + }} + /> ); }; 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} >